Skip to content

Commit

Permalink
[MNG-8015] Control the type of path where each dependency can be placed
Browse files Browse the repository at this point in the history
  • Loading branch information
desruisseaux authored and gnodet committed Feb 10, 2024
1 parent fc9a7d8 commit 0477c52
Show file tree
Hide file tree
Showing 28 changed files with 1,851 additions and 243 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@
import org.apache.maven.api.annotations.Nonnull;

/**
* Interface that defines some kind of enums that can be extended by Maven plugins or extensions.
*
* Implementation must have {@code equals()} and {@code hashCode()} implemented, so implementations of this interface
* can be used as keys.
*/
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,319 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.maven.api;

import java.io.File;
import java.nio.file.Path;
import java.util.Objects;
import java.util.Optional;
import java.util.StringJoiner;

import org.apache.maven.api.annotations.Experimental;
import org.apache.maven.api.annotations.Nonnull;

/**
* The option of a Java command-line tool where to place the paths to some dependencies.
* A {@code PathType} can identify the class-path, the module-path, the patches for a specific module,
* or another kind of path.
*
* <p>One path type is handled in a special way: unlike other options,
* the paths specified in a {@code --patch-module} Java option is effective only for a specified module.
* This type is created by calls to {@link #patchModule(String)} and a new instance must be created for
* every module to patch.</p>
*
* <p>Path types are often exclusive. For example, a dependency should not be both on the Java class-path
* and on the Java module-path.</p>
*
* @see org.apache.maven.api.services.DependencyResolverResult#getDispatchedPaths()
*
* @since 4.0.0
*/
@Experimental
public enum JavaPathType implements PathType {
/**
* The path identified by the Java {@code --class-path} option.
* Used for compilation, execution and Javadoc among others.
*
* <p><b>Context-sensitive interpretation:</b>
* A dependency with this path type will not necessarily be placed on the class-path.
* There are two circumstances where the dependency may nevertheless be placed somewhere else:
* </p>
* <ul>
* <li>If {@link #MODULES} path type is also set, then the dependency can be placed either on the
* class-path or on the module-path, but only one of those. The choice is up to the plugin,
* possibly using heuristic rules (Maven 3 behavior).</li>
* <li>If a {@link #patchModule(String)} is also set and the main JAR file is placed on the module-path,
* then the test dependency will be placed on the Java {@code --patch-module} option instead of the
* class-path.</li>
* </ul>
*/
CLASSES("--class-path"),

/**
* The path identified by the Java {@code --module-path} option.
* Used for compilation, execution and Javadoc among others.
*
* <p><b>Context-sensitive interpretation:</b>
* A dependency with this flag will not necessarily be placed on the module-path.
* There are two circumstances where the dependency may nevertheless be placed somewhere else:
* </p>
* <ul>
* <li>If {@link #CLASSES} path type is also set, then the dependency <em>should</em> be placed on the
* module-path, but is also compatible with placement on the class-path. Compatibility can
* be achieved, for example, by repeating in the {@code META-INF/services/} directory the services
* that are declared in the {@code module-info.class} file. In that case, the path type can be chosen
* by the plugin.</li>
* <li>If a {@link #patchModule(String)} is also set and the main JAR file is placed on the module-path,
* then the test dependency will be placed on the Java {@code --patch-module} option instead of the
* {@code --module-path} option.</li>
* </ul>
*/
MODULES("--module-path"),

/**
* The path identified by the Java {@code --upgrade-module-path} option.
*/
UPGRADE_MODULES("--upgrade-module-path"),

/**
* The path identified by the Java {@code --patch-module} option.
* Note that this option is incomplete, because it must be followed by a module name.
* Use this type only when the module to patch is unknown.
*
* @see #patchModule(String)
*/
PATCH_MODULE("--patch-module"),

/**
* The path identified by the Java {@code --processor-path} option.
*/
PROCESSOR_CLASSES("--processor-path"),

/**
* The path identified by the Java {@code --processor-module-path} option.
*/
PROCESSOR_MODULES("--processor-module-path"),

/**
* The path identified by the Java {@code -agentpath} option.
*/
AGENT("-agentpath"),

/**
* The path identified by the Javadoc {@code -doclet} option.
*/
DOCLET("-doclet"),

/**
* The path identified by the Javadoc {@code -tagletpath} option.
*/
TAGLETS("-tagletpath");

/**
* Creates a path identified by the Java {@code --patch-module} option.
* Contrarily to the other types of paths, this path is applied to only
* one specific module. Used for compilation and execution among others.
*
* <p><b>Context-sensitive interpretation:</b>
* This path type makes sense only when a main module is added on the module-path by another dependency.
* In no main module is found, the patch dependency may be added on the class-path or module-path
* depending on whether {@link #CLASSES} or {@link #MODULES} is present.
* </p>
*
* @param moduleName name of the module on which to apply the path
* @return an identification of the patch-module path for the given module.
*
* @see Modular#moduleName()
*/
@Nonnull
public static Modular patchModule(@Nonnull String moduleName) {
return PATCH_MODULE.new Modular(moduleName);
}

/**
* The tools option for this path, or {@code null} if none.
*
* @see #option()
*/
private final String option;

/**
* Creates a new enumeration value for a path associated to the given tool option.
*
* @param option the Java tools option for this path, or {@code null} if none
*/
JavaPathType(String option) {
this.option = option;
}

@Override
public String id() {
return name();
}

/**
* Returns the name of the tool option for this path. For example, if this path type
* is {@link #MODULES}, then this method returns {@code "--module-path"}. The option
* does not include the {@linkplain Modular#moduleName() module name} on which it applies.
*
* @return the name of the tool option for this path type
*/
@Nonnull
@Override
public Optional<String> option() {
return Optional.ofNullable(option);
}

/**
* Returns the option followed by a string representation of the given path elements.
* For example, if this type is {@link #MODULES}, then the option is {@code "--module-path"}
* followed by the specified path elements.
*
* @param paths the path to format as a tool option
* @return the option associated to this path type followed by the given path elements,
* or an empty string if there is no path element
* @throws IllegalStateException if no option is associated to this path type
*/
@Nonnull
@Override
public String option(Iterable<? extends Path> paths) {
return format(null, paths);
}

/**
* Implementation shared with {@link Modular}.
*/
String format(String moduleName, Iterable<? extends Path> paths) {
if (option == null) {
throw new IllegalStateException("No option is associated to this path type.");
}
String prefix = (moduleName == null) ? (option + ' ') : (option + ' ' + moduleName + '=');
StringJoiner joiner = new StringJoiner(File.pathSeparator, prefix, "");
joiner.setEmptyValue("");
for (Path p : paths) {
joiner.add(p.toString());
}
return joiner.toString();
}

@Override
public String toString() {
return "PathType[" + id() + "]";
}

/**
* Type of path which is applied to only one specific Java module.
* The main case is the Java {@code --patch-module} option.
*
* @see #PATCH_MODULE
* @see #patchModule(String)
*/
public final class Modular implements PathType {
/**
* Name of the module for which a path is specified.
*/
@Nonnull
private final String moduleName;

/**
* Creates a new path type for the specified module.
*
* @param moduleName name of the module for which a path is specified
*/
private Modular(@Nonnull String moduleName) {
this.moduleName = Objects.requireNonNull(moduleName);
}

@Override
public String id() {
return JavaPathType.this.name() + ":" + moduleName;
}

/**
* Returns the type of path without indication about the target module.
* This is usually {@link #PATCH_MODULE}.
*
* @return type of path without indication about the target module
*/
@Nonnull
public JavaPathType rawType() {
return JavaPathType.this;
}

/**
* Returns the name of the tool option for this path, not including the module name.
*
* @return name of the tool option for this path, not including the module name
*/
@Nonnull
public String name() {
return JavaPathType.this.name();
}

/**
* Returns the name of the module for which a path is specified
*
* @return name of the module for which a path is specified
*/
@Nonnull
public String moduleName() {
return moduleName;
}

/**
* Returns the name of the tool option for this path.
* The option does not include the {@linkplain #moduleName() module name} on which it applies.
*
* @return the name of the tool option for this path type
*/
@Nonnull
@Override
public Optional<String> option() {
return JavaPathType.this.option();
}

/**
* Returns the option followed by a string representation of the given path elements.
* The path elements are separated by an option-specific or platform-specific separator.
* If the given {@code paths} argument contains no element, then this method returns an empty string.
*
* @param paths the path to format as a string
* @return the option associated to this path type followed by the given path elements,
* or an empty string if there is no path element.
*/
@Nonnull
@Override
public String option(Iterable<? extends Path> paths) {
return format(moduleName, paths);
}

/**
* Returns the programmatic name of this path type, including the module to patch.
* For example, if this type was created by {@code JavaPathType.patchModule("foo.bar")},
* then this method returns {@code "PathType[PATCH_MODULE:foo.bar]")}.
*
* @return the programmatic name together with the module name on which it applies
*/
@Nonnull
@Override
public String toString() {
return "PathType[" + id() + "]";
}
}
}
Loading

0 comments on commit 0477c52

Please sign in to comment.