diff --git a/api/maven-api-core/src/main/java/org/apache/maven/api/Dependency.java b/api/maven-api-core/src/main/java/org/apache/maven/api/Dependency.java index efad1415adf8..d0906581b62f 100644 --- a/api/maven-api-core/src/main/java/org/apache/maven/api/Dependency.java +++ b/api/maven-api-core/src/main/java/org/apache/maven/api/Dependency.java @@ -30,16 +30,8 @@ public interface Dependency extends Artifact { @Nonnull Type getType(); - /** - * The dependency properties. - * - * @return the dependency properties, never {@code null} - */ - @Nonnull - DependencyProperties getDependencyProperties(); - @Nonnull - Scope getScope(); + DependencyScope getScope(); boolean isOptional(); @@ -49,5 +41,6 @@ public interface Dependency extends Artifact { * @return a {@link DependencyCoordinate} */ @Nonnull + @Override DependencyCoordinate toCoordinate(); } diff --git a/api/maven-api-core/src/main/java/org/apache/maven/api/DependencyCoordinate.java b/api/maven-api-core/src/main/java/org/apache/maven/api/DependencyCoordinate.java index 7ba4bd1ec75e..c0295bfaa0e4 100644 --- a/api/maven-api-core/src/main/java/org/apache/maven/api/DependencyCoordinate.java +++ b/api/maven-api-core/src/main/java/org/apache/maven/api/DependencyCoordinate.java @@ -41,7 +41,7 @@ public interface DependencyCoordinate extends ArtifactCoordinate { Type getType(); @Nonnull - Scope getScope(); + DependencyScope getScope(); @Nullable Boolean getOptional(); diff --git a/api/maven-api-core/src/main/java/org/apache/maven/api/DependencyScope.java b/api/maven-api-core/src/main/java/org/apache/maven/api/DependencyScope.java new file mode 100644 index 000000000000..2951acb1b44c --- /dev/null +++ b/api/maven-api-core/src/main/java/org/apache/maven/api/DependencyScope.java @@ -0,0 +1,125 @@ +/* + * 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.util.Collections; +import java.util.Map; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +import org.apache.maven.api.annotations.Experimental; +import org.apache.maven.api.annotations.Immutable; +import org.apache.maven.api.annotations.Nonnull; + +/** + * Dependency scope. + *

+ * Implementation must have {@code equals()} and {@code hashCode()} implemented, so implementations of this interface + * can be used as keys. + * + * @since 4.0.0 + */ +@Experimental +@Immutable +public enum DependencyScope { + + /** + * None. Allows you to declare dependencies (for example to alter reactor build order) but in reality dependencies + * in this scope are not part of any build path scope. + */ + NONE("none", false), + + /** + * Empty scope. + */ + EMPTY("", false), + + /** + * Compile only. + */ + COMPILE_ONLY("compile-only", false), + + /** + * Compile. + */ + COMPILE("compile", true), + + /** + * Runtime. + */ + RUNTIME("runtime", true), + + /** + * Provided. + */ + PROVIDED("provided", false), + + /** + * Test compile only. + */ + TEST_ONLY("test-only", false), + + /** + * Test. + */ + TEST("test", false), + + /** + * Test runtime. + */ + TEST_RUNTIME("test-runtime", true), + + /** + * System scope. + *

+ * Important: this scope {@code id} MUST BE KEPT in sync with label in + * {@code org.eclipse.aether.util.artifact.Scopes#SYSTEM}. + */ + SYSTEM("system", false); + + private static final Map IDS = Collections.unmodifiableMap( + Stream.of(DependencyScope.values()).collect(Collectors.toMap(s -> s.id, s -> s))); + + public static DependencyScope forId(String id) { + return IDS.get(id); + } + + private final String id; + private final boolean transitive; + + DependencyScope(String id, boolean transitive) { + this.id = id; + this.transitive = transitive; + } + + /** + * The {@code id} uniquely represents a value for this extensible enum. + * This id should be used to compute the equality and hash code for the instance. + * + * @return the id + */ + @Nonnull + public String id() { + return id; + } + + public boolean isTransitive() { + return transitive; + } +} diff --git a/api/maven-api-core/src/main/java/org/apache/maven/api/ExtensibleEnum.java b/api/maven-api-core/src/main/java/org/apache/maven/api/ExtensibleEnum.java new file mode 100644 index 000000000000..5e29a498c847 --- /dev/null +++ b/api/maven-api-core/src/main/java/org/apache/maven/api/ExtensibleEnum.java @@ -0,0 +1,39 @@ +/* + * 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 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. + */ +public interface ExtensibleEnum { + + /** + * The {@code id} uniquely represents a value for this extensible enum. + * This id should be used to compute the equality and hash code for the instance. + * + * @return the id + */ + @Nonnull + String id(); +} diff --git a/api/maven-api-core/src/main/java/org/apache/maven/api/ExtensibleEnums.java b/api/maven-api-core/src/main/java/org/apache/maven/api/ExtensibleEnums.java new file mode 100644 index 000000000000..7843cbef7066 --- /dev/null +++ b/api/maven-api-core/src/main/java/org/apache/maven/api/ExtensibleEnums.java @@ -0,0 +1,100 @@ +/* + * 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.util.*; + +abstract class ExtensibleEnums { + + static Language language(String id) { + return new DefaultLanguage(id); + } + + static PathScope pathScope(String id, ProjectScope projectScope, DependencyScope... dependencyScopes) { + return new DefaultPathScope(id, projectScope, dependencyScopes); + } + + static ProjectScope projectScope(String id) { + return new DefaultProjectScope(id); + } + + private static class DefaultExtensibleEnum implements ExtensibleEnum { + + private final String id; + + DefaultExtensibleEnum(String id) { + this.id = Objects.requireNonNull(id); + } + + public String id() { + return id; + } + + @Override + public int hashCode() { + return id().hashCode(); + } + + @Override + public boolean equals(Object obj) { + return obj != null && getClass() == obj.getClass() && id().equals(((DefaultExtensibleEnum) obj).id()); + } + + @Override + public String toString() { + return getClass().getSimpleName() + "[" + id() + "]"; + } + } + + private static class DefaultPathScope extends DefaultExtensibleEnum implements PathScope { + private final ProjectScope projectScope; + private final Set dependencyScopes; + + DefaultPathScope(String id, ProjectScope projectScope, DependencyScope... dependencyScopes) { + super(id); + this.projectScope = Objects.requireNonNull(projectScope); + this.dependencyScopes = + Collections.unmodifiableSet(new HashSet<>(Arrays.asList(Objects.requireNonNull(dependencyScopes)))); + } + + @Override + public ProjectScope projectScope() { + return projectScope; + } + + @Override + public Set dependencyScopes() { + return dependencyScopes; + } + } + + private static class DefaultProjectScope extends DefaultExtensibleEnum implements ProjectScope { + + DefaultProjectScope(String id) { + super(id); + } + } + + private static class DefaultLanguage extends DefaultExtensibleEnum implements Language { + + DefaultLanguage(String id) { + super(id); + } + } +} diff --git a/api/maven-api-core/src/main/java/org/apache/maven/api/JavaPathType.java b/api/maven-api-core/src/main/java/org/apache/maven/api/JavaPathType.java new file mode 100644 index 000000000000..a34b686c8c2c --- /dev/null +++ b/api/maven-api-core/src/main/java/org/apache/maven/api/JavaPathType.java @@ -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. + * + *

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.

+ * + *

Path types are often exclusive. For example, a dependency should not be both on the Java class-path + * and on the Java module-path.

+ * + * @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. + * + *

Context-sensitive interpretation: + * 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: + *

+ * + */ + CLASSES("--class-path"), + + /** + * The path identified by the Java {@code --module-path} option. + * Used for compilation, execution and Javadoc among others. + * + *

Context-sensitive interpretation: + * 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: + *

+ * + */ + 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. + * + *

Context-sensitive interpretation: + * 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. + *

+ * + * @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 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(final Iterable paths) { + return format(null, paths); + } + + /** + * Implementation shared with {@link Modular}. + */ + final String format(final String moduleName, final Iterable 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 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 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() + "]"; + } + } +} diff --git a/api/maven-api-core/src/main/java/org/apache/maven/api/Language.java b/api/maven-api-core/src/main/java/org/apache/maven/api/Language.java new file mode 100644 index 000000000000..0fc459287b57 --- /dev/null +++ b/api/maven-api-core/src/main/java/org/apache/maven/api/Language.java @@ -0,0 +1,47 @@ +/* + * 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 org.apache.maven.api.annotations.Experimental; +import org.apache.maven.api.annotations.Immutable; + +import static org.apache.maven.api.ExtensibleEnums.language; + +/** + * Language. + *

+ * Implementation must have {@code equals()} and {@code hashCode()} implemented, so implementations of this interface + * can be used as keys. + * + * @since 4.0.0 + */ +@Experimental +@Immutable +@SuppressWarnings("checkstyle:InterfaceIsType") +public interface Language extends ExtensibleEnum { + + /** + * The "none" language. It is not versioned, family is same to itself, and compatible with itself only. + * In turn, every {@link Language} implementation must be compatible with {@code NONE} language. + */ + Language NONE = language("none"); + + // TODO: this should be moved out from here to Java Support (builtin into core) + Language JAVA_FAMILY = language("java"); +} diff --git a/api/maven-api-core/src/main/java/org/apache/maven/api/DependencyProperties.java b/api/maven-api-core/src/main/java/org/apache/maven/api/Packaging.java similarity index 54% rename from api/maven-api-core/src/main/java/org/apache/maven/api/DependencyProperties.java rename to api/maven-api-core/src/main/java/org/apache/maven/api/Packaging.java index 9f634168480b..ede4c46b073f 100644 --- a/api/maven-api-core/src/main/java/org/apache/maven/api/DependencyProperties.java +++ b/api/maven-api-core/src/main/java/org/apache/maven/api/Packaging.java @@ -18,42 +18,35 @@ */ package org.apache.maven.api; -import java.util.Map; - import org.apache.maven.api.annotations.Experimental; import org.apache.maven.api.annotations.Immutable; import org.apache.maven.api.annotations.Nonnull; /** - * Dependency properties supported by Maven Core. + * Interface representing a Maven project packaging. * * @since 4.0.0 */ @Experimental @Immutable -public interface DependencyProperties { - /** - * Boolean flag telling that dependency contains all of its dependencies. Value of this key should be parsed with - * {@link Boolean#parseBoolean(String)} to obtain value. - *

- * Important: this flag must be kept in sync with resolver! (as is used during collection) - */ - String FLAG_INCLUDES_DEPENDENCIES = "includesDependencies"; - +public interface Packaging extends ExtensibleEnum { /** - * Boolean flag telling that dependency is meant to be placed on class path. Value of this key should be parsed with - * {@link Boolean#parseBoolean(String)} to obtain value. + * The packaging id. */ - String FLAG_CLASS_PATH_CONSTITUENT = "classPathConstituent"; + @Nonnull + String id(); /** - * Returns immutable "map view" of all the properties. + * The language of this packaging. */ @Nonnull - Map asMap(); + default Language language() { + return getType().getLanguage(); + } /** - * Returns {@code true} if given flag is {@code true}. + * The type of main artifact produced by this packaging. */ - boolean checkFlag(@Nonnull String flag); + @Nonnull + Type getType(); } diff --git a/api/maven-api-core/src/main/java/org/apache/maven/api/PathScope.java b/api/maven-api-core/src/main/java/org/apache/maven/api/PathScope.java new file mode 100644 index 000000000000..7132dfaa7e54 --- /dev/null +++ b/api/maven-api-core/src/main/java/org/apache/maven/api/PathScope.java @@ -0,0 +1,76 @@ +/* + * 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.util.*; + +import org.apache.maven.api.annotations.Experimental; +import org.apache.maven.api.annotations.Immutable; +import org.apache.maven.api.annotations.Nonnull; + +import static org.apache.maven.api.ExtensibleEnums.pathScope; + +/** + * Build path scope. + *

+ * Implementation must have {@code equals()} and {@code hashCode()} implemented, so implementations of this interface + * can be used as keys. + * + * @since 4.0.0 + */ +@Experimental +@Immutable +public interface PathScope extends ExtensibleEnum { + + @Nonnull + ProjectScope projectScope(); + + @Nonnull + Set dependencyScopes(); + + PathScope MAIN_COMPILE = pathScope( + "main-compile", + ProjectScope.MAIN, + DependencyScope.EMPTY, + DependencyScope.COMPILE_ONLY, + DependencyScope.COMPILE, + DependencyScope.PROVIDED); + + PathScope MAIN_RUNTIME = pathScope( + "main-runtime", ProjectScope.MAIN, DependencyScope.EMPTY, DependencyScope.COMPILE, DependencyScope.RUNTIME); + + PathScope TEST_COMPILE = pathScope( + "test-compile", + ProjectScope.TEST, + DependencyScope.EMPTY, + DependencyScope.COMPILE, + DependencyScope.PROVIDED, + DependencyScope.TEST_ONLY, + DependencyScope.TEST); + + PathScope TEST_RUNTIME = pathScope( + "test-runtime", + ProjectScope.TEST, + DependencyScope.EMPTY, + DependencyScope.COMPILE, + DependencyScope.RUNTIME, + DependencyScope.PROVIDED, + DependencyScope.TEST, + DependencyScope.TEST_RUNTIME); +} diff --git a/api/maven-api-core/src/main/java/org/apache/maven/api/PathType.java b/api/maven-api-core/src/main/java/org/apache/maven/api/PathType.java new file mode 100644 index 000000000000..7a558081af1d --- /dev/null +++ b/api/maven-api-core/src/main/java/org/apache/maven/api/PathType.java @@ -0,0 +1,102 @@ +/* + * 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.nio.file.Path; +import java.util.Optional; + +import org.apache.maven.api.annotations.Experimental; +import org.apache.maven.api.annotations.Nonnull; + +/** + * The option of a command-line tool where to place the paths to some dependencies. + * A {@code PathType} can identify the Java class-path, the Java module-path, + * or another kind of path for another programming language for example. + * Path types are often exclusive. For example, a dependency should not be + * both on the Java class-path and on the Java module-path. + * + * @see org.apache.maven.api.services.DependencyResolverResult#getDispatchedPaths() + * + * @since 4.0.0 + */ +@Experimental +public interface PathType { + /** + * Returns the unique name of this path type, including the module to patch if any. + * For example, if this type is {@link JavaPathType#MODULES}, then this method returns {@code "MODULES"}. + * But if this type was created by {@code JavaPathType.patchModule("foo.bar")}, then this method returns + * {@code "PATCH_MODULE:foo.bar"}. + * + * @return the programmatic name together with the module name on which it applies + * @see #toString() + */ + @Nonnull + String id(); + + /** + * Returns the name of the tool option for this path. For example, if this path type + * is {@link JavaPathType#MODULES}, then this method returns {@code "--module-path"}. + * The option does not include the {@linkplain JavaPathType.Modular#moduleName() module name} + * on which it applies. + * + * @return the name of the tool option for this path type + */ + @Nonnull + Optional 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. + * + *

Examples: + * If {@code paths} is a list containing two elements, {@code path1} and {@code path2}, then: + *

+ * + * + * @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 + String option(Iterable paths); + + /** + * Returns the name of this path type. For example, if this path type + * is {@link JavaPathType#MODULES}, then this method returns {@code "MODULES"}. + * + * @return the programmatic name of this path type + */ + @Nonnull + String name(); + + /** + * Returns a string representation for this extensible enum describing a path type. + * For example {@code "PathType[PATCH_MODULE:foo.bar]"}. + */ + @Nonnull + @Override + String toString(); +} diff --git a/api/maven-api-core/src/main/java/org/apache/maven/api/Project.java b/api/maven-api-core/src/main/java/org/apache/maven/api/Project.java index 1b448f9bc311..948bb2561211 100644 --- a/api/maven-api-core/src/main/java/org/apache/maven/api/Project.java +++ b/api/maven-api-core/src/main/java/org/apache/maven/api/Project.java @@ -76,7 +76,17 @@ public interface Project { * @see #getArtifacts() */ @Nonnull - String getPackaging(); + Packaging getPackaging(); + + /** + * Returns the project language. It is by default determined by {@link #getPackaging()}. + * + * @see #getPackaging() + */ + @Nonnull + default Language getLanguage() { + return getPackaging().language(); + } /** * Returns the project POM artifact, which is the artifact of the POM of this project. Every project have a POM diff --git a/api/maven-api-core/src/main/java/org/apache/maven/api/Scope.java b/api/maven-api-core/src/main/java/org/apache/maven/api/ProjectScope.java similarity index 53% rename from api/maven-api-core/src/main/java/org/apache/maven/api/Scope.java rename to api/maven-api-core/src/main/java/org/apache/maven/api/ProjectScope.java index 624293c1e92d..e663e61463a0 100644 --- a/api/maven-api-core/src/main/java/org/apache/maven/api/Scope.java +++ b/api/maven-api-core/src/main/java/org/apache/maven/api/ProjectScope.java @@ -18,49 +18,31 @@ */ package org.apache.maven.api; -import java.util.HashMap; -import java.util.Map; - import org.apache.maven.api.annotations.Experimental; +import org.apache.maven.api.annotations.Immutable; + +import static org.apache.maven.api.ExtensibleEnums.projectScope; /** - * Scope for a dependency + * Project scope. + *

+ * Implementation must have {@code equals()} and {@code hashCode()} implemented, so implementations of this interface + * can be used as keys. * * @since 4.0.0 */ @Experimental -public enum Scope { - EMPTY(""), - COMPILE_ONLY("compile-only"), - COMPILE("compile"), - RUNTIME("runtime"), - PROVIDED("provided"), - TEST_COMPILE_ONLY("test-compile-only"), - TEST("test"), - TEST_RUNTIME("test-runtime"), - IMPORT("import"); // TODO: v4: remove import scope somehow - - private final String id; - - private static final Map SCOPES; - - static { - Map scopes = new HashMap<>(); - for (Scope s : Scope.values()) { - scopes.put(s.id, s); - } - SCOPES = scopes; - } - - Scope(String id) { - this.id = id; - } - - public String id() { - return this.id; - } - - public static Scope get(String scope) { - return SCOPES.get(scope); - } +@Immutable +@SuppressWarnings("checkstyle:InterfaceIsType") +public interface ProjectScope extends ExtensibleEnum { + + /** + * Main scope. + */ + ProjectScope MAIN = projectScope("main"); + + /** + * Test scope. + */ + ProjectScope TEST = projectScope("test"); } diff --git a/api/maven-api-core/src/main/java/org/apache/maven/api/ResolutionScope.java b/api/maven-api-core/src/main/java/org/apache/maven/api/ResolutionScope.java deleted file mode 100644 index 7d003694d633..000000000000 --- a/api/maven-api-core/src/main/java/org/apache/maven/api/ResolutionScope.java +++ /dev/null @@ -1,97 +0,0 @@ -/* - * 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.util.Arrays; -import java.util.Collections; -import java.util.HashSet; -import java.util.Map; -import java.util.Optional; -import java.util.Set; -import java.util.stream.Collectors; -import java.util.stream.Stream; - -import org.apache.maven.api.annotations.Experimental; - -/** - * Dependencies resolution scopes available before - * mojo execution. - * - * Important note: The {@code id} values of this enum correspond to constants of - * {@code org.apache.maven.artifact.Artifact} class and MUST BE KEPT IN SYNC. - * - * @since 4.0.0 - */ -@Experimental -public enum ResolutionScope { - /** - * compile resolution scope - * = compile-only + compile + provided dependencies - */ - PROJECT_COMPILE("project-compile", Scope.EMPTY, Scope.COMPILE_ONLY, Scope.COMPILE, Scope.PROVIDED), - /** - * runtime resolution scope - * = compile + runtime dependencies - */ - PROJECT_RUNTIME("project-runtime", Scope.EMPTY, Scope.COMPILE, Scope.RUNTIME), - /** - * test-compile resolution scope - * = compile-only + compile + provided + test-compile-only + test - * dependencies - */ - TEST_COMPILE( - "test-compile", - Scope.EMPTY, - Scope.COMPILE_ONLY, - Scope.COMPILE, - Scope.PROVIDED, - Scope.TEST_COMPILE_ONLY, - Scope.TEST), - /** - * test resolution scope - * = compile + runtime + provided + test + test-runtime - * dependencies - */ - TEST_RUNTIME( - "test-runtime", Scope.EMPTY, Scope.COMPILE, Scope.RUNTIME, Scope.PROVIDED, Scope.TEST, Scope.TEST_RUNTIME); - - private static final Map VALUES = - Stream.of(ResolutionScope.values()).collect(Collectors.toMap(ResolutionScope::id, s -> s)); - - public static ResolutionScope fromString(String id) { - return Optional.ofNullable(VALUES.get(id)) - .orElseThrow(() -> new IllegalArgumentException("Unknown resolution scope " + id)); - } - - private final String id; - private final Set scopes; - - ResolutionScope(String id, Scope... scopes) { - this.id = id; - this.scopes = Collections.unmodifiableSet(new HashSet<>(Arrays.asList(scopes))); - } - - public String id() { - return this.id; - } - - public Set scopes() { - return scopes; - } -} diff --git a/api/maven-api-core/src/main/java/org/apache/maven/api/Session.java b/api/maven-api-core/src/main/java/org/apache/maven/api/Session.java index 65d4798c7c6d..56731f9f3a99 100644 --- a/api/maven-api-core/src/main/java/org/apache/maven/api/Session.java +++ b/api/maven-api-core/src/main/java/org/apache/maven/api/Session.java @@ -195,13 +195,21 @@ public interface Session { /** * Shortcut for {@code getService(RepositoryFactory.class).createLocal(...)}. * + * @param path location of the local repository to create + * @return cache of artifacts downloaded from a remote repository or built locally + * * @see org.apache.maven.api.services.RepositoryFactory#createLocal(Path) */ - LocalRepository createLocalRepository(Path path); + @Nonnull + LocalRepository createLocalRepository(@Nonnull Path path); /** * Shortcut for {@code getService(RepositoryFactory.class).createRemote(...)}. * + * @param id identifier of the remote repository to create + * @param url location of the remote repository + * @return remote repository that can be used to download or upload artifacts + * * @see org.apache.maven.api.services.RepositoryFactory#createRemote(String, String) */ @Nonnull @@ -210,48 +218,76 @@ public interface Session { /** * Shortcut for {@code getService(RepositoryFactory.class).createRemote(...)}. * + * @param repository information needed for establishing connections with remote repository + * @return remote repository that can be used to download or upload artifacts + * * @see org.apache.maven.api.services.RepositoryFactory#createRemote(Repository) */ @Nonnull RemoteRepository createRemoteRepository(@Nonnull Repository repository); /** + * Creates a coordinate out of string that is formatted like: + * {@code :[:[:]]:}. + *

* Shortcut for {@code getService(ArtifactFactory.class).create(...)}. * - * @see org.apache.maven.api.services.ArtifactCoordinateFactory#create(Session, String, String, String, String) + * @param coordString the string having "standard" coordinate. + * @return coordinate used to point to the artifact + * + * @see org.apache.maven.api.services.ArtifactCoordinateFactory#create(Session, String) */ - ArtifactCoordinate createArtifactCoordinate(String groupId, String artifactId, String version, String extension); + @Nonnull + ArtifactCoordinate createArtifactCoordinate(@Nonnull String coordString); /** - * Creates a coordinate out of string that is formatted like: - * {@code :[:[:]]:} - *

* Shortcut for {@code getService(ArtifactFactory.class).create(...)}. * - * @param coordString the string having "standard" coordinate. - * @return an {@code ArtifactCoordinate}, never {@code null} - * @see org.apache.maven.api.services.ArtifactCoordinateFactory#create(Session, String) + * @param groupId the group identifier, or {@code null} is unspecified + * @param artifactId the artifact identifier, or {@code null} is unspecified + * @param version the artifact version, or {@code null} is unspecified + * @param extension the artifact extension, or {@code null} is unspecified + * @return coordinate used to point to the artifact + * + * @see org.apache.maven.api.services.ArtifactCoordinateFactory#create(Session, String, String, String, String) */ - ArtifactCoordinate createArtifactCoordinate(String coordString); + @Nonnull + ArtifactCoordinate createArtifactCoordinate(String groupId, String artifactId, String version, String extension); /** * Shortcut for {@code getService(ArtifactFactory.class).create(...)}. * + * @param groupId the group identifier, or {@code null} is unspecified + * @param artifactId the artifact identifier, or {@code null} is unspecified + * @param version the artifact version, or {@code null} is unspecified + * @param classifier the artifact classifier, or {@code null} is unspecified + * @param extension the artifact extension, or {@code null} is unspecified + * @param type the artifact type, or {@code null} is unspecified + * @return coordinate used to point to the artifact + * * @see org.apache.maven.api.services.ArtifactCoordinateFactory#create(Session, String, String, String, String, String, String) */ + @Nonnull ArtifactCoordinate createArtifactCoordinate( String groupId, String artifactId, String version, String classifier, String extension, String type); /** * Shortcut for {@code getService(ArtifactFactory.class).create(...)}. * + * @param artifact artifact from which to get coordinates + * @return coordinate used to point to the artifact + * * @see org.apache.maven.api.services.ArtifactCoordinateFactory#create(Session, String, String, String, String, String, String) */ - ArtifactCoordinate createArtifactCoordinate(Artifact artifact); + @Nonnull + ArtifactCoordinate createArtifactCoordinate(@Nonnull Artifact artifact); /** * Shortcut for {@code getService(DependencyFactory.class).create(...)}. * + * @param coordinate artifact coordinate to get as a dependency coordinate + * @return dependency coordinate for the given artifact + * * @see DependencyCoordinateFactory#create(Session, ArtifactCoordinate) */ @Nonnull @@ -260,6 +296,9 @@ ArtifactCoordinate createArtifactCoordinate( /** * Shortcut for {@code getService(DependencyFactory.class).create(...)}. * + * @param dependency dependency for which to get the coordinate + * @return coordinate for the given dependency + * * @see DependencyCoordinateFactory#create(Session, Dependency) */ @Nonnull @@ -268,85 +307,131 @@ ArtifactCoordinate createArtifactCoordinate( /** * Shortcut for {@code getService(ArtifactFactory.class).create(...)}. * + * @param groupId the group identifier, or {@code null} is unspecified + * @param artifactId the artifact identifier, or {@code null} is unspecified + * @param version the artifact version, or {@code null} is unspecified + * @param extension the artifact extension, or {@code null} is unspecified + * @return artifact with the given coordinates + * * @see org.apache.maven.api.services.ArtifactFactory#create(Session, String, String, String, String) */ + @Nonnull Artifact createArtifact(String groupId, String artifactId, String version, String extension); /** * Shortcut for {@code getService(ArtifactFactory.class).create(...)}. * + * @param groupId the group identifier, or {@code null} is unspecified + * @param artifactId the artifact identifier, or {@code null} is unspecified + * @param version the artifact version, or {@code null} is unspecified + * @param classifier the artifact classifier, or {@code null} is unspecified + * @param extension the artifact extension, or {@code null} is unspecified + * @param type the artifact type, or {@code null} is unspecified + * @return artifact with the given coordinates + * * @see org.apache.maven.api.services.ArtifactFactory#create(Session, String, String, String, String, String, String) */ + @Nonnull Artifact createArtifact( String groupId, String artifactId, String version, String classifier, String extension, String type); /** * Shortcut for {@code getService(ArtifactResolver.class).resolve(...)}. * - * @see org.apache.maven.api.services.ArtifactResolver#resolve(Session, Collection) + * @param coordinate coordinates of the artifact to resolve + * @return requested artifact together with the path to its file * @throws org.apache.maven.api.services.ArtifactResolverException if the artifact resolution failed + * + * @see org.apache.maven.api.services.ArtifactResolver#resolve(Session, Collection) */ - Map.Entry resolveArtifact(ArtifactCoordinate coordinate); + @Nonnull + Map.Entry resolveArtifact(@Nonnull ArtifactCoordinate coordinate); /** * Shortcut for {@code getService(ArtifactResolver.class).resolve(...)}. * - * @see org.apache.maven.api.services.ArtifactResolver#resolve(Session, Collection) + * @param coordinates coordinates of all artifacts to resolve + * @return requested artifacts together with the paths to their files * @throws org.apache.maven.api.services.ArtifactResolverException if the artifact resolution failed + * + * @see org.apache.maven.api.services.ArtifactResolver#resolve(Session, Collection) */ - Map resolveArtifacts(ArtifactCoordinate... coordinates); + @Nonnull + Map resolveArtifacts(@Nonnull ArtifactCoordinate... coordinates); /** * Shortcut for {@code getService(ArtifactResolver.class).resolve(...)}. * - * @see org.apache.maven.api.services.ArtifactResolver#resolve(Session, Collection) + * @param coordinates coordinates of all artifacts to resolve + * @return requested artifacts together with the paths to their files * @throws org.apache.maven.api.services.ArtifactResolverException if the artifact resolution failed + * + * @see org.apache.maven.api.services.ArtifactResolver#resolve(Session, Collection) */ - Map resolveArtifacts(Collection coordinates); + @Nonnull + Map resolveArtifacts(@Nonnull Collection coordinates); /** * Shortcut for {@code getService(ArtifactResolver.class).resolve(...)}. * - * @see org.apache.maven.api.services.ArtifactResolver#resolve(Session, Collection) + * @param artifact the artifact to resolve + * @return requested artifact together with the path to its file * @throws org.apache.maven.api.services.ArtifactResolverException if the artifact resolution failed + * + * @see org.apache.maven.api.services.ArtifactResolver#resolve(Session, Collection) */ - Map.Entry resolveArtifact(Artifact artifact); + @Nonnull + Map.Entry resolveArtifact(@Nonnull Artifact artifact); /** * Shortcut for {@code getService(ArtifactResolver.class).resolve(...)}. * - * @see org.apache.maven.api.services.ArtifactResolver#resolve(Session, Collection) + * @param artifacts all artifacts to resolve + * @return requested artifacts together with the paths to their files * @throws org.apache.maven.api.services.ArtifactResolverException if the artifact resolution failed + * + * @see org.apache.maven.api.services.ArtifactResolver#resolve(Session, Collection) */ - Map resolveArtifacts(Artifact... artifacts); + @Nonnull + Map resolveArtifacts(@Nonnull Artifact... artifacts); /** * Shortcut for {@code getService(ArtifactInstaller.class).install(...)}. * - * @see org.apache.maven.api.services.ArtifactInstaller#install(Session, Collection) + * @param artifacts the artifacts to install * @throws org.apache.maven.api.services.ArtifactInstallerException if the artifacts installation failed + * + * @see org.apache.maven.api.services.ArtifactInstaller#install(Session, Collection) */ - void installArtifacts(Artifact... artifacts); + void installArtifacts(@Nonnull Artifact... artifacts); /** * Shortcut for {@code getService(ArtifactInstaller.class).install(...)}. * - * @see org.apache.maven.api.services.ArtifactInstaller#install(Session, Collection) + * @param artifacts the artifacts to install * @throws org.apache.maven.api.services.ArtifactInstallerException if the artifacts installation failed + * + * @see org.apache.maven.api.services.ArtifactInstaller#install(Session, Collection) */ - void installArtifacts(Collection artifacts); + void installArtifacts(@Nonnull Collection artifacts); /** * Shortcut for {@code getService(ArtifactDeployer.class).deploy(...)}. * - * @see org.apache.maven.api.services.ArtifactDeployer#deploy(Session, RemoteRepository, Collection) + * @param repository the repository where to deploy artifacts + * @param artifacts the artifacts to deploy * @throws org.apache.maven.api.services.ArtifactDeployerException if the artifacts deployment failed + * + * @see org.apache.maven.api.services.ArtifactDeployer#deploy(Session, RemoteRepository, Collection) */ - void deployArtifact(RemoteRepository repository, Artifact... artifacts); + void deployArtifact(@Nonnull RemoteRepository repository, @Nonnull Artifact... artifacts); /** * Shortcut for {@code getService(ArtifactManager.class).setPath(...)}. * + * @param artifact the artifact for which to associate a path + * @param path path to associate to the given artifact + * * @see org.apache.maven.api.services.ArtifactManager#setPath(Artifact, Path) */ void setArtifactPath(@Nonnull Artifact artifact, @Nonnull Path path); @@ -354,6 +439,9 @@ Artifact createArtifact( /** * Shortcut for {@code getService(ArtifactManager.class).getPath(...)}. * + * @param artifact the artifact for which to get a path + * @return path associated to the given artifact + * * @see org.apache.maven.api.services.ArtifactManager#getPath(Artifact) */ @Nonnull @@ -365,6 +453,9 @@ Artifact createArtifact( *

* Shortcut for {@code getService(LocalArtifactManager.class).getPathForLocalArtitact(...)}. * + * @param artifact the artifact for which to get a local path + * @return local path associated to the given artifact, or {@code null} if none + * * @see org.apache.maven.api.services.LocalRepositoryManager#getPathForLocalArtifact(Session, LocalRepository, Artifact) */ Path getPathForLocalArtifact(@Nonnull Artifact artifact); @@ -376,6 +467,10 @@ Artifact createArtifact( *

* Shortcut for {@code getService(LocalArtifactManager.class).getPathForRemoteArtifact(...)}. * + * @param remote the repository from where artifacts are downloaded + * @param artifact the artifact for which to get a path + * @return path associated to the given artifact + * * @see org.apache.maven.api.services.LocalRepositoryManager#getPathForRemoteArtifact(Session, LocalRepository, RemoteRepository, Artifact) */ @Nonnull @@ -385,6 +480,12 @@ Artifact createArtifact( * Checks whether a given artifact version is considered a {@code SNAPSHOT} or not. *

* Shortcut for {@code getService(ArtifactManager.class).isSnapshot(...)}. + *

+ * In case there is {@link Artifact} in scope, the recommended way to perform this check is + * use of {@link Artifact#isSnapshot()} instead. + * + * @param version artifact version + * @return whether the given version is a snapshot * * @see org.apache.maven.api.services.VersionParser#isSnapshot(String) */ @@ -393,6 +494,9 @@ Artifact createArtifact( /** * Shortcut for {@code getService(DependencyCollector.class).collect(...)} * + * @param artifact artifact for which to get the dependencies, including transitive ones + * @return root node of the dependency graph for the given artifact + * * @see org.apache.maven.api.services.DependencyCollector#collect(Session, Artifact) * @throws org.apache.maven.api.services.DependencyCollectorException if the dependency collection failed */ @@ -402,6 +506,9 @@ Artifact createArtifact( /** * Shortcut for {@code getService(DependencyCollector.class).collect(...)} * + * @param project project for which to get the dependencies, including transitive ones + * @return root node of the dependency graph for the given project + * * @see org.apache.maven.api.services.DependencyCollector#collect(Session, Project) * @throws org.apache.maven.api.services.DependencyCollectorException if the dependency collection failed */ @@ -415,6 +522,9 @@ Artifact createArtifact( *

* Shortcut for {@code getService(DependencyCollector.class).resolve(...)} * + * @param dependency dependency for which to get transitive dependencies + * @return root node of the dependency graph for the given artifact + * * @see org.apache.maven.api.services.DependencyCollector#collect(Session, DependencyCoordinate) * @throws org.apache.maven.api.services.DependencyCollectorException if the dependency collection failed */ @@ -424,20 +534,79 @@ Artifact createArtifact( /** * Shortcut for {@code getService(DependencyResolver.class).flatten(...)}. * - * @see org.apache.maven.api.services.DependencyResolver#flatten(Session, Node, ResolutionScope) + * @param node node for which to get a flattened list + * @param scope build path scope (main compile, test compile, etc.) of desired nodes + * @return flattened list of node with the given build path scope * @throws org.apache.maven.api.services.DependencyResolverException if the dependency flattening failed + * + * @see org.apache.maven.api.services.DependencyResolver#flatten(Session, Node, PathScope) */ @Nonnull - List flattenDependencies(@Nonnull Node node, @Nonnull ResolutionScope scope); + List flattenDependencies(@Nonnull Node node, @Nonnull PathScope scope); + /** + * Shortcut for {@code getService(DependencyResolver.class).resolve(...).getPaths()}. + * + * @param dependencyCoordinate coordinate of the dependency for which to get the paths + * @return paths to the transitive dependencies of the given dependency + * + * @see org.apache.maven.api.services.DependencyResolver#resolve(Session, DependencyCoordinate) + */ @Nonnull List resolveDependencies(@Nonnull DependencyCoordinate dependencyCoordinate); + /** + * Shortcut for {@code getService(DependencyResolver.class).resolve(...).getPaths()}. + * + * @param dependencyCoordinates coordinates of all dependency for which to get the paths + * @return paths to the transitive dependencies of the given dependencies + * + * @see org.apache.maven.api.services.DependencyResolver#resolve(Session, List) + */ @Nonnull List resolveDependencies(@Nonnull List dependencyCoordinates); + /** + * Shortcut for {@code getService(DependencyResolver.class).resolve(...).getPaths()}. + * + * @param project the project for which to get dependencies + * @param scope build path scope (main compile, test compile, etc.) of desired paths + * @return paths to the transitive dependencies of the given project + * + * @see org.apache.maven.api.services.DependencyResolver#resolve(Session, Project, PathScope) + */ @Nonnull - List resolveDependencies(@Nonnull Project project, @Nonnull ResolutionScope scope); + List resolveDependencies(@Nonnull Project project, @Nonnull PathScope scope); + + /** + * Shortcut for {@code getService(DependencyResolver.class).resolve(...).getDispatchedPaths()}. + * + * @param dependencyCoordinate coordinate of the dependency for which to get the paths + * @param scope build path scope (main compile, test compile, etc.) of desired paths + * @param desiredTypes the type of paths to include in the result + * @return paths to the transitive dependencies of the given project + * + * @see org.apache.maven.api.services.DependencyResolver#resolve(Session, Project, PathScope) + */ + @Nonnull + Map> resolveDependencies( + @Nonnull DependencyCoordinate dependencyCoordinate, + @Nonnull PathScope scope, + @Nonnull Collection desiredTypes); + + /** + * Shortcut for {@code getService(DependencyResolver.class).resolve(...).getDispatchedPaths()}. + * + * @param project the project for which to get dependencies + * @param scope build path scope (main compile, test compile, etc.) of desired paths + * @param desiredTypes the type of paths to include in the result + * @return paths to the transitive dependencies of the given project + * + * @see org.apache.maven.api.services.DependencyResolver#resolve(Session, Project, PathScope) + */ + @Nonnull + Map> resolveDependencies( + @Nonnull Project project, @Nonnull PathScope scope, @Nonnull Collection desiredTypes); /** * Resolves an artifact's meta version (if any) to a concrete version. For example, resolves "1.0-SNAPSHOT" @@ -445,8 +614,11 @@ Artifact createArtifact( *

* Shortcut for {@code getService(VersionResolver.class).resolve(...)} * - * @see org.apache.maven.api.services.VersionResolver#resolve(Session, ArtifactCoordinate) (String) + * @param artifact the artifact for which to resolve the version + * @return resolved version of the given artifact * @throws org.apache.maven.api.services.VersionResolverException if the resolution failed + * + * @see org.apache.maven.api.services.VersionResolver#resolve(Session, ArtifactCoordinate) (String) */ @Nonnull Version resolveVersion(@Nonnull ArtifactCoordinate artifact); @@ -459,9 +631,10 @@ Artifact createArtifact( * In this case though, the result contains simply the (parsed) input version, regardless of the * repositories and their contents. * + * @param artifact the artifact for which to resolve the versions * @return a list of resolved {@code Version}s. - * @see org.apache.maven.api.services.VersionRangeResolver#resolve(Session, ArtifactCoordinate) (String) * @throws org.apache.maven.api.services.VersionRangeResolverException if the resolution failed + * @see org.apache.maven.api.services.VersionRangeResolver#resolve(Session, ArtifactCoordinate) (String) */ @Nonnull List resolveVersionRange(@Nonnull ArtifactCoordinate artifact); @@ -471,8 +644,10 @@ Artifact createArtifact( *

* Shortcut for {@code getService(VersionParser.class).parseVersion(...)}. * - * @see org.apache.maven.api.services.VersionParser#parseVersion(String) + * @param version the version string to parse + * @return the version parsed from the given string * @throws org.apache.maven.api.services.VersionParserException if the parsing failed + * @see org.apache.maven.api.services.VersionParser#parseVersion(String) */ @Nonnull Version parseVersion(@Nonnull String version); @@ -482,8 +657,10 @@ Artifact createArtifact( *

* Shortcut for {@code getService(VersionParser.class).parseVersionRange(...)}. * - * @see org.apache.maven.api.services.VersionParser#parseVersionRange(String) + * @param versionRange the version string to parse + * @return the version range parsed from the given string * @throws org.apache.maven.api.services.VersionParserException if the parsing failed + * @see org.apache.maven.api.services.VersionParser#parseVersionRange(String) */ @Nonnull VersionRange parseVersionRange(@Nonnull String versionRange); @@ -493,9 +670,23 @@ Artifact createArtifact( *

* Shortcut for {@code getService(VersionParser.class).parseVersionConstraint(...)}. * - * @see org.apache.maven.api.services.VersionParser#parseVersionConstraint(String) + * @param versionConstraint the version string to parse + * @return the version constraint parsed from the given string * @throws org.apache.maven.api.services.VersionParserException if the parsing failed + * @see org.apache.maven.api.services.VersionParser#parseVersionConstraint(String) */ @Nonnull VersionConstraint parseVersionConstraint(@Nonnull String versionConstraint); + + Type requireType(String id); + + Language requireLanguage(String id); + + Packaging requirePackaging(String id); + + ProjectScope requireProjectScope(String id); + + DependencyScope requireDependencyScope(String id); + + PathScope requirePathScope(String id); } diff --git a/api/maven-api-core/src/main/java/org/apache/maven/api/Type.java b/api/maven-api-core/src/main/java/org/apache/maven/api/Type.java index 15ffc0b59a69..4842ae5d46a8 100644 --- a/api/maven-api-core/src/main/java/org/apache/maven/api/Type.java +++ b/api/maven-api-core/src/main/java/org/apache/maven/api/Type.java @@ -18,6 +18,8 @@ */ package org.apache.maven.api; +import java.util.Set; + import org.apache.maven.api.annotations.Experimental; import org.apache.maven.api.annotations.Immutable; import org.apache.maven.api.annotations.Nonnull; @@ -30,20 +32,65 @@ *

* It provides information about the file type (or extension) of the associated artifact, * its default classifier, and how the artifact will be used in the build when creating - * classpaths. + * class-paths or module-paths. *

* For example, the type {@code java-source} has a {@code jar} extension and a * {@code sources} classifier. The artifact and its dependencies should be added - * to the classpath. + * to the build path. * * @since 4.0.0 */ @Experimental @Immutable -public interface Type { +public interface Type extends ExtensibleEnum { + /** + * Artifact type name for a POM file. + */ + String POM = "pom"; + + /** + * Artifact type name for a JAR file that can be placed either on the class-path or on the module-path. + * The path (classes or modules) is chosen by the plugin, possibly using heuristic rules. + * This is the behavior of Maven 3. + */ + String JAR = "jar"; + + /** + * Artifact type name for a JAR file to unconditionally place on the class-path. + * If the JAR is modular, its module information are ignored. + * This type is new in Maven 4. + */ + String CLASSPATH_JAR = "classpath-jar"; + + /** + * Artifact type name for a JAR file to unconditionally place on the module-path. + * If the JAR is not modular, then it is loaded by Java as an unnamed module. + * This type is new in Maven 4. + */ + String MODULAR_JAR = "modular-jar"; + + /** + * Artifact type name for source code packaged in a JAR file. + */ + String JAVA_SOURCE = "java-source"; + + /** + * Artifact type name for javadoc packaged in a JAR file. + */ + String JAVADOC = "javadoc"; + + /** + * Artifact type name for a Maven plugin. + */ + String MAVEN_PLUGIN = "maven-plugin"; - String LANGUAGE_NONE = "none"; - String LANGUAGE_JAVA = "java"; + /** + * Artifact type name for a JAR file containing test classes. If the main artifact is placed on the class-path + * ({@value #JAR} or {@value #CLASSPATH_JAR} types), then the test artifact will also be placed on the class-path. + * Otherwise, if the main artifact is placed on the module-path ({@value #JAR} or {@value #MODULAR_JAR} types), + * then the test artifact will be added using {@code --patch-module} option. + */ + String TEST_JAR = "test-jar"; /** * Returns the dependency type id. @@ -52,14 +99,15 @@ public interface Type { * @return the id of this type, never {@code null}. */ @Nonnull - String getId(); + String id(); /** * Returns the dependency type language. * * @return the language of this type, never {@code null}. */ - String getLanguage(); + @Nonnull + Language getLanguage(); /** * Get the file extension of artifacts of this type. @@ -79,16 +127,6 @@ public interface Type { @Nullable String getClassifier(); - /** - * Specifies if the artifact contains java classes and should be - * added to the classpath. - * - * @return if the artifact should be added to the class path - */ - default boolean isAddedToClassPath() { - return getDependencyProperties().checkFlag(DependencyProperties.FLAG_CLASS_PATH_CONSTITUENT); - } - /** * Specifies if the artifact already embeds its own dependencies. * This is the case for JEE packages or similar artifacts such as @@ -96,15 +134,20 @@ default boolean isAddedToClassPath() { * * @return if the artifact's dependencies are included in the artifact */ - default boolean isIncludesDependencies() { - return getDependencyProperties().checkFlag(DependencyProperties.FLAG_INCLUDES_DEPENDENCIES); - } + boolean isIncludesDependencies(); /** - * Gets the default properties associated with this dependency type. + * Types of path (class-path, module-path, …) where the dependency can be placed. + * For most deterministic builds, the array length should be 1. In such case, + * the dependency will be unconditionally placed on the specified type of path + * and no heuristic rule will be involved. * - * @return the default properties, never {@code null}. + *

It is nevertheless common to specify two or more types of path. For example, + * a Java library may be compatible with either the class-path or the module-path, + * and the user may have provided no instruction about which type to use. In such + * case, the plugin may apply rules for choosing a path. See for example + * {@link JavaPathType#CLASSES} and {@link JavaPathType#MODULES}.

*/ @Nonnull - DependencyProperties getDependencyProperties(); + Set getPathTypes(); } diff --git a/api/maven-api-core/src/main/java/org/apache/maven/api/services/DependencyCoordinateFactoryRequest.java b/api/maven-api-core/src/main/java/org/apache/maven/api/services/DependencyCoordinateFactoryRequest.java index 6db92dbe652a..34483be0de67 100644 --- a/api/maven-api-core/src/main/java/org/apache/maven/api/services/DependencyCoordinateFactoryRequest.java +++ b/api/maven-api-core/src/main/java/org/apache/maven/api/services/DependencyCoordinateFactoryRequest.java @@ -89,7 +89,7 @@ static DependencyCoordinateFactoryRequest build(@Nonnull Session session, @Nonnu .version(dependency.getVersion().asString()) .classifier(dependency.getClassifier()) .extension(dependency.getExtension()) - .type(dependency.getType().getId()) + .type(dependency.getType().id()) .scope(dependency.getScope().id()) .optional(dependency.isOptional()) .build(); diff --git a/api/maven-api-core/src/main/java/org/apache/maven/api/services/DependencyResolver.java b/api/maven-api-core/src/main/java/org/apache/maven/api/services/DependencyResolver.java index 3da46b1f5bde..9fefbc4cda7c 100644 --- a/api/maven-api-core/src/main/java/org/apache/maven/api/services/DependencyResolver.java +++ b/api/maven-api-core/src/main/java/org/apache/maven/api/services/DependencyResolver.java @@ -22,8 +22,8 @@ import org.apache.maven.api.DependencyCoordinate; import org.apache.maven.api.Node; +import org.apache.maven.api.PathScope; import org.apache.maven.api.Project; -import org.apache.maven.api.ResolutionScope; import org.apache.maven.api.Service; import org.apache.maven.api.Session; import org.apache.maven.api.annotations.Experimental; @@ -35,7 +35,7 @@ @Experimental public interface DependencyResolver extends Service { - List flatten(Session session, Node node, ResolutionScope scope) throws DependencyResolverException; + List flatten(Session session, Node node, PathScope scope) throws DependencyResolverException; /** * This method collects, flattens and resolves the dependencies. @@ -47,7 +47,7 @@ public interface DependencyResolver extends Service { * @throws ArtifactResolverException * * @see DependencyCollector#collect(DependencyCollectorRequest) - * @see #flatten(Session, Node, ResolutionScope) + * @see #flatten(Session, Node, PathScope) * @see ArtifactResolver#resolve(ArtifactResolverRequest) */ DependencyResolverResult resolve(DependencyResolverRequest request) @@ -60,7 +60,7 @@ default DependencyResolverResult resolve(@Nonnull Session session, @Nonnull Proj @Nonnull default DependencyResolverResult resolve( - @Nonnull Session session, @Nonnull Project project, @Nonnull ResolutionScope scope) { + @Nonnull Session session, @Nonnull Project project, @Nonnull PathScope scope) { return resolve(DependencyResolverRequest.build(session, project, scope)); } @@ -71,7 +71,7 @@ default DependencyResolverResult resolve(@Nonnull Session session, @Nonnull Depe @Nonnull default DependencyResolverResult resolve( - @Nonnull Session session, @Nonnull DependencyCoordinate dependency, @Nonnull ResolutionScope scope) { + @Nonnull Session session, @Nonnull DependencyCoordinate dependency, @Nonnull PathScope scope) { return resolve(DependencyResolverRequest.build(session, dependency, scope)); } @@ -83,9 +83,7 @@ default DependencyResolverResult resolve( @Nonnull default DependencyResolverResult resolve( - @Nonnull Session session, - @Nonnull List dependencies, - @Nonnull ResolutionScope scope) { + @Nonnull Session session, @Nonnull List dependencies, @Nonnull PathScope scope) { return resolve(DependencyResolverRequest.build(session, dependencies, scope)); } } diff --git a/api/maven-api-core/src/main/java/org/apache/maven/api/services/DependencyResolverRequest.java b/api/maven-api-core/src/main/java/org/apache/maven/api/services/DependencyResolverRequest.java index 0519ececf1d7..2f410a8a303f 100644 --- a/api/maven-api-core/src/main/java/org/apache/maven/api/services/DependencyResolverRequest.java +++ b/api/maven-api-core/src/main/java/org/apache/maven/api/services/DependencyResolverRequest.java @@ -20,11 +20,14 @@ import java.util.Collection; import java.util.List; +import java.util.function.Predicate; import org.apache.maven.api.Artifact; import org.apache.maven.api.DependencyCoordinate; +import org.apache.maven.api.JavaPathType; +import org.apache.maven.api.PathScope; +import org.apache.maven.api.PathType; import org.apache.maven.api.Project; -import org.apache.maven.api.ResolutionScope; import org.apache.maven.api.Session; import org.apache.maven.api.annotations.Experimental; import org.apache.maven.api.annotations.Nonnull; @@ -35,7 +38,17 @@ public interface DependencyResolverRequest extends DependencyCollectorRequest { @Nonnull - ResolutionScope getResolutionScope(); + PathScope getPathScope(); + + /** + * Returns a filter for the types of path (class-path, module-path, …) accepted by the tool. + * For example, if a Java tools accepts only class-path elements, then the filter should return + * {@code true} for {@link JavaPathType#CLASSES} and {@code false} for {@link JavaPathType#MODULES}. + * If no filter is explicitly set, then the default is a filter accepting everything. + * + * @return a filter for the types of path (class-path, module-path, …) accepted by the tool + */ + Predicate getPathTypeFilter(); @Nonnull static DependencyResolverRequestBuilder builder() { @@ -44,50 +57,51 @@ static DependencyResolverRequestBuilder builder() { @Nonnull static DependencyResolverRequest build(Session session, Project project) { - return build(session, project, ResolutionScope.PROJECT_RUNTIME); + return build(session, project, PathScope.MAIN_RUNTIME); } @Nonnull - static DependencyResolverRequest build(Session session, Project project, ResolutionScope scope) { + static DependencyResolverRequest build(Session session, Project project, PathScope scope) { return new DependencyResolverRequestBuilder() .session(session) .project(project) - .resolutionScope(scope) + .pathScope(scope) .build(); } @Nonnull static DependencyResolverRequest build(Session session, DependencyCoordinate dependency) { - return build(session, dependency, ResolutionScope.PROJECT_RUNTIME); + return build(session, dependency, PathScope.MAIN_RUNTIME); } @Nonnull - static DependencyResolverRequest build(Session session, DependencyCoordinate dependency, ResolutionScope scope) { + static DependencyResolverRequest build(Session session, DependencyCoordinate dependency, PathScope scope) { return new DependencyResolverRequestBuilder() .session(session) .dependency(dependency) - .resolutionScope(scope) + .pathScope(scope) .build(); } @Nonnull static DependencyResolverRequest build(Session session, List dependencies) { - return build(session, dependencies, ResolutionScope.PROJECT_RUNTIME); + return build(session, dependencies, PathScope.MAIN_RUNTIME); } @Nonnull - static DependencyResolverRequest build( - Session session, List dependencies, ResolutionScope scope) { + static DependencyResolverRequest build(Session session, List dependencies, PathScope scope) { return new DependencyResolverRequestBuilder() .session(session) .dependencies(dependencies) - .resolutionScope(scope) + .pathScope(scope) .build(); } @NotThreadSafe class DependencyResolverRequestBuilder extends DependencyCollectorRequestBuilder { - ResolutionScope resolutionScope; + PathScope pathScope; + + Predicate pathTypeFilter; @Nonnull @Override @@ -154,20 +168,57 @@ public DependencyResolverRequestBuilder verbose(boolean verbose) { } @Nonnull - public DependencyResolverRequestBuilder resolutionScope(@Nonnull ResolutionScope resolutionScope) { - this.resolutionScope = resolutionScope; + public DependencyResolverRequestBuilder pathScope(@Nonnull PathScope pathScope) { + this.pathScope = pathScope; + return this; + } + + /** + * Filters the types of paths to include in the result. + * The result will contain only the paths of types for which the predicate returned {@code true}. + * It is recommended to apply a filter for retaining only the types of paths of interest, + * because it can resolve ambiguities when a path could be of many types. + * + * @param pathTypeFilter predicate evaluating whether a path type should be included in the result + * @return {@code this} for method call chaining + */ + @Nonnull + public DependencyResolverRequestBuilder pathTypeFilter(@Nonnull Predicate pathTypeFilter) { + this.pathTypeFilter = pathTypeFilter; return this; } + /** + * Specifies the type of paths to include in the result. This is a convenience method for + * {@link #pathTypeFilter(Predicate)} using {@link Collection#contains(Object)} as the filter. + * + * @param desiredTypes the type of paths to include in the result + * @return {@code this} for method call chaining + */ + @Nonnull + public DependencyResolverRequestBuilder pathTypeFilter(@Nonnull Collection desiredTypes) { + return pathTypeFilter(desiredTypes::contains); + } + @Override public DependencyResolverRequest build() { return new DefaultDependencyResolverRequest( - session, project, rootArtifact, root, dependencies, managedDependencies, verbose, resolutionScope); + session, + project, + rootArtifact, + root, + dependencies, + managedDependencies, + verbose, + pathScope, + pathTypeFilter); } static class DefaultDependencyResolverRequest extends DefaultDependencyCollectorRequest implements DependencyResolverRequest { - private final ResolutionScope resolutionScope; + private final PathScope pathScope; + + private final Predicate pathTypeFilter; DefaultDependencyResolverRequest( Session session, @@ -177,9 +228,11 @@ static class DefaultDependencyResolverRequest extends DefaultDependencyCollector Collection dependencies, Collection managedDependencies, boolean verbose, - ResolutionScope resolutionScope) { + PathScope pathScope, + Predicate pathTypeFilter) { super(session, project, rootArtifact, root, dependencies, managedDependencies, verbose); - this.resolutionScope = nonNull(resolutionScope, "resolutionScope cannot be null"); + this.pathScope = nonNull(pathScope, "pathScope cannot be null"); + this.pathTypeFilter = (pathTypeFilter != null) ? pathTypeFilter : (t) -> true; if (verbose) { throw new IllegalArgumentException("verbose cannot be true for resolving dependencies"); } @@ -187,8 +240,13 @@ static class DefaultDependencyResolverRequest extends DefaultDependencyCollector @Nonnull @Override - public ResolutionScope getResolutionScope() { - return resolutionScope; + public PathScope getPathScope() { + return pathScope; + } + + @Override + public Predicate getPathTypeFilter() { + return pathTypeFilter; } } } diff --git a/api/maven-api-core/src/main/java/org/apache/maven/api/services/DependencyResolverResult.java b/api/maven-api-core/src/main/java/org/apache/maven/api/services/DependencyResolverResult.java index 793243850068..60468046145e 100644 --- a/api/maven-api-core/src/main/java/org/apache/maven/api/services/DependencyResolverResult.java +++ b/api/maven-api-core/src/main/java/org/apache/maven/api/services/DependencyResolverResult.java @@ -21,9 +21,11 @@ import java.nio.file.Path; import java.util.List; import java.util.Map; +import java.util.Optional; import org.apache.maven.api.Dependency; import org.apache.maven.api.Node; +import org.apache.maven.api.PathType; import org.apache.maven.api.annotations.Experimental; import org.apache.maven.api.annotations.Nonnull; @@ -32,13 +34,57 @@ public interface DependencyResolverResult extends DependencyCollectorResult { /** * The ordered list of the flattened dependency nodes. + * + * @return the ordered list of the flattened dependency nodes */ @Nonnull List getNodes(); + /** + * Returns the file paths of all dependencies, regardless on which tool option those paths should be placed. + * The returned list may contain a mix of Java class-path, Java module-path, and other types of path elements. + * + * @return the paths of all dependencies + */ @Nonnull List getPaths(); + /** + * Returns the file paths of all dependencies, dispatched according the tool options where to place them. + * The {@link PathType} keys identify, for example, {@code --class-path} or {@code --module-path} options. + * In the case of Java tools, the map may also contain {@code --patch-module} options, which are + * {@linkplain org.apache.maven.api.JavaPathType#patchModule(String) handled in a special way}. + * + *

Design note: + * All types of path are determined together because they are sometime mutually exclusive. + * For example, an artifact of type {@value org.apache.maven.api.Type#JAR} can be placed + * either on the class-path or on the module-path. The project needs to make a choice + * (possibly using heuristic rules), then to add the dependency in only one of the options + * identified by {@link PathType}.

+ * + * @return file paths to place on the different tool options + */ + @Nonnull + Map> getDispatchedPaths(); + @Nonnull Map getDependencies(); + + /** + * Formats the command-line option for the path of the specified type. + * The option are documented in {@link org.apache.maven.api.JavaPathType} enumeration values. + * + * @param type the desired type of path (class-path, module-path, …) + * @return the option to pass to Java tools + */ + default Optional formatOption(final PathType type) { + List paths = getDispatchedPaths().get(type); + if (paths != null) { + String option = type.option(paths); + if (!option.isEmpty()) { + return Optional.of(option); + } + } + return Optional.empty(); + } } diff --git a/api/maven-api-core/src/main/java/org/apache/maven/api/services/ExtensibleEnumRegistry.java b/api/maven-api-core/src/main/java/org/apache/maven/api/services/ExtensibleEnumRegistry.java new file mode 100644 index 000000000000..3a5673c583e2 --- /dev/null +++ b/api/maven-api-core/src/main/java/org/apache/maven/api/services/ExtensibleEnumRegistry.java @@ -0,0 +1,34 @@ +/* + * 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.services; + +import java.util.Optional; + +import org.apache.maven.api.ExtensibleEnum; +import org.apache.maven.api.Service; +import org.apache.maven.api.annotations.Nonnull; + +public interface ExtensibleEnumRegistry extends Service { + @Nonnull + Optional lookup(String id); + + default T require(String id) { + return lookup(id).orElseThrow(() -> new IllegalArgumentException("Unknown extensible enum value '" + id + "'")); + } +} diff --git a/api/maven-api-core/src/main/java/org/apache/maven/api/services/LanguageRegistry.java b/api/maven-api-core/src/main/java/org/apache/maven/api/services/LanguageRegistry.java new file mode 100644 index 000000000000..d2f0f0933157 --- /dev/null +++ b/api/maven-api-core/src/main/java/org/apache/maven/api/services/LanguageRegistry.java @@ -0,0 +1,23 @@ +/* + * 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.services; + +import org.apache.maven.api.Language; + +public interface LanguageRegistry extends ExtensibleEnumRegistry {} diff --git a/api/maven-api-core/src/main/java/org/apache/maven/api/services/PackagingRegistry.java b/api/maven-api-core/src/main/java/org/apache/maven/api/services/PackagingRegistry.java new file mode 100644 index 000000000000..e114e2664058 --- /dev/null +++ b/api/maven-api-core/src/main/java/org/apache/maven/api/services/PackagingRegistry.java @@ -0,0 +1,23 @@ +/* + * 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.services; + +import org.apache.maven.api.Packaging; + +public interface PackagingRegistry extends ExtensibleEnumRegistry {} diff --git a/api/maven-api-core/src/main/java/org/apache/maven/api/services/PathScopeRegistry.java b/api/maven-api-core/src/main/java/org/apache/maven/api/services/PathScopeRegistry.java new file mode 100644 index 000000000000..06b5d5964733 --- /dev/null +++ b/api/maven-api-core/src/main/java/org/apache/maven/api/services/PathScopeRegistry.java @@ -0,0 +1,23 @@ +/* + * 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.services; + +import org.apache.maven.api.PathScope; + +public interface PathScopeRegistry extends ExtensibleEnumRegistry {} diff --git a/api/maven-api-core/src/main/java/org/apache/maven/api/services/ProjectScopeRegistry.java b/api/maven-api-core/src/main/java/org/apache/maven/api/services/ProjectScopeRegistry.java new file mode 100644 index 000000000000..a3b4f24a5d58 --- /dev/null +++ b/api/maven-api-core/src/main/java/org/apache/maven/api/services/ProjectScopeRegistry.java @@ -0,0 +1,26 @@ +/* + * 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.services; + +import org.apache.maven.api.ProjectScope; + +/** + * Manager for {@link ProjectScope}. + */ +public interface ProjectScopeRegistry extends ExtensibleEnumRegistry {} diff --git a/api/maven-api-core/src/main/java/org/apache/maven/api/services/TypeRegistry.java b/api/maven-api-core/src/main/java/org/apache/maven/api/services/TypeRegistry.java index c484df40f5db..ca4c40d48a19 100644 --- a/api/maven-api-core/src/main/java/org/apache/maven/api/services/TypeRegistry.java +++ b/api/maven-api-core/src/main/java/org/apache/maven/api/services/TypeRegistry.java @@ -18,7 +18,6 @@ */ package org.apache.maven.api.services; -import org.apache.maven.api.Service; import org.apache.maven.api.Type; import org.apache.maven.api.annotations.Experimental; import org.apache.maven.api.annotations.Nonnull; @@ -29,7 +28,7 @@ * @since 4.0.0 */ @Experimental -public interface TypeRegistry extends Service { +public interface TypeRegistry extends ExtensibleEnumRegistry { /** * Obtain the {@link Type} from the specified {@code id}. @@ -40,5 +39,5 @@ public interface TypeRegistry extends Service { * @return the type */ @Nonnull - Type getType(@Nonnull String id); + Type require(@Nonnull String id); } diff --git a/api/maven-api-spi/src/main/java/org/apache/maven/api/spi/ExtensibleEnumProvider.java b/api/maven-api-spi/src/main/java/org/apache/maven/api/spi/ExtensibleEnumProvider.java new file mode 100644 index 000000000000..514651088ac3 --- /dev/null +++ b/api/maven-api-spi/src/main/java/org/apache/maven/api/spi/ExtensibleEnumProvider.java @@ -0,0 +1,28 @@ +/* + * 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.spi; + +import java.util.Collection; + +import org.apache.maven.api.ExtensibleEnum; + +public interface ExtensibleEnumProvider { + + Collection provides(); +} diff --git a/api/maven-api-spi/src/main/java/org/apache/maven/api/spi/LanguageProvider.java b/api/maven-api-spi/src/main/java/org/apache/maven/api/spi/LanguageProvider.java new file mode 100644 index 000000000000..9757d04f2005 --- /dev/null +++ b/api/maven-api-spi/src/main/java/org/apache/maven/api/spi/LanguageProvider.java @@ -0,0 +1,23 @@ +/* + * 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.spi; + +import org.apache.maven.api.Language; + +public interface LanguageProvider extends ExtensibleEnumProvider {} diff --git a/api/maven-api-spi/src/main/java/org/apache/maven/api/spi/PackagingProvider.java b/api/maven-api-spi/src/main/java/org/apache/maven/api/spi/PackagingProvider.java new file mode 100644 index 000000000000..bfcf3a219100 --- /dev/null +++ b/api/maven-api-spi/src/main/java/org/apache/maven/api/spi/PackagingProvider.java @@ -0,0 +1,23 @@ +/* + * 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.spi; + +import org.apache.maven.api.Packaging; + +public interface PackagingProvider extends ExtensibleEnumProvider {} diff --git a/api/maven-api-spi/src/main/java/org/apache/maven/api/spi/PathScopeProvider.java b/api/maven-api-spi/src/main/java/org/apache/maven/api/spi/PathScopeProvider.java new file mode 100644 index 000000000000..2e2597b839d0 --- /dev/null +++ b/api/maven-api-spi/src/main/java/org/apache/maven/api/spi/PathScopeProvider.java @@ -0,0 +1,23 @@ +/* + * 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.spi; + +import org.apache.maven.api.PathScope; + +public interface PathScopeProvider extends ExtensibleEnumProvider {} diff --git a/api/maven-api-spi/src/main/java/org/apache/maven/api/spi/ProjectScopeProvider.java b/api/maven-api-spi/src/main/java/org/apache/maven/api/spi/ProjectScopeProvider.java new file mode 100644 index 000000000000..c4a9a1f992a3 --- /dev/null +++ b/api/maven-api-spi/src/main/java/org/apache/maven/api/spi/ProjectScopeProvider.java @@ -0,0 +1,23 @@ +/* + * 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.spi; + +import org.apache.maven.api.ProjectScope; + +public interface ProjectScopeProvider extends ExtensibleEnumProvider {} diff --git a/maven-artifact/src/main/java/org/apache/maven/artifact/DefaultArtifact.java b/maven-artifact/src/main/java/org/apache/maven/artifact/DefaultArtifact.java index 9a1ca1f4336b..44f2615790ca 100644 --- a/maven-artifact/src/main/java/org/apache/maven/artifact/DefaultArtifact.java +++ b/maven-artifact/src/main/java/org/apache/maven/artifact/DefaultArtifact.java @@ -181,52 +181,64 @@ private boolean empty(String value) { return (value == null) || (value.trim().length() < 1); } + @Override public String getClassifier() { return classifier; } + @Override public boolean hasClassifier() { return classifier != null && !classifier.isEmpty(); } + @Override public String getScope() { return scope; } + @Override public String getGroupId() { return groupId; } + @Override public String getArtifactId() { return artifactId; } + @Override public String getVersion() { return version; } + @Override public void setVersion(String version) { this.version = version; setBaseVersionInternal(version); versionRange = null; } + @Override public String getType() { return type; } + @Override public void setFile(File file) { this.file = file; } + @Override public File getFile() { return file; } + @Override public ArtifactRepository getRepository() { return repository; } + @Override public void setRepository(ArtifactRepository repository) { this.repository = repository; } @@ -235,10 +247,12 @@ public void setRepository(ArtifactRepository repository) { // // ---------------------------------------------------------------------- + @Override public String getId() { return getDependencyConflictId() + ":" + getBaseVersion(); } + @Override public String getDependencyConflictId() { StringBuilder sb = new StringBuilder(128); sb.append(getGroupId()); @@ -257,6 +271,7 @@ private void appendArtifactTypeClassifierString(StringBuilder sb) { } } + @Override public void addMetadata(ArtifactMetadata metadata) { if (metadataMap == null) { metadataMap = new HashMap<>(); @@ -270,6 +285,7 @@ public void addMetadata(ArtifactMetadata metadata) { } } + @Override public Collection getMetadataList() { if (metadataMap == null) { return Collections.emptyList(); @@ -282,6 +298,7 @@ public Collection getMetadataList() { // Object overrides // ---------------------------------------------------------------------- + @Override public String toString() { StringBuilder sb = new StringBuilder(); if (getGroupId() != null) { @@ -323,6 +340,7 @@ public int hashCode() { return Objects.hash(groupId, artifactId, type, classifier, version); } + @Override public String getBaseVersion() { if (baseVersion == null && version != null) { setBaseVersionInternal(version); @@ -339,6 +357,7 @@ protected String getBaseVersionInternal() { return baseVersion; } + @Override public void setBaseVersion(String baseVersion) { setBaseVersionInternal(baseVersion); } @@ -347,6 +366,7 @@ protected void setBaseVersionInternal(String baseVersion) { this.baseVersion = ArtifactUtils.toSnapshotVersion(baseVersion); } + @Override public int compareTo(Artifact a) { int result = groupId.compareTo(a.getGroupId()); if (result == 0) { @@ -376,47 +396,58 @@ public int compareTo(Artifact a) { return result; } + @Override public void updateVersion(String version, ArtifactRepository localRepository) { setResolvedVersion(version); setFile(new File(localRepository.getBasedir(), localRepository.pathOf(this))); } + @Override public String getDownloadUrl() { return downloadUrl; } + @Override public void setDownloadUrl(String downloadUrl) { this.downloadUrl = downloadUrl; } + @Override public ArtifactFilter getDependencyFilter() { return dependencyFilter; } + @Override public void setDependencyFilter(ArtifactFilter artifactFilter) { dependencyFilter = artifactFilter; } + @Override public ArtifactHandler getArtifactHandler() { return artifactHandler; } + @Override public List getDependencyTrail() { return dependencyTrail; } + @Override public void setDependencyTrail(List dependencyTrail) { this.dependencyTrail = dependencyTrail; } + @Override public void setScope(String scope) { this.scope = scope; } + @Override public VersionRange getVersionRange() { return versionRange; } + @Override public void setVersionRange(VersionRange versionRange) { this.versionRange = versionRange; selectVersionFromNewRangeIfAvailable(); @@ -431,70 +462,86 @@ private void selectVersionFromNewRangeIfAvailable() { } } + @Override public void selectVersion(String version) { this.version = version; setBaseVersionInternal(version); } + @Override public void setGroupId(String groupId) { this.groupId = groupId; } + @Override public void setArtifactId(String artifactId) { this.artifactId = artifactId; } + @Override public boolean isSnapshot() { return getBaseVersion() != null && (getBaseVersion().endsWith(SNAPSHOT_VERSION) || getBaseVersion().equals(LATEST_VERSION)); } + @Override public void setResolved(boolean resolved) { this.resolved = resolved; } + @Override public boolean isResolved() { return resolved; } + @Override public void setResolvedVersion(String version) { this.version = version; // retain baseVersion } + @Override public void setArtifactHandler(ArtifactHandler artifactHandler) { this.artifactHandler = artifactHandler; } + @Override public void setRelease(boolean release) { this.release = release; } + @Override public boolean isRelease() { return release; } + @Override public List getAvailableVersions() { return availableVersions; } + @Override public void setAvailableVersions(List availableVersions) { this.availableVersions = availableVersions; } + @Override public boolean isOptional() { return optional; } + @Override public ArtifactVersion getSelectedVersion() throws OverConstrainedVersionException { return versionRange.getSelectedVersion(this); } + @Override public boolean isSelectedVersionKnown() throws OverConstrainedVersionException { return versionRange.isSelectedVersionKnown(this); } + @Override public void setOptional(boolean optional) { this.optional = optional; } diff --git a/maven-artifact/src/main/java/org/apache/maven/artifact/handler/ArtifactHandler.java b/maven-artifact/src/main/java/org/apache/maven/artifact/handler/ArtifactHandler.java index 92cca2a069dd..12f50ef31a76 100644 --- a/maven-artifact/src/main/java/org/apache/maven/artifact/handler/ArtifactHandler.java +++ b/maven-artifact/src/main/java/org/apache/maven/artifact/handler/ArtifactHandler.java @@ -53,5 +53,16 @@ public interface ArtifactHandler { String getLanguage(); + /** + * Specifies if the artifact contains java classes and can be added to the classpath. + * Whether the artifact should be added to the classpath depends on other + * dependency properties. + * + * @return if the artifact can be added to the class path + * + * @deprecated A value of {@code true} does not mean that the dependency should + * be placed on the classpath. See {@code JavaPathType} instead for better analysis. + */ + @Deprecated boolean isAddedToClasspath(); } diff --git a/maven-artifact/src/test/java/org/apache/maven/artifact/handler/ArtifactHandlerMock.java b/maven-artifact/src/test/java/org/apache/maven/artifact/handler/ArtifactHandlerMock.java index deb3e77c5456..f70db5bcbd32 100644 --- a/maven-artifact/src/test/java/org/apache/maven/artifact/handler/ArtifactHandlerMock.java +++ b/maven-artifact/src/test/java/org/apache/maven/artifact/handler/ArtifactHandlerMock.java @@ -27,6 +27,7 @@ public void setExtension(String extension) { this.extension = extension; } + @Override public String getExtension() { return extension; } @@ -35,6 +36,7 @@ public void setDirectory(String directory) { this.directory = directory; } + @Override public String getDirectory() { return directory; } @@ -43,6 +45,7 @@ public void setClassifier(String classifier) { this.classifier = classifier; } + @Override public String getClassifier() { return classifier; } @@ -51,6 +54,7 @@ public void setPackaging(String packaging) { this.packaging = packaging; } + @Override public String getPackaging() { return packaging; } @@ -59,6 +63,7 @@ public void setIncludesDependencies(boolean includesDependencies) { this.includesDependencies = includesDependencies; } + @Override public boolean isIncludesDependencies() { return includesDependencies; } @@ -67,14 +72,18 @@ public void setLanguage(String language) { this.language = language; } + @Override public String getLanguage() { return language; } + @Deprecated public void setAddedToClasspath(boolean addedToClasspath) { this.addedToClasspath = addedToClasspath; } + @Override + @Deprecated public boolean isAddedToClasspath() { return addedToClasspath; } diff --git a/maven-compat/src/test/java/org/apache/maven/repository/TestArtifactHandler.java b/maven-compat/src/test/java/org/apache/maven/repository/TestArtifactHandler.java index 860fbe3fb528..93c90bd889fa 100644 --- a/maven-compat/src/test/java/org/apache/maven/repository/TestArtifactHandler.java +++ b/maven-compat/src/test/java/org/apache/maven/repository/TestArtifactHandler.java @@ -39,30 +39,38 @@ public TestArtifactHandler(String type, String extension) { this.extension = extension; } + @Override public String getClassifier() { return null; } + @Override public String getDirectory() { return getPackaging() + "s"; } + @Override public String getExtension() { return extension; } + @Override public String getLanguage() { return "java"; } + @Override public String getPackaging() { return type; } + @Override + @Deprecated public boolean isAddedToClasspath() { return true; } + @Override public boolean isIncludesDependencies() { return false; } diff --git a/maven-core/src/main/java/org/apache/maven/artifact/handler/DefaultArtifactHandler.java b/maven-core/src/main/java/org/apache/maven/artifact/handler/DefaultArtifactHandler.java index 0a083732c05d..198fe6fb6aa3 100644 --- a/maven-core/src/main/java/org/apache/maven/artifact/handler/DefaultArtifactHandler.java +++ b/maven-core/src/main/java/org/apache/maven/artifact/handler/DefaultArtifactHandler.java @@ -37,6 +37,7 @@ public class DefaultArtifactHandler implements ArtifactHandler { private String language; + @Deprecated private boolean addedToClasspath; /** @@ -146,10 +147,12 @@ public void setLanguage(final String language) { } @Override + @Deprecated public boolean isAddedToClasspath() { return addedToClasspath; } + @Deprecated public void setAddedToClasspath(final boolean addedToClasspath) { this.addedToClasspath = addedToClasspath; } diff --git a/maven-core/src/main/java/org/apache/maven/artifact/handler/manager/DefaultArtifactHandlerManager.java b/maven-core/src/main/java/org/apache/maven/artifact/handler/manager/DefaultArtifactHandlerManager.java index 87166eb29a75..2ad0541914dc 100644 --- a/maven-core/src/main/java/org/apache/maven/artifact/handler/manager/DefaultArtifactHandlerManager.java +++ b/maven-core/src/main/java/org/apache/maven/artifact/handler/manager/DefaultArtifactHandlerManager.java @@ -26,6 +26,7 @@ import java.util.Set; import java.util.concurrent.ConcurrentHashMap; +import org.apache.maven.api.JavaPathType; import org.apache.maven.api.Type; import org.apache.maven.api.services.TypeRegistry; import org.apache.maven.artifact.handler.ArtifactHandler; @@ -60,9 +61,10 @@ public void onEvent(Object event) { } } + @Override public ArtifactHandler getArtifactHandler(String id) { return allHandlers.computeIfAbsent(id, k -> { - Type type = typeRegistry.getType(id); + Type type = typeRegistry.require(id); return new DefaultArtifactHandler( id, type.getExtension(), @@ -70,11 +72,17 @@ public ArtifactHandler getArtifactHandler(String id) { null, null, type.isIncludesDependencies(), - type.getLanguage(), - type.isAddedToClassPath()); // TODO: watch out for module path + type.getLanguage().id(), + type.getPathTypes().contains(JavaPathType.CLASSES)); + // TODO: watch out for module path }); + + // Note: here, type decides is artifact added to "build path" (for example during resolution) + // and "build path" is intermediate data that is used to create actual Java classpath/modulepath + // but to create those, proper filtering should happen via Type properties. } + @Override public void addHandlers(Map handlers) { throw new UnsupportedOperationException("Adding handlers programmatically is not supported anymore"); } diff --git a/maven-core/src/main/java/org/apache/maven/internal/aether/TypeRegistryAdapter.java b/maven-core/src/main/java/org/apache/maven/internal/aether/TypeRegistryAdapter.java index b5f3f9c61437..d3861f2c571c 100644 --- a/maven-core/src/main/java/org/apache/maven/internal/aether/TypeRegistryAdapter.java +++ b/maven-core/src/main/java/org/apache/maven/internal/aether/TypeRegistryAdapter.java @@ -18,6 +18,7 @@ */ package org.apache.maven.internal.aether; +import org.apache.maven.api.PathType; import org.apache.maven.api.Type; import org.apache.maven.api.services.TypeRegistry; import org.apache.maven.internal.impl.DefaultType; @@ -35,17 +36,18 @@ class TypeRegistryAdapter implements ArtifactTypeRegistry { @Override public ArtifactType get(String typeId) { - Type type = typeRegistry.getType(typeId); + Type type = typeRegistry.require(typeId); if (type instanceof ArtifactType) { return (ArtifactType) type; } if (type != null) { return new DefaultType( - type.getId(), + type.id(), type.getLanguage(), type.getExtension(), type.getClassifier(), - type.getDependencyProperties()); + type.isIncludesDependencies(), + type.getPathTypes().toArray(new PathType[0])); } return null; } diff --git a/maven-core/src/main/java/org/apache/maven/internal/impl/AbstractSession.java b/maven-core/src/main/java/org/apache/maven/internal/impl/AbstractSession.java index ce90e19be732..bc65ac91d881 100644 --- a/maven-core/src/main/java/org/apache/maven/internal/impl/AbstractSession.java +++ b/maven-core/src/main/java/org/apache/maven/internal/impl/AbstractSession.java @@ -29,6 +29,7 @@ import java.util.Optional; import java.util.WeakHashMap; import java.util.concurrent.CopyOnWriteArrayList; +import java.util.stream.Collectors; import org.apache.maven.api.*; import org.apache.maven.api.annotations.Nonnull; @@ -53,40 +54,49 @@ public abstract class AbstractSession implements InternalSession { private final Map allDependencies = Collections.synchronizedMap(new WeakHashMap<>()); + @Override public RemoteRepository getRemoteRepository(org.eclipse.aether.repository.RemoteRepository repository) { return allRepositories.computeIfAbsent(repository, DefaultRemoteRepository::new); } + @Override public Node getNode(org.eclipse.aether.graph.DependencyNode node) { return getNode(node, false); } + @Override public Node getNode(org.eclipse.aether.graph.DependencyNode node, boolean verbose) { return allNodes.computeIfAbsent(node, n -> new DefaultNode(this, n, verbose)); } @Nonnull + @Override public Artifact getArtifact(@Nonnull org.eclipse.aether.artifact.Artifact artifact) { return allArtifacts.computeIfAbsent(artifact, a -> new DefaultArtifact(this, a)); } @Nonnull + @Override public Dependency getDependency(@Nonnull org.eclipse.aether.graph.Dependency dependency) { return allDependencies.computeIfAbsent(dependency, d -> new DefaultDependency(this, d)); } + @Override public List getProjects(List projects) { return projects == null ? null : map(projects, this::getProject); } + @Override public Project getProject(MavenProject project) { return allProjects.computeIfAbsent(project.getId(), id -> new DefaultProject(this, project)); } + @Override public List toRepositories(List repositories) { return repositories == null ? null : map(repositories, this::toRepository); } + @Override public org.eclipse.aether.repository.RemoteRepository toRepository(RemoteRepository repository) { if (repository instanceof DefaultRemoteRepository) { return ((DefaultRemoteRepository) repository).getRepository(); @@ -96,6 +106,7 @@ public org.eclipse.aether.repository.RemoteRepository toRepository(RemoteReposit } } + @Override public org.eclipse.aether.repository.LocalRepository toRepository(LocalRepository repository) { if (repository instanceof DefaultLocalRepository) { return ((DefaultLocalRepository) repository).getRepository(); @@ -105,22 +116,28 @@ public org.eclipse.aether.repository.LocalRepository toRepository(LocalRepositor } } + @Override public List toArtifactRepositories(List repositories) { return repositories == null ? null : map(repositories, this::toArtifactRepository); } + @Override public abstract ArtifactRepository toArtifactRepository(RemoteRepository repository); + @Override public List toDependencies(Collection dependencies) { return dependencies == null ? null : map(dependencies, this::toDependency); } + @Override public abstract org.eclipse.aether.graph.Dependency toDependency(DependencyCoordinate dependency); + @Override public List toArtifacts(Collection artifacts) { return artifacts == null ? null : map(artifacts, this::toArtifact); } + @Override public org.eclipse.aether.artifact.Artifact toArtifact(Artifact artifact) { File file = getService(ArtifactManager.class) .getPath(artifact) @@ -142,6 +159,7 @@ public org.eclipse.aether.artifact.Artifact toArtifact(Artifact artifact) { file); } + @Override public org.eclipse.aether.artifact.Artifact toArtifact(ArtifactCoordinate coord) { if (coord instanceof DefaultArtifactCoordinate) { return ((DefaultArtifactCoordinate) coord).getCoordinate(); @@ -335,7 +353,8 @@ public Map.Entry resolveArtifact(Artifact artifact) { @Override public Map resolveArtifacts(Artifact... artifacts) { ArtifactCoordinateFactory acf = getService(ArtifactCoordinateFactory.class); - List coords = map(Arrays.asList(artifacts), a -> acf.create(this, a)); + List coords = + Arrays.stream(artifacts).map(a -> acf.create(this, a)).collect(Collectors.toList()); return resolveArtifacts(coords); } @@ -420,6 +439,7 @@ public DependencyCoordinate createDependencyCoordinate(@Nonnull ArtifactCoordina * @see DependencyCoordinateFactory#create(Session, ArtifactCoordinate) */ @Nonnull + @Override public DependencyCoordinate createDependencyCoordinate(@Nonnull Dependency dependency) { return getService(DependencyCoordinateFactory.class).create(this, dependency); } @@ -462,7 +482,7 @@ public Node collectDependencies(@Nonnull DependencyCoordinate dependency) { @Nonnull @Override - public List flattenDependencies(@Nonnull Node node, @Nonnull ResolutionScope scope) { + public List flattenDependencies(@Nonnull Node node, @Nonnull PathScope scope) { return getService(DependencyResolver.class).flatten(this, node, scope); } @@ -477,12 +497,40 @@ public List resolveDependencies(List dependencies) { } @Override - public List resolveDependencies(Project project, ResolutionScope scope) { + public List resolveDependencies(Project project, PathScope scope) { return getService(DependencyResolver.class) .resolve(this, project, scope) .getPaths(); } + @Override + public Map> resolveDependencies( + @Nonnull DependencyCoordinate dependency, + @Nonnull PathScope scope, + @Nonnull Collection desiredTypes) { + return getService(DependencyResolver.class) + .resolve(DependencyResolverRequest.builder() + .session(this) + .dependency(dependency) + .pathScope(scope) + .pathTypeFilter(desiredTypes) + .build()) + .getDispatchedPaths(); + } + + @Override + public Map> resolveDependencies( + @Nonnull Project project, @Nonnull PathScope scope, @Nonnull Collection desiredTypes) { + return getService(DependencyResolver.class) + .resolve(DependencyResolverRequest.builder() + .session(this) + .project(project) + .pathScope(scope) + .pathTypeFilter(desiredTypes) + .build()) + .getDispatchedPaths(); + } + @Override public Path getPathForLocalArtifact(@Nonnull Artifact artifact) { return getService(LocalRepositoryManager.class).getPathForLocalArtifact(this, getLocalRepository(), artifact); @@ -518,4 +566,34 @@ public Version resolveVersion(ArtifactCoordinate artifact) { public List resolveVersionRange(ArtifactCoordinate artifact) { return getService(VersionRangeResolver.class).resolve(this, artifact).getVersions(); } + + @Override + public Type requireType(String id) { + return getService(TypeRegistry.class).require(id); + } + + @Override + public Language requireLanguage(String id) { + return getService(LanguageRegistry.class).require(id); + } + + @Override + public Packaging requirePackaging(String id) { + return getService(PackagingRegistry.class).require(id); + } + + @Override + public ProjectScope requireProjectScope(String id) { + return getService(ProjectScopeRegistry.class).require(id); + } + + @Override + public DependencyScope requireDependencyScope(String id) { + return DependencyScope.forId(id); + } + + @Override + public PathScope requirePathScope(String id) { + return getService(PathScopeRegistry.class).require(id); + } } diff --git a/maven-core/src/main/java/org/apache/maven/internal/impl/DefaultDependency.java b/maven-core/src/main/java/org/apache/maven/internal/impl/DefaultDependency.java index 3f2f06893ebf..8137a2e2c968 100644 --- a/maven-core/src/main/java/org/apache/maven/internal/impl/DefaultDependency.java +++ b/maven-core/src/main/java/org/apache/maven/internal/impl/DefaultDependency.java @@ -23,30 +23,26 @@ import org.apache.maven.api.Artifact; import org.apache.maven.api.Dependency; import org.apache.maven.api.DependencyCoordinate; -import org.apache.maven.api.DependencyProperties; -import org.apache.maven.api.Scope; +import org.apache.maven.api.DependencyScope; import org.apache.maven.api.Type; import org.apache.maven.api.Version; import org.apache.maven.api.annotations.Nonnull; import org.apache.maven.api.annotations.Nullable; -import org.apache.maven.api.services.TypeRegistry; import org.apache.maven.repository.internal.DefaultModelVersionParser; import org.eclipse.aether.artifact.ArtifactProperties; import static org.apache.maven.internal.impl.Utils.nonNull; public class DefaultDependency implements Dependency { + private final InternalSession session; private final org.eclipse.aether.graph.Dependency dependency; - private final DependencyProperties dependencyProperties; private final String key; public DefaultDependency( @Nonnull InternalSession session, @Nonnull org.eclipse.aether.graph.Dependency dependency) { this.session = nonNull(session, "session"); this.dependency = nonNull(dependency, "dependency"); - this.dependencyProperties = - new DefaultDependencyProperties(dependency.getArtifact().getProperties()); this.key = getGroupId() + ':' + getArtifactId() @@ -102,12 +98,7 @@ public Type getType() { String type = dependency .getArtifact() .getProperty(ArtifactProperties.TYPE, dependency.getArtifact().getExtension()); - return session.getService(TypeRegistry.class).getType(type); - } - - @Override - public DependencyProperties getDependencyProperties() { - return dependencyProperties; + return session.requireType(type); } @Override @@ -117,8 +108,8 @@ public boolean isSnapshot() { @Nonnull @Override - public Scope getScope() { - return Scope.get(dependency.getScope()); + public DependencyScope getScope() { + return session.requireDependencyScope(dependency.getScope()); } @Nullable diff --git a/maven-core/src/main/java/org/apache/maven/internal/impl/DefaultDependencyCoordinate.java b/maven-core/src/main/java/org/apache/maven/internal/impl/DefaultDependencyCoordinate.java index 54121e8da6d8..c0420f5fbba4 100644 --- a/maven-core/src/main/java/org/apache/maven/internal/impl/DefaultDependencyCoordinate.java +++ b/maven-core/src/main/java/org/apache/maven/internal/impl/DefaultDependencyCoordinate.java @@ -23,7 +23,6 @@ import org.apache.maven.api.*; import org.apache.maven.api.annotations.Nonnull; import org.apache.maven.api.annotations.Nullable; -import org.apache.maven.api.services.TypeRegistry; import org.eclipse.aether.artifact.ArtifactProperties; import static org.apache.maven.internal.impl.Utils.nonNull; @@ -73,13 +72,13 @@ public Type getType() { String type = dependency .getArtifact() .getProperty(ArtifactProperties.TYPE, dependency.getArtifact().getExtension()); - return session.getService(TypeRegistry.class).getType(type); + return session.requireType(type); } @Nonnull @Override - public Scope getScope() { - return Scope.get(dependency.getScope()); + public DependencyScope getScope() { + return session.requireDependencyScope(dependency.getScope()); } @Nullable diff --git a/maven-core/src/main/java/org/apache/maven/internal/impl/DefaultDependencyProperties.java b/maven-core/src/main/java/org/apache/maven/internal/impl/DefaultDependencyProperties.java deleted file mode 100644 index 2ee780713034..000000000000 --- a/maven-core/src/main/java/org/apache/maven/internal/impl/DefaultDependencyProperties.java +++ /dev/null @@ -1,66 +0,0 @@ -/* - * 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.internal.impl; - -import java.util.Arrays; -import java.util.Collection; -import java.util.Collections; -import java.util.HashMap; -import java.util.Map; - -import org.apache.maven.api.DependencyProperties; -import org.apache.maven.api.annotations.Nonnull; - -import static org.apache.maven.internal.impl.Utils.nonNull; - -/** - * Default implementation of artifact properties. - */ -public class DefaultDependencyProperties implements DependencyProperties { - private final Map properties; - - public DefaultDependencyProperties(String... flags) { - this(Arrays.asList(flags)); - } - - public DefaultDependencyProperties(@Nonnull Collection flags) { - nonNull(flags, "flags"); - HashMap map = new HashMap<>(); - for (String flag : flags) { - map.put(flag, Boolean.TRUE.toString()); - } - this.properties = Collections.unmodifiableMap(map); - } - - public DefaultDependencyProperties(@Nonnull Map properties) { - this.properties = Collections.unmodifiableMap(nonNull(properties, "properties")); - } - - @Nonnull - @Override - public Map asMap() { - return properties; - } - - @Override - public boolean checkFlag(@Nonnull String flag) { - nonNull(flag, "flag"); - return Boolean.parseBoolean(properties.getOrDefault(flag, "")); - } -} diff --git a/maven-core/src/main/java/org/apache/maven/internal/impl/DefaultDependencyResolver.java b/maven-core/src/main/java/org/apache/maven/internal/impl/DefaultDependencyResolver.java index c5ba1a32b02d..65a1a18ea05f 100644 --- a/maven-core/src/main/java/org/apache/maven/internal/impl/DefaultDependencyResolver.java +++ b/maven-core/src/main/java/org/apache/maven/internal/impl/DefaultDependencyResolver.java @@ -21,25 +21,25 @@ import javax.inject.Named; import javax.inject.Singleton; +import java.io.IOException; import java.nio.file.Path; -import java.util.ArrayList; import java.util.Collection; import java.util.Collections; -import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.Objects; import java.util.Set; +import java.util.function.Predicate; import java.util.stream.Collectors; import java.util.stream.Stream; +import org.apache.maven.api.*; import org.apache.maven.api.Artifact; import org.apache.maven.api.ArtifactCoordinate; import org.apache.maven.api.Dependency; import org.apache.maven.api.Node; +import org.apache.maven.api.PathType; import org.apache.maven.api.Project; -import org.apache.maven.api.ResolutionScope; -import org.apache.maven.api.Scope; import org.apache.maven.api.Session; import org.apache.maven.api.services.*; import org.apache.maven.lifecycle.LifecycleExecutionException; @@ -58,7 +58,7 @@ public class DefaultDependencyResolver implements DependencyResolver { @Override - public List flatten(Session s, Node node, ResolutionScope scope) throws DependencyResolverException { + public List flatten(Session s, Node node, PathScope scope) throws DependencyResolverException { InternalSession session = InternalSession.from(s); DependencyNode root = cast(AbstractNode.class, node, "node").getDependencyNode(); List dependencies = session.getRepositorySystem() @@ -67,70 +67,81 @@ public List flatten(Session s, Node node, ResolutionScope scope) throws De return map(dependencies, session::getNode); } - private static DependencyFilter getScopeDependencyFilter(ResolutionScope scope) { - Set scopes = scope.scopes().stream().map(Scope::id).collect(Collectors.toSet()); + private static DependencyFilter getScopeDependencyFilter(PathScope scope) { + Set scopes = + scope.dependencyScopes().stream().map(DependencyScope::id).collect(Collectors.toSet()); return (n, p) -> { org.eclipse.aether.graph.Dependency d = n.getDependency(); return d == null || scopes.contains(d.getScope()); }; } + /** + * Collects, flattens and resolves the dependencies. + * + * @param request the request to resolve + * @return the result of the resolution + */ @Override - public DependencyResolverResult resolve(DependencyResolverRequest request) + public DependencyResolverResult resolve(final DependencyResolverRequest request) throws DependencyCollectorException, DependencyResolverException, ArtifactResolverException { - nonNull(request, "request can not be null"); - InternalSession session = InternalSession.from(request.getSession()); - + nonNull(request, "request"); + final InternalSession session = InternalSession.from(request.getSession()); + final Predicate filter = request.getPathTypeFilter(); + final PathModularizationCache cache = new PathModularizationCache(); // TODO: should be project-wide cache. if (request.getProject().isPresent()) { - DependencyResolutionResult result = resolveDependencies( - request.getSession(), request.getProject().get(), request.getResolutionScope()); + final DependencyResolutionResult resolved = resolveDependencies( + request.getSession(), request.getProject().get(), request.getPathScope()); - Map nodes = stream( - result.getDependencyGraph()) + final Map nodes = stream( + resolved.getDependencyGraph()) .filter(n -> n.getDependency() != null) .collect(Collectors.toMap(DependencyNode::getDependency, n -> n)); - Node root = session.getNode(result.getDependencyGraph()); - List dependencies = new ArrayList<>(); - Map artifacts = new LinkedHashMap<>(); - List paths = new ArrayList<>(); - for (org.eclipse.aether.graph.Dependency dep : result.getResolvedDependencies()) { - dependencies.add(session.getNode(nodes.get(dep))); + final Node root = session.getNode(resolved.getDependencyGraph()); + final List deprendencies = resolved.getResolvedDependencies(); + final DefaultDependencyResolverResult result = + new DefaultDependencyResolverResult(resolved.getCollectionErrors(), root, deprendencies.size()); + for (org.eclipse.aether.graph.Dependency dep : deprendencies) { + Node node = session.getNode(nodes.get(dep)); Path path = dep.getArtifact().getFile().toPath(); - artifacts.put(session.getDependency(dep), path); - paths.add(path); + try { + result.addDependency(node, session.getDependency(dep), filter, path, cache); + } catch (IOException e) { + throw cannotReadModuleInfo(path, e); + } } - return new DefaultDependencyResolverResult( - result.getCollectionErrors(), root, dependencies, paths, artifacts); + return result; } - DependencyCollectorResult collectorResult = + final DependencyCollectorResult collectorResult = session.getService(DependencyCollector.class).collect(request); - List nodes = flatten(session, collectorResult.getRoot(), request.getResolutionScope()); - List deps = - nodes.stream().map(Node::getDependency).filter(Objects::nonNull).collect(Collectors.toList()); - List coordinates = - deps.stream().map(Artifact::toCoordinate).collect(Collectors.toList()); - Map artifacts = session.resolveArtifacts(coordinates); - Map dependencies = new LinkedHashMap<>(); - List paths = new ArrayList<>(); - for (Dependency d : deps) { - Path path = artifacts.get(d); - if (dependencies.put(d, path) != null) { - throw new IllegalStateException("Duplicate key"); + final List nodes = flatten(session, collectorResult.getRoot(), request.getPathScope()); + final List coordinates = nodes.stream() + .map(Node::getDependency) + .filter(Objects::nonNull) + .map(Artifact::toCoordinate) + .collect(Collectors.toList()); + final Map artifacts = session.resolveArtifacts(coordinates); + final DefaultDependencyResolverResult result = new DefaultDependencyResolverResult( + collectorResult.getExceptions(), collectorResult.getRoot(), nodes.size()); + for (Node node : nodes) { + Dependency d = node.getDependency(); + Path path = (d != null) ? artifacts.get(d) : null; + try { + result.addDependency(node, d, filter, path, cache); + } catch (IOException e) { + throw cannotReadModuleInfo(path, e); } - paths.add(path); } - - return new DefaultDependencyResolverResult( - collectorResult.getExceptions(), collectorResult.getRoot(), nodes, paths, dependencies); + return result; } - private Stream stream(DependencyNode node) { - return Stream.concat(Stream.of(node), node.getChildren().stream().flatMap(this::stream)); + private static Stream stream(final DependencyNode node) { + return Stream.concat(Stream.of(node), node.getChildren().stream().flatMap(DefaultDependencyResolver::stream)); } - private DependencyResolutionResult resolveDependencies(Session session, Project project, ResolutionScope scope) { + private DependencyResolutionResult resolveDependencies(Session session, Project project, PathScope scope) { Collection toResolve = toScopes(scope); try { LifecycleDependencyResolver lifecycleDependencyResolver = @@ -147,57 +158,15 @@ private DependencyResolutionResult resolveDependencies(Session session, Project } } - private MavenProject getMavenProject(Project project) { + private static MavenProject getMavenProject(final Project project) { return ((DefaultProject) project).getProject(); } - private Collection toScopes(ResolutionScope scope) { - return map(scope.scopes(), Scope::id); + private Collection toScopes(PathScope scope) { + return map(scope.dependencyScopes(), DependencyScope::id); } - static class DefaultDependencyResolverResult implements DependencyResolverResult { - private final List exceptions; - private final Node root; - private final List nodes; - private final List paths; - private final Map dependencies; - - DefaultDependencyResolverResult( - List exceptions, - Node root, - List nodes, - List paths, - Map dependencies) { - this.exceptions = exceptions; - this.root = root; - this.nodes = nodes; - this.paths = paths; - this.dependencies = dependencies; - } - - @Override - public List getExceptions() { - return exceptions; - } - - @Override - public Node getRoot() { - return root; - } - - @Override - public List getNodes() { - return nodes; - } - - @Override - public List getPaths() { - return paths; - } - - @Override - public Map getDependencies() { - return dependencies; - } + private static DependencyResolverException cannotReadModuleInfo(final Path path, final IOException cause) { + return new DependencyResolverException("Cannot read module information of " + path, cause); } } diff --git a/maven-core/src/main/java/org/apache/maven/internal/impl/DefaultDependencyResolverResult.java b/maven-core/src/main/java/org/apache/maven/internal/impl/DefaultDependencyResolverResult.java new file mode 100644 index 000000000000..b41d307ab363 --- /dev/null +++ b/maven-core/src/main/java/org/apache/maven/internal/impl/DefaultDependencyResolverResult.java @@ -0,0 +1,354 @@ +/* + * 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.internal.impl; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.Collections; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.function.Predicate; + +import org.apache.maven.api.Dependency; +import org.apache.maven.api.JavaPathType; +import org.apache.maven.api.Node; +import org.apache.maven.api.PathType; +import org.apache.maven.api.services.DependencyResolverRequest; +import org.apache.maven.api.services.DependencyResolverResult; + +/** + * The result of collecting dependencies with a dependency resolver. + * New instances are initially empty. Callers must populate with calls + * to the following methods, in that order: + * + *
    + *
  • {@link #addOutputDirectory(Path, Path, PathModularizationCache)} (optional)
  • + *
  • {@link #addDependency(Node, Dependency, Predicate, Path, PathModularizationCache)}
  • + *
+ * + * @see DefaultDependencyResolver#resolve(DependencyResolverRequest) + */ +final class DefaultDependencyResolverResult implements DependencyResolverResult { + /** + * The exceptions that occurred while building the dependency graph. + */ + private final List exceptions; + + /** + * The root node of the dependency graph. + */ + private final Node root; + + /** + * The ordered list of the flattened dependency nodes. + */ + private final List nodes; + + /** + * The file paths of all dependencies, regardless on which Java tool option those paths should be placed. + */ + private final List paths; + + /** + * The file paths of all dependencies, dispatched according the Java options where to place them. + */ + private final Map> dispatchedPaths; + + /** + * The dependencies together with the path to each dependency. + */ + private final Map dependencies; + + /** + * Information about modules in the main output. This field is initially null and is set to a non-null + * value when the output directories have been set, or when it is too late for setting them. + */ + private PathModularization outputModules; + + /** + * Creates an initially empty result. Callers should add path elements by calls + * to {@link #addDependency(Node, Dependency, Predicate, Path, PathModularizationCache)}. + * + * @param exceptions the exceptions that occurred while building the dependency graph + * @param root the root node of the dependency graph + * @param count estimated number of dependencies + */ + DefaultDependencyResolverResult(final List exceptions, final Node root, final int count) { + this.exceptions = exceptions; + this.root = root; + nodes = new ArrayList<>(count); + paths = new ArrayList<>(count); + dispatchedPaths = new LinkedHashMap<>(); + dependencies = new LinkedHashMap<>(count + count / 3); + } + + /** + * Adds the given path element to the specified type of path. + * + * @param type the type of path (class-path, module-path, …) + * @param path the path element to add + */ + private void addPathElement(final PathType type, final Path path) { + dispatchedPaths.computeIfAbsent(type, (t) -> new ArrayList<>()).add(path); + } + + /** + * Adds main and test output directories to the result. This method adds the main output directory + * to the module-path if it contains a {@code module-info.class}, or to the class-path otherwise. + * For the test output directory, the rules are more complex and are governed by the fact that + * Java does not accept the placement of two modules of the same name on the module-path. + * So the modular test output directory usually needs to be placed in a {@code --path-module} option. + * + *
    + *
  • If the test output directory is modular, then: + *
      + *
    • If a test module name is identical to a main module name, + * place the test directory in a {@code --patch-module} option.
    • + *
    • Otherwise, place the test directory on the module-path. However, this case + * (a module existing only in test output, not in main output) should be uncommon.
    • + *
    + *
  • + *
  • Otherwise (test output contains no module information), then: + *
      + *
    • If the main output is on the module-path, place the test output + * on a {@code --patch-module} option.
    • + *
    • Otherwise (main output on the class-path), place the test output on the class-path too.
    • + *
    + *
  • + *
+ * + * This method must be invoked before {@link #addDependency(Node, Dependency, Predicate, Path, PathModularizationCache)} + * if output directories are desired on the class-path or module-path. + * This method can be invoked at most once. + * + * @param main the main output directory, or {@code null} if none + * @param test the test output directory, or {@code null} if none + * @param cache cache of module information about each dependency + * @throws IOException if an error occurred while reading module information + * + * TODO: this is currently not called + */ + void addOutputDirectory(final Path main, final Path test, final PathModularizationCache cache) throws IOException { + if (outputModules != null) { + throw new IllegalStateException("Output directories must be set first and only once."); + } + if (main != null) { + outputModules = cache.getModuleInfo(main); + addPathElement(outputModules.getPathType(), main); + } else { + outputModules = PathModularization.NONE; + } + if (test != null) { + boolean addToClasspath = true; + final PathModularization testModules = cache.getModuleInfo(test); + final boolean isModuleHierarchy = outputModules.isModuleHierarchy() || testModules.isModuleHierarchy(); + for (final String moduleName : outputModules.getModuleNames().values()) { + Path subdir = test; + if (isModuleHierarchy) { + // If module hierarchy is used, the directory names shall be the module names. + final Path path = test.resolve(moduleName); + if (!Files.isDirectory(path)) { + // Main module without tests. It is okay. + continue; + } + subdir = path; + } + // When the same module is found in main and test output, the latter is patching the former. + addPathElement(JavaPathType.patchModule(moduleName), subdir); + addToClasspath = false; + } + /* + * If the test output directory provides some modules of its own, add them. + * Except for this unusual case, tests should never be added to the module-path. + */ + for (final Map.Entry entry : + testModules.getModuleNames().entrySet()) { + if (!outputModules.containsModule(entry.getValue())) { + addPathElement(JavaPathType.MODULES, entry.getKey()); + addToClasspath = false; + } + } + if (addToClasspath) { + addPathElement(JavaPathType.CLASSES, test); + } + } + } + + /** + * Adds a dependency to the result. This method populates the {@link #nodes}, {@link #paths}, + * {@link #dispatchedPaths} and {@link #dependencies} collections with the given arguments. + * + * @param node the dependency node + * @param dep the dependency for the given node, or {@code null} if none + * @param filter filter the paths accepted by the tool which will consume the path. + * @param path the path to the dependency, or {@code null} if the dependency was null + * @param cache cache of module information about each dependency + * @throws IOException if an error occurred while reading module information + */ + void addDependency( + final Node node, + final Dependency dep, + final Predicate filter, + final Path path, + final PathModularizationCache cache) + throws IOException { + nodes.add(node); + if (dep == null) { + return; + } + if (dependencies.put(dep, path) != null) { + throw new IllegalStateException("Duplicated key: " + dep); + } + if (path == null) { + return; + } + paths.add(path); + /* + * Dispatch the dependency to class-path, module-path, patch-module path, etc. + * according the dependency properties. We need to process patch-module first, + * because this type depends on whether a module of the same name has already + * been added on the module-type. + */ + // final DependencyProperties properties = dep.getDependencyProperties(); + final Set pathTypes = dep.getType().getPathTypes(); + if (containsPatches(pathTypes)) { + if (outputModules == null) { + // For telling users that it is too late for setting the output directory. + outputModules = PathModularization.NONE; + } + PathType type = null; + for (Map.Entry info : + cache.getModuleInfo(path).getModuleNames().entrySet()) { + String moduleName = info.getValue(); + type = JavaPathType.patchModule(moduleName); + if (!containsModule(moduleName, cache)) { + /* + * Not patching an existing module. This case should be unusual. If it nevertheless + * happens, add on class-path or module-path if allowed, or keep patching otherwise. + * The latter case (keep patching) is okay if the main module will be defined later. + */ + type = cache.selectPathType(pathTypes, filter, path).orElse(type); + } + addPathElement(type, info.getKey()); + // There is usually no more than one element, but nevertheless allow multi-modules. + } + /* + * If the dependency has no module information, search for an artifact of the same groupId + * and artifactId. If one is found, we are patching that module. If none is found, add the + * dependency as a normal dependency. + */ + if (type == null) { + Path main = findArtifactPath(dep.getGroupId(), dep.getArtifactId()); + if (main != null) { + for (Map.Entry info : + cache.getModuleInfo(main).getModuleNames().entrySet()) { + type = JavaPathType.patchModule(info.getValue()); + addPathElement(type, info.getKey()); + // There is usually no more than one element, but nevertheless allow multi-modules. + } + } + } + if (type != null) { + return; // Dependency added, we are done. + } + } + cache.selectPathType(pathTypes, filter, path).ifPresent((type) -> addPathElement(type, path)); + } + + /** + * Returns whether the given set of path types contains at least one patch for a module. + */ + private boolean containsPatches(final Set types) { + for (PathType type : types) { + if (type instanceof JavaPathType.Modular) { + type = ((JavaPathType.Modular) type).rawType(); + } + if (JavaPathType.PATCH_MODULE.equals(type)) { + return true; + } + } + return false; + } + + /** + * Returns whether at least one previously added modular dependency contains a module of the given name. + * + * @param moduleName name of the module to search + * @param cache cache of module information about each dependency + */ + private boolean containsModule(final String moduleName, final PathModularizationCache cache) throws IOException { + for (Path path : dispatchedPaths.getOrDefault(JavaPathType.MODULES, Collections.emptyList())) { + if (cache.getModuleInfo(path).containsModule(moduleName)) { + return true; + } + } + return false; + } + + /** + * Searches an artifact of the given group and artifact identifiers, and returns its path + * + * @param group the group identifier to search + * @param artifact the artifact identifier to search + * @return path to the desired artifact, or {@code null} if not found + */ + private Path findArtifactPath(final String group, final String artifact) throws IOException { + for (final Map.Entry entry : dependencies.entrySet()) { + Dependency dep = entry.getKey(); + if (group.equals(dep.getGroupId()) && artifact.equals(dep.getArtifactId())) { + return entry.getValue(); + } + } + return null; + } + + @Override + public List getExceptions() { + return exceptions; + } + + @Override + public Node getRoot() { + return root; + } + + @Override + public List getNodes() { + return nodes; + } + + @Override + public List getPaths() { + return paths; + } + + @Override + public Map> getDispatchedPaths() { + return dispatchedPaths; + } + + @Override + public Map getDependencies() { + return dependencies; + } +} diff --git a/maven-core/src/main/java/org/apache/maven/internal/impl/DefaultPackagingRegistry.java b/maven-core/src/main/java/org/apache/maven/internal/impl/DefaultPackagingRegistry.java new file mode 100644 index 000000000000..2a5b8a63b53e --- /dev/null +++ b/maven-core/src/main/java/org/apache/maven/internal/impl/DefaultPackagingRegistry.java @@ -0,0 +1,73 @@ +/* + * 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.internal.impl; + +import javax.inject.Inject; +import javax.inject.Named; +import javax.inject.Singleton; + +import java.util.Map; +import java.util.Optional; + +import org.apache.maven.api.Packaging; +import org.apache.maven.api.Type; +import org.apache.maven.api.services.PackagingRegistry; +import org.apache.maven.api.services.TypeRegistry; +import org.apache.maven.lifecycle.mapping.LifecycleMapping; + +/** + * TODO: this is session scoped as SPI can contribute. + */ +@Named +@Singleton +public class DefaultPackagingRegistry implements PackagingRegistry { + private final Map lifecycleMappings; + + private final TypeRegistry typeRegistry; + + @Inject + public DefaultPackagingRegistry(Map lifecycleMappings, TypeRegistry typeRegistry) { + this.lifecycleMappings = lifecycleMappings; + this.typeRegistry = typeRegistry; + } + + @Override + public Optional lookup(String id) { + LifecycleMapping lifecycleMapping = lifecycleMappings.get(id); + if (lifecycleMapping == null) { + return Optional.empty(); + } + Type type = typeRegistry.lookup(id).orElse(null); + if (type == null) { + return Optional.empty(); + } + + return Optional.of(new Packaging() { + @Override + public String id() { + return id; + } + + @Override + public Type getType() { + return type; + } + }); + } +} diff --git a/maven-core/src/main/java/org/apache/maven/internal/impl/DefaultProject.java b/maven-core/src/main/java/org/apache/maven/internal/impl/DefaultProject.java index f438261aee5d..e2f59668eca8 100644 --- a/maven-core/src/main/java/org/apache/maven/internal/impl/DefaultProject.java +++ b/maven-core/src/main/java/org/apache/maven/internal/impl/DefaultProject.java @@ -27,7 +27,6 @@ import org.apache.maven.api.annotations.Nullable; import org.apache.maven.api.model.DependencyManagement; import org.apache.maven.api.model.Model; -import org.apache.maven.api.services.TypeRegistry; import org.apache.maven.project.MavenProject; import org.apache.maven.project.artifact.ProjectArtifact; import org.eclipse.aether.util.artifact.ArtifactIdUtils; @@ -38,10 +37,12 @@ public class DefaultProject implements Project { private final InternalSession session; private final MavenProject project; + private final Packaging packaging; public DefaultProject(InternalSession session, MavenProject project) { this.session = session; this.project = project; + this.packaging = session.requirePackaging(project.getPackaging()); } public InternalSession getSession() { @@ -86,8 +87,8 @@ public List getArtifacts() { @Nonnull @Override - public String getPackaging() { - return project.getPackaging(); + public Packaging getPackaging() { + return packaging; } @Nonnull @@ -175,13 +176,13 @@ public String getExtension() { @Override public Type getType() { String type = dependency.getType(); - return session.getService(TypeRegistry.class).getType(type); + return session.requireType(type); } @Nonnull @Override - public Scope getScope() { - return Scope.get(dependency.getScope()); + public DependencyScope getScope() { + return session.requireDependencyScope(dependency.getScope()); } @Override diff --git a/maven-core/src/main/java/org/apache/maven/internal/impl/DefaultType.java b/maven-core/src/main/java/org/apache/maven/internal/impl/DefaultType.java index 6297f5c4dde0..cc5534f4cdb1 100644 --- a/maven-core/src/main/java/org/apache/maven/internal/impl/DefaultType.java +++ b/maven-core/src/main/java/org/apache/maven/internal/impl/DefaultType.java @@ -18,10 +18,12 @@ */ package org.apache.maven.internal.impl; -import java.util.HashMap; +import java.util.*; import java.util.Map; -import org.apache.maven.api.DependencyProperties; +import org.apache.maven.api.JavaPathType; +import org.apache.maven.api.Language; +import org.apache.maven.api.PathType; import org.apache.maven.api.Type; import org.eclipse.aether.artifact.ArtifactProperties; import org.eclipse.aether.artifact.ArtifactType; @@ -29,37 +31,44 @@ import static org.apache.maven.internal.impl.Utils.nonNull; public class DefaultType implements Type, ArtifactType { + private final String id; + + private final Language language; + private final String extension; private final String classifier; - - private final DependencyProperties dependencyProperties; + private final boolean includesDependencies; + private final Set pathTypes; public DefaultType( String id, - String language, + Language language, String extension, String classifier, - DependencyProperties dependencyProperties) { - nonNull(id, "id"); - nonNull(language, "language"); + boolean includesDependencies, + PathType... pathTypes) { + this.id = nonNull(id, "id"); + this.language = nonNull(language, "language"); this.extension = nonNull(extension, "extension"); this.classifier = classifier; - nonNull(dependencyProperties, "dependencyProperties"); - HashMap props = new HashMap<>(dependencyProperties.asMap()); - props.put(ArtifactProperties.TYPE, id); - props.put(ArtifactProperties.LANGUAGE, language); - this.dependencyProperties = new DefaultDependencyProperties(props); + this.includesDependencies = includesDependencies; + this.pathTypes = Collections.unmodifiableSet(new HashSet<>(Arrays.asList(pathTypes))); + } + + @Override + public String id() { + return id; } @Override public String getId() { - return dependencyProperties.asMap().get(ArtifactProperties.TYPE); + return id(); } @Override - public String getLanguage() { - return dependencyProperties.asMap().get(ArtifactProperties.LANGUAGE); + public Language getLanguage() { + return language; } @Override @@ -73,12 +82,22 @@ public String getClassifier() { } @Override - public DependencyProperties getDependencyProperties() { - return dependencyProperties; + public boolean isIncludesDependencies() { + return this.includesDependencies; + } + + public Set getPathTypes() { + return this.pathTypes; } @Override public Map getProperties() { - return getDependencyProperties().asMap(); + Map properties = new HashMap<>(); + properties.put(ArtifactProperties.TYPE, this.id); + properties.put(ArtifactProperties.LANGUAGE, this.language.id()); + properties.put(ArtifactProperties.INCLUDES_DEPENDENCIES, String.valueOf(includesDependencies)); + properties.put( + ArtifactProperties.CONSTITUTES_BUILD_PATH, String.valueOf(pathTypes.contains(JavaPathType.CLASSES))); + return Collections.unmodifiableMap(properties); } } diff --git a/maven-core/src/main/java/org/apache/maven/internal/impl/DefaultTypeRegistry.java b/maven-core/src/main/java/org/apache/maven/internal/impl/DefaultTypeRegistry.java index cebd30cc666a..0a74fa7f0489 100644 --- a/maven-core/src/main/java/org/apache/maven/internal/impl/DefaultTypeRegistry.java +++ b/maven-core/src/main/java/org/apache/maven/internal/impl/DefaultTypeRegistry.java @@ -22,13 +22,13 @@ import javax.inject.Named; import javax.inject.Singleton; -import java.util.ArrayList; import java.util.Map; +import java.util.Optional; import java.util.concurrent.ConcurrentHashMap; -import org.apache.maven.api.DependencyProperties; import org.apache.maven.api.Type; import org.apache.maven.api.annotations.Nonnull; +import org.apache.maven.api.services.LanguageRegistry; import org.apache.maven.api.services.TypeRegistry; import org.apache.maven.artifact.handler.ArtifactHandler; import org.apache.maven.artifact.handler.manager.LegacyArtifactHandlerManager; @@ -42,6 +42,8 @@ public class DefaultTypeRegistry extends AbstractEventSpy implements TypeRegistry { private final Map types; + private final LanguageRegistry languageRegistry; + private final ConcurrentHashMap usedTypes; private final ConcurrentHashMap legacyTypes; @@ -49,8 +51,10 @@ public class DefaultTypeRegistry extends AbstractEventSpy implements TypeRegistr private final LegacyArtifactHandlerManager manager; @Inject - public DefaultTypeRegistry(Map types, LegacyArtifactHandlerManager manager) { + public DefaultTypeRegistry( + Map types, LanguageRegistry languageRegistry, LegacyArtifactHandlerManager manager) { this.types = nonNull(types, "types"); + this.languageRegistry = nonNull(languageRegistry, "languageRegistry"); this.usedTypes = new ConcurrentHashMap<>(); this.legacyTypes = new ConcurrentHashMap<>(); this.manager = nonNull(manager, "artifactHandlerManager"); @@ -67,9 +71,14 @@ public void onEvent(Object event) { } } + @Override + public Optional lookup(String id) { + return Optional.of(require(id)); + } + @Override @Nonnull - public Type getType(String id) { + public Type require(String id) { nonNull(id, "id"); return usedTypes.computeIfAbsent(id, i -> { Type type = types.get(id); @@ -78,19 +87,14 @@ public Type getType(String id) { type = legacyTypes.computeIfAbsent(id, k -> { // Copy data as the ArtifactHandler is not immutable, but Type should be. ArtifactHandler handler = manager.getArtifactHandler(id); - ArrayList flags = new ArrayList<>(); - if (handler.isAddedToClasspath()) { - flags.add(DependencyProperties.FLAG_CLASS_PATH_CONSTITUENT); - } - if (handler.isIncludesDependencies()) { - flags.add(DependencyProperties.FLAG_INCLUDES_DEPENDENCIES); - } return new DefaultType( id, - handler.getLanguage(), + languageRegistry.require(handler.getLanguage()), handler.getExtension(), handler.getClassifier(), - new DefaultDependencyProperties(flags)); + handler.isIncludesDependencies() + // TODO: add path types + ); }); } return type; diff --git a/maven-core/src/main/java/org/apache/maven/internal/impl/ExtensibleEnumRegistries.java b/maven-core/src/main/java/org/apache/maven/internal/impl/ExtensibleEnumRegistries.java new file mode 100644 index 000000000000..d512e74b4635 --- /dev/null +++ b/maven-core/src/main/java/org/apache/maven/internal/impl/ExtensibleEnumRegistries.java @@ -0,0 +1,92 @@ +/* + * 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.internal.impl; + +import javax.inject.Inject; +import javax.inject.Named; +import javax.inject.Singleton; + +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +import org.apache.maven.SessionScoped; +import org.apache.maven.api.*; +import org.apache.maven.api.services.*; +import org.apache.maven.api.spi.*; + +public class ExtensibleEnumRegistries { + + @Named + @SessionScoped + public static class DefaultPathScopeRegistry extends DefaultExtensibleEnumRegistry + implements PathScopeRegistry { + + @Inject + public DefaultPathScopeRegistry(List providers) { + super( + providers, + PathScope.MAIN_COMPILE, + PathScope.MAIN_RUNTIME, + PathScope.TEST_COMPILE, + PathScope.TEST_RUNTIME); + } + } + + @Named + @SessionScoped + public static class DefaultProjectScopeRegistry + extends DefaultExtensibleEnumRegistry implements ProjectScopeRegistry { + + @Inject + public DefaultProjectScopeRegistry(List providers) { + super(providers, ProjectScope.MAIN, ProjectScope.TEST); + } + } + + @Named + @Singleton + public static class DefaultLanguageRegistry extends DefaultExtensibleEnumRegistry + implements LanguageRegistry { + + @Inject + public DefaultLanguageRegistry(List providers) { + super(providers, Language.NONE, Language.JAVA_FAMILY); + } + } + + static class DefaultExtensibleEnumRegistry> + implements ExtensibleEnumRegistry { + + private final Map values; + + DefaultExtensibleEnumRegistry(List

providers, T... builtinValues) { + values = Stream.concat( + Stream.of(builtinValues), providers.stream().flatMap(p -> p.provides().stream())) + .collect(Collectors.toMap(t -> t.id(), t -> t)); + } + + @Override + public Optional lookup(String id) { + return Optional.ofNullable(values.get(id)); + } + } +} diff --git a/maven-core/src/main/java/org/apache/maven/internal/impl/PathModularization.java b/maven-core/src/main/java/org/apache/maven/internal/impl/PathModularization.java new file mode 100644 index 000000000000..631f8344c254 --- /dev/null +++ b/maven-core/src/main/java/org/apache/maven/internal/impl/PathModularization.java @@ -0,0 +1,265 @@ +/* + * 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.internal.impl; + +import java.io.IOException; +import java.io.InputStream; +import java.io.UncheckedIOException; +import java.lang.module.ModuleDescriptor; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; +import java.util.jar.Attributes; +import java.util.jar.JarFile; +import java.util.jar.Manifest; +import java.util.stream.Stream; +import java.util.zip.ZipEntry; + +import org.apache.maven.api.JavaPathType; +import org.apache.maven.api.annotations.Nonnull; + +/** + * Information about the modules contained in a path element. + * The path element may be a JAR file or a directory. Directories may use either package hierarchy + * or module hierarchy, but not module source hierarchy. The latter is excluded because this class + * is for path elements of compiled codes. + */ +final class PathModularization { + /** + * A unique constant for all non-modular dependencies. + */ + public static final PathModularization NONE = new PathModularization(); + + /** + * Name of the file to use as a sentinel value for deciding if a directory or a JAR is modular. + */ + private static final String MODULE_INFO = "module-info.class"; + + /** + * The attribute for automatic module name in {@code META-INF/MANIFEST.MF} files. + */ + private static final Attributes.Name AUTO_MODULE_NAME = new Attributes.Name("Automatic-Module-Name"); + + /** + * Module information for the path specified at construction time. + * This map is usually either empty if no module was found, or a singleton map. + * It may however contain more than one entry if module hierarchy was detected, + * in which case there is one key per sub-directory. + * + *

This map may contain null values if the constructor was invoked with {@code resolve} + * parameter set to false. This is more efficient when only the module existence needs to + * be tested, and module descriptors are not needed.

+ * + * @see #getModuleNames() + */ + private final Map descriptors; + + /** + * Whether module hierarchy was detected. If false, then package hierarchy is assumed. + * In a package hierarchy, the {@linkplain #descriptors} map has either zero or one entry. + * In a module hierarchy, the descriptors map may have an arbitrary number of entries, + * including one (so the map size cannot be used as a criterion). + * + * @see #isModuleHierarchy() + */ + private final boolean isModuleHierarchy; + + /** + * Constructs an empty instance for non-modular dependencies. + * + * @see #NONE + */ + private PathModularization() { + descriptors = Collections.emptyMap(); + isModuleHierarchy = false; + } + + /** + * Finds module information in the given JAR file, output directory, or test output directory. + * If no module is found, or if module information cannot be extracted, then this constructor + * builds an empty map. + * + *

If the {@code resolve} parameter value is {@code false}, then some or all map values may + * be null instead of the actual module name. This option can avoid the cost of reading module + * descriptors when only the modules existence needs to be verified.

+ * + *

Algorithm: + * If the given path is a directory, then there is a choice: + *

+ *
    + *
  • Package hierarchy: if a {@code module-info.class} file is found at the root, + * then builds a singleton map with the module name declared in that descriptor.
  • + *
  • Module hierarchy: if {@code module-info.class} files are found in sub-directories, + * at a deep intentionally restricted to one level, then builds a map of module names found + * in the descriptor of each sub-directory.
  • + *
+ * + * Otherwise if the given path is a JAR file, then there is a choice: + *
    + *
  • If a {@code module-info.class} file is found in the root directory or in a + * {@code "META-INF/versions/{n}/"} subdirectory, builds a singleton map with + * the module name declared in that descriptor.
  • + *
  • Otherwise if an {@code "Automatic-Module-Name"} attribute is declared in the + * {@code META-INF/MANIFEST.MF} file, builds a singleton map with the value of that attribute.
  • + *
+ * + * Otherwise builds an empty map. + * + * @param path directory or JAR file to test + * @param resolve whether the module names are requested. If false, null values may be used instead + * @throws IOException if an error occurred while reading the JAR file or the module descriptor + */ + PathModularization(final Path path, final boolean resolve) throws IOException { + if (Files.isDirectory(path)) { + /* + * Package hierarchy: only one module with descriptor at the root. + * This is the layout of output directories in projects using the + * classical (Java 8 and before) way to organize source files. + */ + Path file = path.resolve(MODULE_INFO); + if (Files.isRegularFile(file)) { + String name = null; + if (resolve) { + try (InputStream in = Files.newInputStream(file)) { + name = getModuleName(in); + } + } + descriptors = Collections.singletonMap(file, name); + isModuleHierarchy = false; + return; + } + /* + * Module hierarchy: many modules, one per directory, with descriptor at the root of the sub-directory. + * This is the layout of output directories in projects using the new (Java 9 and later) way to organize + * source files. + */ + if (Files.isDirectory(file)) { + final Map names = new HashMap<>(); + try (Stream subdirs = Files.list(file)) { + subdirs.filter(Files::isDirectory).forEach((subdir) -> { + Path mf = subdir.resolve(MODULE_INFO); + if (Files.isRegularFile(mf)) { + String name = null; + if (resolve) { + try (InputStream in = Files.newInputStream(mf)) { + name = getModuleName(in); + } catch (IOException e) { + throw new UncheckedIOException(e); + } + } + names.put(mf, name); + } + }); + } catch (UncheckedIOException e) { + throw e.getCause(); + } + if (!names.isEmpty()) { + descriptors = Collections.unmodifiableMap(names); + isModuleHierarchy = true; + return; + } + } + } else if (Files.isRegularFile(path)) { + /* + * JAR file: can contain only one module, with descriptor at the root. + * If no descriptor, the "Automatic-Module-Name" manifest attribute is + * taken as a fallback. + */ + try (JarFile jar = new JarFile(path.toFile())) { + final ZipEntry entry = jar.getEntry(MODULE_INFO); + if (entry != null) { + String name = null; + if (resolve) { + try (InputStream in = jar.getInputStream(entry)) { + name = getModuleName(in); + } + } + descriptors = Collections.singletonMap(path, name); + isModuleHierarchy = false; + return; + } + // No module descriptor, check manifest file. + final Manifest mf = jar.getManifest(); + if (mf != null) { + final Object name = mf.getMainAttributes().get(AUTO_MODULE_NAME); + if (name instanceof String) { + descriptors = Collections.singletonMap(path, (String) name); + isModuleHierarchy = false; + return; + } + } + } + } + descriptors = Collections.emptyMap(); + isModuleHierarchy = false; + } + + /** + * Returns the module name declared in the given {@code module-info} descriptor. + * The input stream may be for a file or for an entry in a JAR file. + */ + @Nonnull + private static String getModuleName(final InputStream in) throws IOException { + return ModuleDescriptor.read(in).name(); + } + + /** + * Returns the type of path detected. The return value is {@link JavaPathType#MODULES} + * if the dependency is a modular JAR file or a directory containing module descriptor(s), + * or {@link JavaPathType#CLASSES} otherwise. A JAR file without module descriptor but with + * an "Automatic-Module-Name" manifest attribute is considered modular. + */ + public JavaPathType getPathType() { + return descriptors.isEmpty() ? JavaPathType.CLASSES : JavaPathType.MODULES; + } + + /** + * Returns whether module hierarchy was detected. If false, then package hierarchy is assumed. + * In a package hierarchy, the {@linkplain #getModuleNames()} map of modules has either zero or one entry. + * In a module hierarchy, the descriptors map may have an arbitrary number of entries, + * including one (so the map size cannot be used as a criterion). + */ + public boolean isModuleHierarchy() { + return isModuleHierarchy; + } + + /** + * Returns the module names for the path specified at construction time. + * This map is usually either empty if no module was found, or a singleton map. + * It may however contain more than one entry if module hierarchy was detected, + * in which case there is one key per sub-directory. + * + *

This map may contain null values if the constructor was invoked with {@code resolve} + * parameter set to false. This is more efficient when only the module existence needs to + * be tested, and module descriptors are not needed.

+ */ + @Nonnull + public Map getModuleNames() { + return descriptors; + } + + /** + * Returns whether the dependency contains a module of the given name. + */ + public boolean containsModule(final String name) { + return descriptors.containsValue(name); + } +} diff --git a/maven-core/src/main/java/org/apache/maven/internal/impl/PathModularizationCache.java b/maven-core/src/main/java/org/apache/maven/internal/impl/PathModularizationCache.java new file mode 100644 index 000000000000..d123205bb02e --- /dev/null +++ b/maven-core/src/main/java/org/apache/maven/internal/impl/PathModularizationCache.java @@ -0,0 +1,135 @@ +/* + * 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.internal.impl; + +import java.io.IOException; +import java.nio.file.Path; +import java.util.HashMap; +import java.util.Map; +import java.util.Optional; +import java.util.Set; +import java.util.function.Predicate; + +import org.apache.maven.api.JavaPathType; +import org.apache.maven.api.PathType; + +/** + * Cache of {@link PathModularization} instances computed for given {@link Path} elements. + * The cache is used for avoiding the need to reopen the same files many times when the + * same dependency is used for different scope. For example a path used for compilation + * is typically also used for tests. + */ +final class PathModularizationCache { + /** + * Module information for each JAR file or output directories. + * Cached when first requested to avoid decoding the module descriptors multiple times. + * + * @see #getModuleInfo(Path) + */ + private final Map moduleInfo; + + /** + * Whether JAR files are modular. This map is redundant with {@link #moduleInfo}, + * but cheaper to compute when the module names are not needed. + * + * @see #getPathType(Path) + */ + private final Map pathTypes; + + /** + * Creates an initially empty cache. + */ + PathModularizationCache() { + moduleInfo = new HashMap<>(); + pathTypes = new HashMap<>(); + } + + /** + * Gets module information for the given JAR file or output directory. + * Module descriptors are read when first requested, then cached. + */ + PathModularization getModuleInfo(final Path path) throws IOException { + PathModularization info = moduleInfo.get(path); + if (info == null) { + info = new PathModularization(path, true); + moduleInfo.put(path, info); + pathTypes.put(path, info.getPathType()); + } + return info; + } + + /** + * Returns {@link JavaPathType#MODULES} if the given JAR file or output directory is modular. + * This is used in heuristic rules for deciding whether to place a dependency on the class-path + * or on the module-path when the {@code "jar"} artifact type is used. + */ + private PathType getPathType(final Path path) throws IOException { + PathType type = pathTypes.get(path); + if (type == null) { + type = new PathModularization(path, false).getPathType(); + pathTypes.put(path, type); + } + return type; + } + + /** + * Selects the type of path where to place the given dependency. + * This method returns one of the values specified in the given array. + * This method does not handle the patch-module paths, because the patches + * depend on which modules have been previously added on the module-paths. + * + *

If the dependency can be a constituent of both the class-path and the module-path, + * then the path type is determined by checking if the dependency is modular.

+ * + * @param types types of path where a dependency can be placed + * @param filter filter the paths accepted by the tool which will consume the path + * @param path path to the JAR file or output directory of the dependency + * @return where to place the dependency, or an empty value if the placement cannot be determined + * @throws IOException if an error occurred while reading module information + */ + Optional selectPathType(final Set types, final Predicate filter, final Path path) + throws IOException { + PathType selected = null; + boolean classes = false; + boolean modules = false; + boolean unknown = false; + for (PathType type : types) { + if (filter.test(type)) { + if (JavaPathType.CLASSES.equals(type)) { + classes = true; + } else if (JavaPathType.MODULES.equals(type)) { + modules = true; + } else { + unknown = true; + } + if (selected == null) { + selected = type; + } else if (unknown) { + // More than one filtered value, and we don't know how to handle at least one of them. + // TODO: add a plugin mechanism for allowing plugin to specify their selection algorithm. + return Optional.empty(); + } + } + } + if (classes & modules) { + selected = getPathType(path); + } + return Optional.ofNullable(selected); + } +} diff --git a/maven-core/src/main/java/org/apache/maven/internal/impl/types/BomTypeProvider.java b/maven-core/src/main/java/org/apache/maven/internal/impl/types/BomTypeProvider.java index d7387a1780b2..fd03cf821918 100644 --- a/maven-core/src/main/java/org/apache/maven/internal/impl/types/BomTypeProvider.java +++ b/maven-core/src/main/java/org/apache/maven/internal/impl/types/BomTypeProvider.java @@ -22,8 +22,8 @@ import javax.inject.Provider; import javax.inject.Singleton; +import org.apache.maven.api.Language; import org.apache.maven.api.Type; -import org.apache.maven.internal.impl.DefaultDependencyProperties; import org.apache.maven.internal.impl.DefaultType; @Named(BomTypeProvider.NAME) @@ -34,7 +34,7 @@ public class BomTypeProvider implements Provider { private final Type type; public BomTypeProvider() { - this.type = new DefaultType(NAME, Type.LANGUAGE_NONE, "pom", null, new DefaultDependencyProperties()); + this.type = new DefaultType(NAME, Language.NONE, "pom", null, false); } @Override diff --git a/maven-core/src/main/java/org/apache/maven/internal/impl/types/ClasspathJarTypeProvider.java b/maven-core/src/main/java/org/apache/maven/internal/impl/types/ClasspathJarTypeProvider.java new file mode 100644 index 000000000000..e8836978249a --- /dev/null +++ b/maven-core/src/main/java/org/apache/maven/internal/impl/types/ClasspathJarTypeProvider.java @@ -0,0 +1,51 @@ +/* + * 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.internal.impl.types; + +import javax.inject.Named; +import javax.inject.Provider; +import javax.inject.Singleton; + +import org.apache.maven.api.JavaPathType; +import org.apache.maven.api.Language; +import org.apache.maven.api.Type; +import org.apache.maven.internal.impl.DefaultType; + +/** + * Type provider for a JAR file to unconditionally place on the class-path. + * Dependencies of this type are class-path constituents only. + * + * @see Type#CLASSPATH_JAR + */ +@Named(ClasspathJarTypeProvider.NAME) +@Singleton +public class ClasspathJarTypeProvider implements Provider { + public static final String NAME = Type.CLASSPATH_JAR; + + private final Type type; + + public ClasspathJarTypeProvider() { + this.type = new DefaultType(NAME, Language.JAVA_FAMILY, "jar", null, false, JavaPathType.CLASSES); + } + + @Override + public Type get() { + return type; + } +} diff --git a/maven-core/src/main/java/org/apache/maven/internal/impl/types/EarTypeProvider.java b/maven-core/src/main/java/org/apache/maven/internal/impl/types/EarTypeProvider.java index 489ed638bb53..d8c319b04b6a 100644 --- a/maven-core/src/main/java/org/apache/maven/internal/impl/types/EarTypeProvider.java +++ b/maven-core/src/main/java/org/apache/maven/internal/impl/types/EarTypeProvider.java @@ -22,9 +22,8 @@ import javax.inject.Provider; import javax.inject.Singleton; -import org.apache.maven.api.DependencyProperties; +import org.apache.maven.api.Language; import org.apache.maven.api.Type; -import org.apache.maven.internal.impl.DefaultDependencyProperties; import org.apache.maven.internal.impl.DefaultType; @Named(EarTypeProvider.NAME) @@ -35,12 +34,7 @@ public class EarTypeProvider implements Provider { private final Type type; public EarTypeProvider() { - this.type = new DefaultType( - NAME, - Type.LANGUAGE_JAVA, - "ear", - null, - new DefaultDependencyProperties(DependencyProperties.FLAG_INCLUDES_DEPENDENCIES)); + this.type = new DefaultType(NAME, Language.JAVA_FAMILY, "ear", null, true); } @Override diff --git a/maven-core/src/main/java/org/apache/maven/internal/impl/types/EjbClientTypeProvider.java b/maven-core/src/main/java/org/apache/maven/internal/impl/types/EjbClientTypeProvider.java index efbfe05a1cc0..57e678cea91a 100644 --- a/maven-core/src/main/java/org/apache/maven/internal/impl/types/EjbClientTypeProvider.java +++ b/maven-core/src/main/java/org/apache/maven/internal/impl/types/EjbClientTypeProvider.java @@ -22,9 +22,9 @@ import javax.inject.Provider; import javax.inject.Singleton; -import org.apache.maven.api.DependencyProperties; +import org.apache.maven.api.JavaPathType; +import org.apache.maven.api.Language; import org.apache.maven.api.Type; -import org.apache.maven.internal.impl.DefaultDependencyProperties; import org.apache.maven.internal.impl.DefaultType; @Named(EjbClientTypeProvider.NAME) @@ -35,12 +35,7 @@ public class EjbClientTypeProvider implements Provider { private final Type type; public EjbClientTypeProvider() { - this.type = new DefaultType( - NAME, - Type.LANGUAGE_JAVA, - "jar", - "client", - new DefaultDependencyProperties(DependencyProperties.FLAG_CLASS_PATH_CONSTITUENT)); + this.type = new DefaultType(NAME, Language.JAVA_FAMILY, "jar", "client", false, JavaPathType.CLASSES); } @Override diff --git a/maven-core/src/main/java/org/apache/maven/internal/impl/types/EjbTypeProvider.java b/maven-core/src/main/java/org/apache/maven/internal/impl/types/EjbTypeProvider.java index e6f8fbdb4b5e..d58890bd76f3 100644 --- a/maven-core/src/main/java/org/apache/maven/internal/impl/types/EjbTypeProvider.java +++ b/maven-core/src/main/java/org/apache/maven/internal/impl/types/EjbTypeProvider.java @@ -22,9 +22,9 @@ import javax.inject.Provider; import javax.inject.Singleton; -import org.apache.maven.api.DependencyProperties; +import org.apache.maven.api.JavaPathType; +import org.apache.maven.api.Language; import org.apache.maven.api.Type; -import org.apache.maven.internal.impl.DefaultDependencyProperties; import org.apache.maven.internal.impl.DefaultType; @Named(EjbTypeProvider.NAME) @@ -35,12 +35,7 @@ public class EjbTypeProvider implements Provider { private final Type type; public EjbTypeProvider() { - this.type = new DefaultType( - NAME, - Type.LANGUAGE_JAVA, - "jar", - null, - new DefaultDependencyProperties(DependencyProperties.FLAG_CLASS_PATH_CONSTITUENT)); + this.type = new DefaultType(NAME, Language.JAVA_FAMILY, "jar", null, false, JavaPathType.CLASSES); } @Override diff --git a/maven-core/src/main/java/org/apache/maven/internal/impl/types/JarTypeProvider.java b/maven-core/src/main/java/org/apache/maven/internal/impl/types/JarTypeProvider.java index 974c34e0ed97..a0737f403fe6 100644 --- a/maven-core/src/main/java/org/apache/maven/internal/impl/types/JarTypeProvider.java +++ b/maven-core/src/main/java/org/apache/maven/internal/impl/types/JarTypeProvider.java @@ -22,25 +22,29 @@ import javax.inject.Provider; import javax.inject.Singleton; -import org.apache.maven.api.DependencyProperties; +import org.apache.maven.api.JavaPathType; +import org.apache.maven.api.Language; import org.apache.maven.api.Type; -import org.apache.maven.internal.impl.DefaultDependencyProperties; import org.apache.maven.internal.impl.DefaultType; +/** + * Type provider for a JAR file that can be placed either on the class-path or on the module-path. + * Dependencies of this type are class-path constituents and module-path constituents. + * Only one of those constituents shall be effective for any given dependency. + * The choice may depend on heuristic rules. + * + * @see Type#JAR + */ @Named(JarTypeProvider.NAME) @Singleton public class JarTypeProvider implements Provider { - public static final String NAME = "jar"; + public static final String NAME = Type.JAR; private final Type type; public JarTypeProvider() { this.type = new DefaultType( - NAME, - Type.LANGUAGE_JAVA, - "jar", - null, - new DefaultDependencyProperties(DependencyProperties.FLAG_CLASS_PATH_CONSTITUENT)); + NAME, Language.JAVA_FAMILY, "jar", null, false, JavaPathType.CLASSES, JavaPathType.MODULES); } @Override diff --git a/maven-core/src/main/java/org/apache/maven/internal/impl/types/JavaSourceTypeProvider.java b/maven-core/src/main/java/org/apache/maven/internal/impl/types/JavaSourceTypeProvider.java index bef8acf716fd..10dfee272183 100644 --- a/maven-core/src/main/java/org/apache/maven/internal/impl/types/JavaSourceTypeProvider.java +++ b/maven-core/src/main/java/org/apache/maven/internal/impl/types/JavaSourceTypeProvider.java @@ -22,19 +22,24 @@ import javax.inject.Provider; import javax.inject.Singleton; +import org.apache.maven.api.Language; import org.apache.maven.api.Type; -import org.apache.maven.internal.impl.DefaultDependencyProperties; import org.apache.maven.internal.impl.DefaultType; +/** + * Type provider for source code packaged in a JAR file. + * + * @see Type#JAVA_SOURCE + */ @Named(JavaSourceTypeProvider.NAME) @Singleton public class JavaSourceTypeProvider implements Provider { - public static final String NAME = "java-source"; + public static final String NAME = Type.JAVA_SOURCE; private final Type type; public JavaSourceTypeProvider() { - this.type = new DefaultType(NAME, Type.LANGUAGE_JAVA, "jar", "sources", new DefaultDependencyProperties()); + this.type = new DefaultType(NAME, Language.JAVA_FAMILY, "jar", "sources", false); } @Override diff --git a/maven-core/src/main/java/org/apache/maven/internal/impl/types/JavadocTypeProvider.java b/maven-core/src/main/java/org/apache/maven/internal/impl/types/JavadocTypeProvider.java index 1ccac4f04b49..99f4fb179400 100644 --- a/maven-core/src/main/java/org/apache/maven/internal/impl/types/JavadocTypeProvider.java +++ b/maven-core/src/main/java/org/apache/maven/internal/impl/types/JavadocTypeProvider.java @@ -22,25 +22,25 @@ import javax.inject.Provider; import javax.inject.Singleton; -import org.apache.maven.api.DependencyProperties; +import org.apache.maven.api.JavaPathType; +import org.apache.maven.api.Language; import org.apache.maven.api.Type; -import org.apache.maven.internal.impl.DefaultDependencyProperties; import org.apache.maven.internal.impl.DefaultType; +/** + * Type provider for javadoc packaged in a JAR file. + * + * @see Type#JAVADOC + */ @Named(JavadocTypeProvider.NAME) @Singleton public class JavadocTypeProvider implements Provider { - public static final String NAME = "javadoc"; + public static final String NAME = Type.JAVADOC; private final Type type; public JavadocTypeProvider() { - this.type = new DefaultType( - NAME, - Type.LANGUAGE_JAVA, - "jar", - "javadoc", - new DefaultDependencyProperties(DependencyProperties.FLAG_CLASS_PATH_CONSTITUENT)); + this.type = new DefaultType(NAME, Language.JAVA_FAMILY, "jar", "javadoc", false, JavaPathType.CLASSES); } @Override diff --git a/maven-core/src/main/java/org/apache/maven/internal/impl/types/MavenPluginTypeProvider.java b/maven-core/src/main/java/org/apache/maven/internal/impl/types/MavenPluginTypeProvider.java index bfdb424c2786..bef3837cc3ef 100644 --- a/maven-core/src/main/java/org/apache/maven/internal/impl/types/MavenPluginTypeProvider.java +++ b/maven-core/src/main/java/org/apache/maven/internal/impl/types/MavenPluginTypeProvider.java @@ -22,25 +22,25 @@ import javax.inject.Provider; import javax.inject.Singleton; -import org.apache.maven.api.DependencyProperties; +import org.apache.maven.api.JavaPathType; +import org.apache.maven.api.Language; import org.apache.maven.api.Type; -import org.apache.maven.internal.impl.DefaultDependencyProperties; import org.apache.maven.internal.impl.DefaultType; +/** + * Type provider for a Maven plugin. + * + * @see Type#MAVEN_PLUGIN + */ @Named(MavenPluginTypeProvider.NAME) @Singleton public class MavenPluginTypeProvider implements Provider { - public static final String NAME = "maven-plugin"; + public static final String NAME = Type.MAVEN_PLUGIN; private final Type type; public MavenPluginTypeProvider() { - this.type = new DefaultType( - NAME, - Type.LANGUAGE_JAVA, - "jar", - null, - new DefaultDependencyProperties(DependencyProperties.FLAG_CLASS_PATH_CONSTITUENT)); + this.type = new DefaultType(NAME, Language.JAVA_FAMILY, "jar", null, false, JavaPathType.CLASSES); } @Override diff --git a/maven-core/src/main/java/org/apache/maven/internal/impl/types/ModularJarTypeProvider.java b/maven-core/src/main/java/org/apache/maven/internal/impl/types/ModularJarTypeProvider.java new file mode 100644 index 000000000000..9dfa5a0df54a --- /dev/null +++ b/maven-core/src/main/java/org/apache/maven/internal/impl/types/ModularJarTypeProvider.java @@ -0,0 +1,51 @@ +/* + * 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.internal.impl.types; + +import javax.inject.Named; +import javax.inject.Provider; +import javax.inject.Singleton; + +import org.apache.maven.api.JavaPathType; +import org.apache.maven.api.Language; +import org.apache.maven.api.Type; +import org.apache.maven.internal.impl.DefaultType; + +/** + * Type provider for a JAR file to unconditionally place on the module-path. + * Dependencies of this type are module-path constituents only. + * + * @see Type#MODULAR_JAR + */ +@Named(ModularJarTypeProvider.NAME) +@Singleton +public class ModularJarTypeProvider implements Provider { + public static final String NAME = Type.MODULAR_JAR; + + private final Type type; + + public ModularJarTypeProvider() { + this.type = new DefaultType(NAME, Language.JAVA_FAMILY, "jar", null, false, JavaPathType.MODULES); + } + + @Override + public Type get() { + return type; + } +} diff --git a/maven-core/src/main/java/org/apache/maven/internal/impl/types/ParTypeProvider.java b/maven-core/src/main/java/org/apache/maven/internal/impl/types/ParTypeProvider.java index 91db9ba182da..1f51178991a5 100644 --- a/maven-core/src/main/java/org/apache/maven/internal/impl/types/ParTypeProvider.java +++ b/maven-core/src/main/java/org/apache/maven/internal/impl/types/ParTypeProvider.java @@ -22,9 +22,8 @@ import javax.inject.Provider; import javax.inject.Singleton; -import org.apache.maven.api.DependencyProperties; +import org.apache.maven.api.Language; import org.apache.maven.api.Type; -import org.apache.maven.internal.impl.DefaultDependencyProperties; import org.apache.maven.internal.impl.DefaultType; @Named(ParTypeProvider.NAME) @@ -35,12 +34,7 @@ public class ParTypeProvider implements Provider { private final Type type; public ParTypeProvider() { - this.type = new DefaultType( - NAME, - Type.LANGUAGE_JAVA, - "par", - null, - new DefaultDependencyProperties(DependencyProperties.FLAG_INCLUDES_DEPENDENCIES)); + this.type = new DefaultType(NAME, Language.JAVA_FAMILY, "par", null, true); } @Override diff --git a/maven-core/src/main/java/org/apache/maven/internal/impl/types/PomTypeProvider.java b/maven-core/src/main/java/org/apache/maven/internal/impl/types/PomTypeProvider.java index fe0f9705d0ea..7b52f299a2f9 100644 --- a/maven-core/src/main/java/org/apache/maven/internal/impl/types/PomTypeProvider.java +++ b/maven-core/src/main/java/org/apache/maven/internal/impl/types/PomTypeProvider.java @@ -22,19 +22,24 @@ import javax.inject.Provider; import javax.inject.Singleton; +import org.apache.maven.api.Language; import org.apache.maven.api.Type; -import org.apache.maven.internal.impl.DefaultDependencyProperties; import org.apache.maven.internal.impl.DefaultType; +/** + * Type provider for a POM file. + * + * @see Type#POM + */ @Named(PomTypeProvider.NAME) @Singleton public class PomTypeProvider implements Provider { - public static final String NAME = "pom"; + public static final String NAME = Type.POM; private final Type type; public PomTypeProvider() { - this.type = new DefaultType(NAME, Type.LANGUAGE_NONE, "pom", null, new DefaultDependencyProperties()); + this.type = new DefaultType(NAME, Language.NONE, "pom", null, false); } @Override diff --git a/maven-core/src/main/java/org/apache/maven/internal/impl/types/RarTypeProvider.java b/maven-core/src/main/java/org/apache/maven/internal/impl/types/RarTypeProvider.java index ad5accd3cd0c..3dd81a68bb4a 100644 --- a/maven-core/src/main/java/org/apache/maven/internal/impl/types/RarTypeProvider.java +++ b/maven-core/src/main/java/org/apache/maven/internal/impl/types/RarTypeProvider.java @@ -22,9 +22,8 @@ import javax.inject.Provider; import javax.inject.Singleton; -import org.apache.maven.api.DependencyProperties; +import org.apache.maven.api.Language; import org.apache.maven.api.Type; -import org.apache.maven.internal.impl.DefaultDependencyProperties; import org.apache.maven.internal.impl.DefaultType; @Named(RarTypeProvider.NAME) @@ -35,12 +34,7 @@ public class RarTypeProvider implements Provider { private final Type type; public RarTypeProvider() { - this.type = new DefaultType( - NAME, - Type.LANGUAGE_JAVA, - "rar", - null, - new DefaultDependencyProperties(DependencyProperties.FLAG_INCLUDES_DEPENDENCIES)); + this.type = new DefaultType(NAME, Language.JAVA_FAMILY, "rar", null, true); } @Override diff --git a/maven-core/src/main/java/org/apache/maven/internal/impl/types/TestJarTypeProvider.java b/maven-core/src/main/java/org/apache/maven/internal/impl/types/TestJarTypeProvider.java index 6bd7ea2da727..00bb4c20fdfd 100644 --- a/maven-core/src/main/java/org/apache/maven/internal/impl/types/TestJarTypeProvider.java +++ b/maven-core/src/main/java/org/apache/maven/internal/impl/types/TestJarTypeProvider.java @@ -22,25 +22,28 @@ import javax.inject.Provider; import javax.inject.Singleton; -import org.apache.maven.api.DependencyProperties; +import org.apache.maven.api.JavaPathType; +import org.apache.maven.api.Language; import org.apache.maven.api.Type; -import org.apache.maven.internal.impl.DefaultDependencyProperties; import org.apache.maven.internal.impl.DefaultType; +/** + * Type provider for a JAR file containing test classes. Dependencies of this type are class-path constituents + * or {@code --patch-module} option values. For any dependency, the choice depends on whether the main JAR file + * was placed on the class-path or module-path respectively. + * + * @see Type#TEST_JAR + */ @Named(TestJarTypeProvider.NAME) @Singleton public class TestJarTypeProvider implements Provider { - public static final String NAME = "test-jar"; + public static final String NAME = Type.TEST_JAR; private final Type type; public TestJarTypeProvider() { this.type = new DefaultType( - NAME, - Type.LANGUAGE_JAVA, - "jar", - "tests", - new DefaultDependencyProperties(DependencyProperties.FLAG_CLASS_PATH_CONSTITUENT)); + NAME, Language.JAVA_FAMILY, "jar", "tests", false, JavaPathType.CLASSES, JavaPathType.PATCH_MODULE); } @Override diff --git a/maven-core/src/main/java/org/apache/maven/internal/impl/types/WarTypeProvider.java b/maven-core/src/main/java/org/apache/maven/internal/impl/types/WarTypeProvider.java index a2b339001d46..cdde4a512833 100644 --- a/maven-core/src/main/java/org/apache/maven/internal/impl/types/WarTypeProvider.java +++ b/maven-core/src/main/java/org/apache/maven/internal/impl/types/WarTypeProvider.java @@ -22,9 +22,8 @@ import javax.inject.Provider; import javax.inject.Singleton; -import org.apache.maven.api.DependencyProperties; +import org.apache.maven.api.Language; import org.apache.maven.api.Type; -import org.apache.maven.internal.impl.DefaultDependencyProperties; import org.apache.maven.internal.impl.DefaultType; @Named(WarTypeProvider.NAME) @@ -35,12 +34,7 @@ public class WarTypeProvider implements Provider { private final Type type; public WarTypeProvider() { - this.type = new DefaultType( - NAME, - Type.LANGUAGE_JAVA, - "war", - null, - new DefaultDependencyProperties(DependencyProperties.FLAG_INCLUDES_DEPENDENCIES)); + this.type = new DefaultType(NAME, Language.JAVA_FAMILY, "war", null, true); } @Override diff --git a/maven-core/src/main/java/org/apache/maven/internal/transformation/impl/TransformedArtifactHandler.java b/maven-core/src/main/java/org/apache/maven/internal/transformation/impl/TransformedArtifactHandler.java index 80ba4e7cdf2e..64087c262d47 100644 --- a/maven-core/src/main/java/org/apache/maven/internal/transformation/impl/TransformedArtifactHandler.java +++ b/maven-core/src/main/java/org/apache/maven/internal/transformation/impl/TransformedArtifactHandler.java @@ -36,30 +36,38 @@ class TransformedArtifactHandler implements ArtifactHandler { this.packaging = requireNonNull(packaging); } + @Override public String getClassifier() { return classifier; } + @Override public String getDirectory() { return null; } + @Override public String getExtension() { return extension; } + @Override public String getLanguage() { return "none"; } + @Override public String getPackaging() { return packaging; } + @Override + @Deprecated public boolean isAddedToClasspath() { return false; } + @Override public boolean isIncludesDependencies() { return false; } diff --git a/maven-core/src/main/java/org/apache/maven/project/MavenProject.java b/maven-core/src/main/java/org/apache/maven/project/MavenProject.java index 0e165e04cb30..9b58e0212646 100644 --- a/maven-core/src/main/java/org/apache/maven/project/MavenProject.java +++ b/maven-core/src/main/java/org/apache/maven/project/MavenProject.java @@ -32,12 +32,14 @@ import java.util.Objects; import java.util.Properties; import java.util.Set; +import java.util.function.Predicate; import org.apache.maven.RepositoryUtils; import org.apache.maven.artifact.Artifact; import org.apache.maven.artifact.ArtifactUtils; import org.apache.maven.artifact.DependencyResolutionRequiredException; import org.apache.maven.artifact.factory.ArtifactFactory; +import org.apache.maven.artifact.handler.ArtifactHandler; import org.apache.maven.artifact.repository.ArtifactRepository; import org.apache.maven.artifact.resolver.filter.ArtifactFilter; import org.apache.maven.lifecycle.internal.DefaultProjectArtifactFactory; @@ -320,68 +322,97 @@ public List getTestCompileSourceRoots() { return testCompileSourceRoots; } - public List getCompileClasspathElements() throws DependencyResolutionRequiredException { - List list = new ArrayList<>(getArtifacts().size() + 1); - - String d = getBuild().getOutputDirectory(); - if (d != null) { - list.add(d); - } - - for (Artifact a : getArtifacts()) { - if (a.getArtifactHandler().isAddedToClasspath()) { - // TODO let the scope handler deal with this - if (Artifact.SCOPE_COMPILE.equals(a.getScope()) - || Artifact.SCOPE_PROVIDED.equals(a.getScope()) - || Artifact.SCOPE_SYSTEM.equals(a.getScope())) { - addArtifactPath(a, list); - } - } - } + // TODO let the scope handler deal with this + private static boolean isCompilePathElement(final String scope) { + return Artifact.SCOPE_COMPILE.equals(scope) + || Artifact.SCOPE_PROVIDED.equals(scope) + || Artifact.SCOPE_SYSTEM.equals(scope); + } - return list; + // TODO let the scope handler deal with this + private static boolean isRuntimePathElement(final String scope) { + return Artifact.SCOPE_COMPILE.equals(scope) || Artifact.SCOPE_RUNTIME.equals(scope); } - // TODO this checking for file == null happens because the resolver has been confused about the root - // artifact or not. things like the stupid dummy artifact coming from surefire. - public List getTestClasspathElements() throws DependencyResolutionRequiredException { - List list = new ArrayList<>(getArtifacts().size() + 2); + // TODO let the scope handler deal with this + private static boolean isTestPathElement(final String scope) { + return true; + } - String d = getBuild().getTestOutputDirectory(); - if (d != null) { - list.add(d); + /** + * Returns a filtered list of classpath elements. This method is invoked when the caller + * requested that all dependencies are placed on the classpath, with no module-path element. + * + * @param scopeFilter a filter returning {@code true} for the artifact scopes to accept. + * @param includeTestDir whether to include the test directory in the classpath elements. + * @return paths of all artifacts placed on the classpath. + * @throws DependencyResolutionRequiredException if an artifact file is used, but has not been resolved + */ + private List getClasspathElements(final Predicate scopeFilter, final boolean includeTestDir) + throws DependencyResolutionRequiredException { + final List list = new ArrayList<>(getArtifacts().size() + 2); + if (includeTestDir) { + String d = getBuild().getTestOutputDirectory(); + if (d != null) { + list.add(d); + } } - - d = getBuild().getOutputDirectory(); + String d = getBuild().getOutputDirectory(); if (d != null) { list.add(d); } - for (Artifact a : getArtifacts()) { - if (a.getArtifactHandler().isAddedToClasspath()) { - addArtifactPath(a, list); + final File f = a.getFile(); + if (f != null && scopeFilter.test(a.getScope())) { + final ArtifactHandler h = a.getArtifactHandler(); + if (h.isAddedToClasspath()) { + list.add(f.getPath()); + } } } - return list; } - public List getRuntimeClasspathElements() throws DependencyResolutionRequiredException { - List list = new ArrayList<>(getArtifacts().size() + 1); + /** + * Returns the elements placed on the classpath for compilation. + * This method can be invoked when the caller does not support module-path. + * + * @throws DependencyResolutionRequiredException if an artifact file is used, but has not been resolved + * + * @deprecated This method is unreliable because it does not consider other dependency properties. + * See {@link org.apache.maven.api.JavaPathType} instead for better analysis. + */ + @Deprecated + public List getCompileClasspathElements() throws DependencyResolutionRequiredException { + return getClasspathElements(MavenProject::isCompilePathElement, false); + } - String d = getBuild().getOutputDirectory(); - if (d != null) { - list.add(d); - } + /** + * Returns the elements placed on the classpath for tests. + * This method can be invoked when the caller does not support module-path. + * + * @throws DependencyResolutionRequiredException if an artifact file is used, but has not been resolved + * + * @deprecated This method is unreliable because it does not consider other dependency properties. + * See {@link org.apache.maven.api.JavaPathType} instead for better analysis. + */ + @Deprecated + public List getTestClasspathElements() throws DependencyResolutionRequiredException { + return getClasspathElements(MavenProject::isTestPathElement, true); + } - for (Artifact a : getArtifacts()) { - if (a.getArtifactHandler().isAddedToClasspath() - // TODO let the scope handler deal with this - && (Artifact.SCOPE_COMPILE.equals(a.getScope()) || Artifact.SCOPE_RUNTIME.equals(a.getScope()))) { - addArtifactPath(a, list); - } - } - return list; + /** + * Returns the elements placed on the classpath for runtime. + * This method can be invoked when the caller does not support module-path. + * + * @throws DependencyResolutionRequiredException if an artifact file is used, but has not been resolved + * + * @deprecated This method is unreliable because it does not consider other dependency properties. + * See {@link org.apache.maven.api.JavaPathType} instead for better analysis. + */ + @Deprecated + public List getRuntimeClasspathElements() throws DependencyResolutionRequiredException { + return getClasspathElements(MavenProject::isRuntimePathElement, false); } // ---------------------------------------------------------------------- @@ -1111,13 +1142,6 @@ private void deepCopy(MavenProject project) { lifecyclePhases.addAll(project.lifecyclePhases); } - private void addArtifactPath(Artifact artifact, List classpath) { - File file = artifact.getFile(); - if (file != null) { - classpath.add(file.getPath()); - } - } - private static String getProjectReferenceId(String groupId, String artifactId, String version) { StringBuilder buffer = new StringBuilder(128); buffer.append(groupId).append(':').append(artifactId).append(':').append(version); @@ -1347,9 +1371,7 @@ public List getCompileArtifacts() { // TODO classpath check doesn't belong here - that's the other method if (a.getArtifactHandler().isAddedToClasspath()) { // TODO let the scope handler deal with this - if (Artifact.SCOPE_COMPILE.equals(a.getScope()) - || Artifact.SCOPE_PROVIDED.equals(a.getScope()) - || Artifact.SCOPE_SYSTEM.equals(a.getScope())) { + if (isCompilePathElement(a.getScope())) { list.add(a); } } @@ -1369,9 +1391,7 @@ public List getCompileDependencies() { for (Artifact a : getArtifacts()) { // TODO let the scope handler deal with this - if (Artifact.SCOPE_COMPILE.equals(a.getScope()) - || Artifact.SCOPE_PROVIDED.equals(a.getScope()) - || Artifact.SCOPE_SYSTEM.equals(a.getScope())) { + if (isCompilePathElement(a.getScope())) { Dependency dependency = new Dependency(); dependency.setArtifactId(a.getArtifactId()); @@ -1437,7 +1457,7 @@ public List getRuntimeDependencies() { for (Artifact a : getArtifacts()) { // TODO let the scope handler deal with this - if (Artifact.SCOPE_COMPILE.equals(a.getScope()) || Artifact.SCOPE_RUNTIME.equals(a.getScope())) { + if (isRuntimePathElement(a.getScope())) { Dependency dependency = new Dependency(); dependency.setArtifactId(a.getArtifactId()); @@ -1459,9 +1479,7 @@ public List getRuntimeArtifacts() { for (Artifact a : getArtifacts()) { // TODO classpath check doesn't belong here - that's the other method - if (a.getArtifactHandler().isAddedToClasspath() - // TODO let the scope handler deal with this - && (Artifact.SCOPE_COMPILE.equals(a.getScope()) || Artifact.SCOPE_RUNTIME.equals(a.getScope()))) { + if (a.getArtifactHandler().isAddedToClasspath() && isRuntimePathElement(a.getScope())) { list.add(a); } } @@ -1481,7 +1499,10 @@ public List getSystemClasspathElements() throws DependencyResolutionRequ if (a.getArtifactHandler().isAddedToClasspath()) { // TODO let the scope handler deal with this if (Artifact.SCOPE_SYSTEM.equals(a.getScope())) { - addArtifactPath(a, list); + File f = a.getFile(); + if (f != null) { + list.add(f.getPath()); + } } } } diff --git a/maven-core/src/main/java/org/apache/maven/project/artifact/PluginArtifact.java b/maven-core/src/main/java/org/apache/maven/project/artifact/PluginArtifact.java index d5bb916664dc..18c3442d0635 100644 --- a/maven-core/src/main/java/org/apache/maven/project/artifact/PluginArtifact.java +++ b/maven-core/src/main/java/org/apache/maven/project/artifact/PluginArtifact.java @@ -57,30 +57,38 @@ public List getManagedDependencies() { // TODO: this is duplicate of MavenPluginArtifactHandlerProvider provided one static class PluginArtifactHandler implements ArtifactHandler { + @Override public String getClassifier() { return null; } + @Override public String getDirectory() { return null; } + @Override public String getExtension() { return "jar"; } + @Override public String getLanguage() { return "none"; } + @Override public String getPackaging() { return "maven-plugin"; } + @Override + @Deprecated public boolean isAddedToClasspath() { return true; } + @Override public boolean isIncludesDependencies() { return false; } diff --git a/maven-core/src/main/java/org/apache/maven/project/artifact/ProjectArtifact.java b/maven-core/src/main/java/org/apache/maven/project/artifact/ProjectArtifact.java index 8d75eb76c576..c040fc93446e 100644 --- a/maven-core/src/main/java/org/apache/maven/project/artifact/ProjectArtifact.java +++ b/maven-core/src/main/java/org/apache/maven/project/artifact/ProjectArtifact.java @@ -62,30 +62,38 @@ public List getManagedDependencies() { // TODO: this is duplicate of PomArtifactHandlerProvider provided one static class PomArtifactHandler implements ArtifactHandler { + @Override public String getClassifier() { return null; } + @Override public String getDirectory() { return null; } + @Override public String getExtension() { return "pom"; } + @Override public String getLanguage() { return "none"; } + @Override public String getPackaging() { return "pom"; } + @Override + @Deprecated public boolean isAddedToClasspath() { return false; } + @Override public boolean isIncludesDependencies() { return false; } diff --git a/maven-core/src/test/java/org/apache/maven/AbstractCoreMavenComponentTestCase.java b/maven-core/src/test/java/org/apache/maven/AbstractCoreMavenComponentTestCase.java index 8feff0671724..dd6beba8aff8 100644 --- a/maven-core/src/test/java/org/apache/maven/AbstractCoreMavenComponentTestCase.java +++ b/maven-core/src/test/java/org/apache/maven/AbstractCoreMavenComponentTestCase.java @@ -34,6 +34,7 @@ import org.apache.maven.execution.DefaultMavenExecutionResult; import org.apache.maven.execution.MavenExecutionRequest; import org.apache.maven.execution.MavenSession; +import org.apache.maven.internal.impl.DefaultLookup; import org.apache.maven.internal.impl.DefaultSessionFactory; import org.apache.maven.model.Build; import org.apache.maven.model.Dependency; @@ -149,7 +150,7 @@ protected MavenSession createMavenSession(File pom, Properties executionProperti initRepoSession(configuration); DefaultSessionFactory defaultSessionFactory = - new DefaultSessionFactory(mock(RepositorySystem.class), null, null, null); + new DefaultSessionFactory(mock(RepositorySystem.class), null, new DefaultLookup(container), null); MavenSession session = new MavenSession( getContainer(), configuration.getRepositorySession(), request, new DefaultMavenExecutionResult()); diff --git a/maven-core/src/test/java/org/apache/maven/internal/impl/TestApi.java b/maven-core/src/test/java/org/apache/maven/internal/impl/TestApi.java index 8f2ec04eb984..ef654feca23b 100644 --- a/maven-core/src/test/java/org/apache/maven/internal/impl/TestApi.java +++ b/maven-core/src/test/java/org/apache/maven/internal/impl/TestApi.java @@ -23,6 +23,7 @@ import java.io.File; import java.nio.file.Path; import java.util.ArrayList; +import java.util.Arrays; import java.util.Collections; import java.util.List; import java.util.Map; @@ -30,13 +31,7 @@ import java.util.Optional; import java.util.stream.Collectors; -import org.apache.maven.api.Artifact; -import org.apache.maven.api.ArtifactCoordinate; -import org.apache.maven.api.Dependency; -import org.apache.maven.api.Node; -import org.apache.maven.api.Project; -import org.apache.maven.api.ResolutionScope; -import org.apache.maven.api.Session; +import org.apache.maven.api.*; import org.apache.maven.api.services.DependencyResolver; import org.apache.maven.api.services.DependencyResolverResult; import org.apache.maven.api.services.ProjectBuilder; @@ -64,6 +59,7 @@ import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertNull; import static org.junit.jupiter.api.Assertions.assertTrue; @PlexusTest @@ -132,6 +128,17 @@ void setup() { sessionScope.seed(InternalSession.class, InternalSession.from(this.session)); } + private Project project(Artifact artifact) { + return session.getService(ProjectBuilder.class) + .build(ProjectBuilderRequest.builder() + .session(session) + .path(session.getPathForLocalArtifact(artifact)) + .processPlugins(false) + .build()) + .getProject() + .get(); + } + @Test void testCreateAndResolveArtifact() { ArtifactCoordinate coord = @@ -150,14 +157,7 @@ void testCreateAndResolveArtifact() { void testBuildProject() { Artifact artifact = session.createArtifact("org.codehaus.plexus", "plexus-utils", "1.4.5", "pom"); - Project project = session.getService(ProjectBuilder.class) - .build(ProjectBuilderRequest.builder() - .session(session) - .path(session.getPathForLocalArtifact(artifact)) - .processPlugins(false) - .build()) - .getProject() - .get(); + Project project = project(artifact); assertNotNull(project); } @@ -171,28 +171,43 @@ void testCollectArtifactDependencies() { @Test void testResolveArtifactCoordinateDependencies() { - ArtifactCoordinate coord = - session.createArtifactCoordinate("org.apache.maven.core.test", "test-extension", "1", "jar"); + DependencyCoordinate coord = session.createDependencyCoordinate( + session.createArtifactCoordinate("org.apache.maven.core.test", "test-extension", "1", "jar")); - List paths = session.resolveDependencies(session.createDependencyCoordinate(coord)); + List paths = session.resolveDependencies(coord); assertNotNull(paths); assertEquals(10, paths.size()); - assertTrue(paths.get(0).getFileName().toString().equals("test-extension-1.jar")); + assertEquals("test-extension-1.jar", paths.get(0).getFileName().toString()); + + // JUnit has an "Automatic-Module-Name", so it appears on the module path. + Map> dispatched = session.resolveDependencies( + coord, PathScope.TEST_COMPILE, Arrays.asList(JavaPathType.CLASSES, JavaPathType.MODULES)); + List classes = dispatched.get(JavaPathType.CLASSES); + List modules = dispatched.get(JavaPathType.MODULES); + assertEquals(2, dispatched.size()); + assertEquals(8, classes.size()); // "pluxus.pom" and "junit.jar" are excluded. + assertEquals(1, modules.size()); + assertEquals("test-extension-1.jar", classes.get(0).getFileName().toString()); + assertEquals("junit-4.13.1.jar", modules.get(0).getFileName().toString()); + assertTrue(paths.containsAll(classes)); + assertTrue(paths.containsAll(modules)); + + // If caller wants only a classpath, JUnit shall move there. + dispatched = session.resolveDependencies(coord, PathScope.TEST_COMPILE, Arrays.asList(JavaPathType.CLASSES)); + classes = dispatched.get(JavaPathType.CLASSES); + modules = dispatched.get(JavaPathType.MODULES); + assertEquals(1, dispatched.size()); + assertEquals(9, classes.size()); + assertNull(modules); + assertTrue(paths.containsAll(classes)); } @Test void testProjectDependencies() { Artifact pom = session.createArtifact("org.codehaus.plexus", "plexus-container-default", "1.0-alpha-32", "pom"); - Project project = session.getService(ProjectBuilder.class) - .build(ProjectBuilderRequest.builder() - .session(session) - .path(session.getPathForLocalArtifact(pom)) - .processPlugins(false) - .build()) - .getProject() - .get(); + Project project = project(pom); assertNotNull(project); Artifact artifact = session.createArtifact("org.apache.maven.core.test", "test-extension", "1", "jar"); @@ -200,7 +215,7 @@ void testProjectDependencies() { assertNotNull(root); DependencyResolverResult result = - session.getService(DependencyResolver.class).resolve(session, project, ResolutionScope.PROJECT_RUNTIME); + session.getService(DependencyResolver.class).resolve(session, project, PathScope.MAIN_RUNTIME); assertNotNull(result); List deps = new ArrayList<>(result.getDependencies().keySet()); List deps2 = result.getNodes().stream() diff --git a/maven-core/src/test/java/org/apache/maven/internal/impl/TestArtifactHandler.java b/maven-core/src/test/java/org/apache/maven/internal/impl/TestArtifactHandler.java index cb86c6efdbc7..30ebee3972d8 100644 --- a/maven-core/src/test/java/org/apache/maven/internal/impl/TestArtifactHandler.java +++ b/maven-core/src/test/java/org/apache/maven/internal/impl/TestArtifactHandler.java @@ -39,30 +39,38 @@ public TestArtifactHandler(String type, String extension) { this.extension = extension; } + @Override public String getClassifier() { return null; } + @Override public String getDirectory() { return getPackaging() + "s"; } + @Override public String getExtension() { return extension; } + @Override public String getLanguage() { return "java"; } + @Override public String getPackaging() { return type; } + @Override + @Deprecated public boolean isAddedToClasspath() { return true; } + @Override public boolean isIncludesDependencies() { return false; } diff --git a/maven-resolver-provider/src/main/java/org/apache/maven/repository/internal/FatArtifactTraverser.java b/maven-resolver-provider/src/main/java/org/apache/maven/repository/internal/FatArtifactTraverser.java new file mode 100644 index 000000000000..903967197592 --- /dev/null +++ b/maven-resolver-provider/src/main/java/org/apache/maven/repository/internal/FatArtifactTraverser.java @@ -0,0 +1,54 @@ +/* + * 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.repository.internal; + +import java.util.Objects; + +import org.eclipse.aether.artifact.ArtifactProperties; +import org.eclipse.aether.collection.DependencyCollectionContext; +import org.eclipse.aether.collection.DependencyTraverser; +import org.eclipse.aether.graph.Dependency; + +public final class FatArtifactTraverser implements DependencyTraverser { + public FatArtifactTraverser() {} + + public boolean traverseDependency(Dependency dependency) { + Objects.requireNonNull(dependency, "dependency cannot be null"); + + String prop = dependency.getArtifact().getProperty(ArtifactProperties.INCLUDES_DEPENDENCIES, ""); + return !Boolean.parseBoolean(prop); + } + + public DependencyTraverser deriveChildTraverser(DependencyCollectionContext context) { + Objects.requireNonNull(context, "context cannot be null"); + return this; + } + + public boolean equals(Object obj) { + if (this == obj) { + return true; + } else { + return null != obj && this.getClass().equals(obj.getClass()); + } + } + + public int hashCode() { + return this.getClass().hashCode(); + } +} diff --git a/maven-resolver-provider/src/main/java/org/apache/maven/repository/internal/MavenRepositorySystemUtils.java b/maven-resolver-provider/src/main/java/org/apache/maven/repository/internal/MavenRepositorySystemUtils.java index a87b6cefdcdb..d9ffb72d3318 100644 --- a/maven-resolver-provider/src/main/java/org/apache/maven/repository/internal/MavenRepositorySystemUtils.java +++ b/maven-resolver-provider/src/main/java/org/apache/maven/repository/internal/MavenRepositorySystemUtils.java @@ -39,7 +39,6 @@ import org.eclipse.aether.util.graph.transformer.ConflictResolver; import org.eclipse.aether.util.graph.transformer.NearestVersionSelector; import org.eclipse.aether.util.graph.transformer.SimpleOptionalitySelector; -import org.eclipse.aether.util.graph.traverser.FatArtifactTraverser; import org.eclipse.aether.util.repository.SimpleArtifactDescriptorPolicy; import static java.util.Objects.requireNonNull; diff --git a/maven-resolver-provider/src/main/java/org/apache/maven/repository/internal/scopes/MavenDependencyScopes.java b/maven-resolver-provider/src/main/java/org/apache/maven/repository/internal/scopes/MavenDependencyScopes.java index 2d074a4e070a..fa0fe5b8f71e 100644 --- a/maven-resolver-provider/src/main/java/org/apache/maven/repository/internal/scopes/MavenDependencyScopes.java +++ b/maven-resolver-provider/src/main/java/org/apache/maven/repository/internal/scopes/MavenDependencyScopes.java @@ -18,7 +18,7 @@ */ package org.apache.maven.repository.internal.scopes; -import org.eclipse.aether.util.artifact.DependencyScopes; +import org.apache.maven.api.DependencyScope; /** * The dependency scopes used for Java dependencies in Maven. This class defines labels only, that are doing pass-thru @@ -31,21 +31,23 @@ public final class MavenDependencyScopes { /** * Important: keep this label in sync with Resolver. */ - public static final String SYSTEM = DependencyScopes.SYSTEM; + public static final String SYSTEM = DependencyScope.SYSTEM.id(); - public static final String COMPILE_ONLY = "compile-only"; + public static final String NONE = DependencyScope.NONE.id(); - public static final String COMPILE = "compile"; + public static final String COMPILE_ONLY = DependencyScope.COMPILE_ONLY.id(); - public static final String PROVIDED = "provided"; + public static final String COMPILE = DependencyScope.COMPILE.id(); - public static final String RUNTIME = "runtime"; + public static final String PROVIDED = DependencyScope.PROVIDED.id(); - public static final String TEST_ONLY = "test-only"; + public static final String RUNTIME = DependencyScope.RUNTIME.id(); - public static final String TEST = "test"; + public static final String TEST_ONLY = DependencyScope.TEST_ONLY.id(); - public static final String TEST_RUNTIME = "test-runtime"; + public static final String TEST = DependencyScope.TEST.id(); + + public static final String TEST_RUNTIME = DependencyScope.TEST_RUNTIME.id(); private MavenDependencyScopes() { // hide constructor