1 /*
2 * Licensed to the Apache Software Foundation (ASF) under one
3 * or more contributor license agreements. See the NOTICE file
4 * distributed with this work for additional information
5 * regarding copyright ownership. The ASF licenses this file
6 * to you under the Apache License, Version 2.0 (the
7 * "License"); you may not use this file except in compliance
8 * with the License. You may obtain a copy of the License at
9 *
10 * http://www.apache.org/licenses/LICENSE-2.0
11 *
12 * Unless required by applicable law or agreed to in writing,
13 * software distributed under the License is distributed on an
14 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15 * KIND, either express or implied. See the License for the
16 * specific language governing permissions and limitations
17 * under the License.
18 */
19 package org.apache.maven.api;
20
21 import javax.tools.DocumentationTool;
22 import javax.tools.JavaFileManager;
23 import javax.tools.StandardLocation;
24
25 import java.io.File;
26 import java.nio.file.Path;
27 import java.util.Objects;
28 import java.util.Optional;
29 import java.util.StringJoiner;
30
31 import org.apache.maven.api.annotations.Experimental;
32 import org.apache.maven.api.annotations.Nonnull;
33
34 /**
35 * The option of a Java command-line tool where to place the paths to some dependencies.
36 * A {@code PathType} can identify the class path, the module path, the patches for a specific module,
37 * or another kind of path.
38 *
39 * <p>One path type is handled in a special way: unlike other options,
40 * the paths specified in a {@code --patch-module} Java option is effective only for a specified module.
41 * This type is created by calls to {@link #patchModule(String)} and a new instance must be created for
42 * every module to patch.</p>
43 *
44 * <p>Path types are often exclusive. For example, a dependency should not be both on the Java class path
45 * and on the Java module path.</p>
46 *
47 * <h2>Relationship with Java compiler standard location</h2>
48 * This enumeration is closely related to the {@link JavaFileManager.Location} enumerations.
49 * A difference is that the latter enumerates input and output files, while {@code JavaPathType}
50 * enumerates only input dependencies. Another difference is that {@code JavaPathType} contains
51 * some enumeration values used only at runtime and therefore not available in {@code javax.tool},
52 * such as agent paths.
53 *
54 * @see org.apache.maven.api.services.DependencyResolverResult#getDispatchedPaths()
55 *
56 * @since 4.0.0
57 */
58 @Experimental
59 public enum JavaPathType implements PathType {
60 /**
61 * The path identified by the Java {@code --class-path} option.
62 * Used for compilation, execution and Javadoc among others.
63 * The Java tools location is {@link StandardLocation#CLASS_PATH}.
64 *
65 * <h4>Context-sensitive interpretation</h4>
66 * A dependency with this path type will not necessarily be placed on the class path.
67 * There are two circumstances where the dependency may nevertheless be placed somewhere else:
68 *
69 * <ul>
70 * <li>If {@link #MODULES} path type is also set, then the dependency can be placed either on the
71 * class path or on the module path, but only one of those. The choice is up to the plugin,
72 * possibly using heuristic rules (Maven 3 behavior).</li>
73 * <li>If a {@link #patchModule(String)} is also set and the main JAR file is placed on the module path,
74 * then the test dependency will be placed on the Java {@code --patch-module} option instead of the
75 * class path.</li>
76 * </ul>
77 */
78 CLASSES(StandardLocation.CLASS_PATH, "--class-path"),
79
80 /**
81 * The path identified by the Java {@code --module-path} option.
82 * Used for compilation, execution and Javadoc among others.
83 * The Java tools location is {@link StandardLocation#MODULE_PATH}.
84 *
85 * <h4>Context-sensitive interpretation</h4>
86 * A dependency with this flag will not necessarily be placed on the module path.
87 * There are two circumstances where the dependency may nevertheless be placed somewhere else:
88 *
89 * <ul>
90 * <li>If {@link #CLASSES} path type is also set, then the dependency <em>should</em> be placed on the
91 * module path, but is also compatible with placement on the class path. Compatibility can
92 * be achieved, for example, by repeating in the {@code META-INF/services/} directory the services
93 * that are declared in the {@code module-info.class} file. In that case, the path type can be chosen
94 * by the plugin.</li>
95 * <li>If a {@link #patchModule(String)} is also set and the main JAR file is placed on the module path,
96 * then the test dependency will be placed on the Java {@code --patch-module} option instead of the
97 * {@code --module-path} option.</li>
98 * </ul>
99 */
100 MODULES(StandardLocation.MODULE_PATH, "--module-path"),
101
102 /**
103 * The path identified by the Java {@code --upgrade-module-path} option.
104 * The Java tools location is {@link StandardLocation#UPGRADE_MODULE_PATH}.
105 */
106 UPGRADE_MODULES(StandardLocation.UPGRADE_MODULE_PATH, "--upgrade-module-path"),
107
108 /**
109 * The path identified by the Java {@code --patch-module} option.
110 * The Java tools location is {@link StandardLocation#PATCH_MODULE_PATH}.
111 *
112 * Note that this option is incomplete, because it must be followed by a module name.
113 * Use this type only when the module to patch is unknown.
114 *
115 * @see #patchModule(String)
116 */
117 PATCH_MODULE(StandardLocation.PATCH_MODULE_PATH, "--patch-module"),
118
119 /**
120 * The path identified by the Java {@code --processor-path} option.
121 * The Java tools location is {@link StandardLocation#ANNOTATION_PROCESSOR_PATH}.
122 */
123 PROCESSOR_CLASSES(StandardLocation.ANNOTATION_PROCESSOR_PATH, "--processor-path"),
124
125 /**
126 * The path identified by the Java {@code --processor-module-path} option.
127 * The Java tools location is {@link StandardLocation#ANNOTATION_PROCESSOR_MODULE_PATH}.
128 */
129 PROCESSOR_MODULES(StandardLocation.ANNOTATION_PROCESSOR_MODULE_PATH, "--processor-module-path"),
130
131 /**
132 * The path identified by the Java {@code -agentpath} option.
133 */
134 AGENT(null, "-agentpath"),
135
136 /**
137 * The path identified by the Javadoc {@code -doclet} option.
138 * The Java tools location is {@link DocumentationTool.Location#DOCLET_PATH}.
139 */
140 DOCLET(DocumentationTool.Location.DOCLET_PATH, "-doclet"),
141
142 /**
143 * The path identified by the Javadoc {@code -tagletpath} option.
144 * The Java tools location is {@link DocumentationTool.Location#TAGLET_PATH}.
145 */
146 TAGLETS(DocumentationTool.Location.TAGLET_PATH, "-tagletpath");
147
148 /**
149 * Creates a path identified by the Java {@code --patch-module} option.
150 * Contrarily to the other types of paths, this path is applied to only
151 * one specific module. Used for compilation and execution among others.
152 *
153 * <h4>Context-sensitive interpretation</h4>
154 * This path type makes sense only when a main module is added on the module path by another dependency.
155 * In no main module is found, the patch dependency may be added on the class path or module path
156 * depending on whether {@link #CLASSES} or {@link #MODULES} is present.
157 *
158 * @param moduleName name of the module on which to apply the path
159 * @return an identification of the patch-module path for the given module.
160 *
161 * @see Modular#moduleName()
162 */
163 @Nonnull
164 public static Modular patchModule(@Nonnull String moduleName) {
165 return PATCH_MODULE.new Modular(moduleName);
166 }
167
168 /**
169 * The {@code javax.tool} enumeration value corresponding to this {@code JavaPathType}, or {@code null} if none.
170 *
171 * @see #location()
172 */
173 private final JavaFileManager.Location location;
174
175 /**
176 * The tools option for this path, or {@code null} if none.
177 *
178 * @see #option()
179 */
180 private final String option;
181
182 /**
183 * Creates a new enumeration value for a path associated to the given tool option.
184 *
185 * @param location the {@code javax.tool} enumeration value, or {@code null} if none.
186 * @param option the Java tools option for this path, or {@code null} if none
187 */
188 JavaPathType(JavaFileManager.Location location, String option) {
189 this.location = location;
190 this.option = option;
191 }
192
193 /**
194 * Returns the unique name of this path type.
195 *
196 * @return the programmatic name of this enumeration value
197 */
198 @Override
199 public String id() {
200 return name();
201 }
202
203 /**
204 * Returns the identification of this path in the {@code javax.tool} API.
205 * The value may be an instance of {@link StandardLocation} or {@link DocumentationTool.Location},
206 * depending on which tool will use this location.
207 *
208 * @return the {@code javax.tool} enumeration value corresponding to this {@code JavaPathType}
209 */
210 public Optional<JavaFileManager.Location> location() {
211 return Optional.ofNullable(location);
212 }
213
214 /**
215 * Returns the path type associated to the given {@code javax.tool} location.
216 * This method is the converse of {@link #location()}.
217 *
218 * @param location identification of a path in the {@code javax.tool} API
219 * @return Java path type associated to the given location
220 */
221 public static Optional<JavaPathType> valueOf(JavaFileManager.Location location) {
222 for (JavaPathType type : JavaPathType.values()) {
223 if (location.equals(type.location)) {
224 return Optional.of(type);
225 }
226 }
227 return Optional.empty();
228 }
229
230 /**
231 * Returns the name of the tool option for this path. For example, if this path type
232 * is {@link #MODULES}, then this method returns {@code "--module-path"}. The option
233 * does not include the {@linkplain Modular#moduleName() module name} on which it applies.
234 *
235 * @return the name of the tool option for this path type
236 */
237 @Nonnull
238 @Override
239 public Optional<String> option() {
240 return Optional.ofNullable(option);
241 }
242
243 /**
244 * Returns the option followed by a string representation of the given path elements.
245 * For example, if this type is {@link #MODULES}, then the option is {@code "--module-path"}
246 * followed by the specified path elements.
247 *
248 * @param paths the path to format as a tool option
249 * @return the option associated to this path type followed by the given path elements,
250 * or an empty array if there is no path element
251 * @throws IllegalStateException if no option is associated to this path type
252 */
253 @Nonnull
254 @Override
255 public String[] option(Iterable<? extends Path> paths) {
256 return format(null, paths);
257 }
258
259 /**
260 * Implementation shared with {@link Modular}.
261 */
262 final String[] format(String moduleName, Iterable<? extends Path> paths) {
263 if (option == null) {
264 throw new IllegalStateException("No option is associated to this path type.");
265 }
266 String prefix = (moduleName == null) ? "" : (moduleName + '=');
267 StringJoiner joiner = new StringJoiner(File.pathSeparator, prefix, "");
268 joiner.setEmptyValue("");
269 for (Path p : paths) {
270 joiner.add(p.toString());
271 }
272 String value = joiner.toString();
273 if (value.isEmpty()) {
274 return new String[0];
275 }
276 return new String[] {option, value};
277 }
278
279 /**
280 * {@return a string representation of this path type for debugging purposes}.
281 */
282 @Override
283 public String toString() {
284 return "PathType[" + id() + "]";
285 }
286
287 /**
288 * Type of path which is applied to only one specific Java module.
289 * The main case is the Java {@code --patch-module} option.
290 *
291 * @see #PATCH_MODULE
292 * @see #patchModule(String)
293 */
294 public final class Modular implements PathType {
295 /**
296 * Name of the module for which a path is specified.
297 */
298 @Nonnull
299 private final String moduleName;
300
301 /**
302 * Creates a new path type for the specified module.
303 *
304 * @param moduleName name of the module for which a path is specified
305 */
306 private Modular(@Nonnull String moduleName) {
307 this.moduleName = Objects.requireNonNull(moduleName);
308 }
309
310 /**
311 * Returns the type of path without indication about the target module.
312 * This is usually {@link #PATCH_MODULE}.
313 *
314 * @return type of path without indication about the target module
315 */
316 @Nonnull
317 public JavaPathType rawType() {
318 return JavaPathType.this;
319 }
320
321 /**
322 * Returns the name of the tool option for this path, including the module name.
323 *
324 * @return name of the tool option for this path, including the module name
325 */
326 @Override
327 public String id() {
328 return JavaPathType.this.name() + ":" + moduleName;
329 }
330
331 /**
332 * Returns the name of the tool option for this path, not including the module name.
333 *
334 * @return name of the tool option for this path, not including the module name
335 */
336 @Nonnull
337 @Override
338 public String name() {
339 return JavaPathType.this.name();
340 }
341
342 /**
343 * Returns the name of the module for which a path is specified
344 *
345 * @return name of the module for which a path is specified
346 */
347 @Nonnull
348 public String moduleName() {
349 return moduleName;
350 }
351
352 /**
353 * Returns the name of the tool option for this path.
354 * The option does not include the {@linkplain #moduleName() module name} on which it applies.
355 *
356 * @return the name of the tool option for this path type
357 */
358 @Nonnull
359 @Override
360 public Optional<String> option() {
361 return JavaPathType.this.option();
362 }
363
364 /**
365 * Returns the option followed by a string representation of the given path elements.
366 * The path elements are separated by an option-specific or platform-specific separator.
367 * If the given {@code paths} argument contains no element, then this method returns an empty string.
368 *
369 * @param paths the path to format as a string
370 * @return the option associated to this path type followed by the given path elements,
371 * or an empty array if there is no path element.
372 */
373 @Nonnull
374 @Override
375 public String[] option(Iterable<? extends Path> paths) {
376 return format(moduleName, paths);
377 }
378
379 /**
380 * {@return a hash code value based on the raw type and module name}.
381 */
382 @Override
383 public int hashCode() {
384 return rawType().hashCode() + 17 * moduleName.hashCode();
385 }
386
387 /**
388 * {@return whether the given object represents the same type of path as this object}.
389 */
390 @Override
391 public boolean equals(Object obj) {
392 if (obj instanceof Modular m) {
393 return rawType() == m.rawType() && moduleName.equals(m.moduleName);
394 }
395 return false;
396 }
397
398 /**
399 * Returns the programmatic name of this path type, including the module to patch.
400 * For example, if this type was created by {@code JavaPathType.patchModule("foo.bar")},
401 * then this method returns {@code "PathType[PATCH_MODULE:foo.bar]")}.
402 *
403 * @return the programmatic name together with the module name on which it applies
404 */
405 @Nonnull
406 @Override
407 public String toString() {
408 return "PathType[" + id() + "]";
409 }
410 }
411 }