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 f419d7ff60a7..e9b3ab956bd9 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 @@ -34,6 +34,8 @@ import org.apache.maven.api.Project; import org.apache.maven.api.RemoteRepository; import org.apache.maven.api.Session; +import org.apache.maven.api.SourceRoot; +import org.apache.maven.api.Version; import org.apache.maven.api.annotations.Experimental; import org.apache.maven.api.annotations.Immutable; import org.apache.maven.api.annotations.Nonnull; @@ -95,6 +97,28 @@ enum RequestType { @Nullable Predicate getPathTypeFilter(); + /** + * Returns the version of the platform where the code will be executed. + * It should be the highest value of the {@code } elements + * inside the {@code } elements of a POM file. + * + *

Application to Java

+ * In the context of a Java project, this is the value given to the {@code --release} compiler option. + * This value can determine whether a dependency will be placed on the class-path or on the module-path. + * For example, if the {@code module-info.class} entry of a JAR file exists only in the + * {@code META-INF/versions/17/} sub-directory, then the default location of that dependency will be + * the module-path only if the {@code --release} option is equal or greater than 17. + * + *

If this value is not provided, then the default value in the context of Java projects + * is the Java version on which Maven is running, as given by {@link Runtime#version()}.

+ * + * @return version of the platform where the code will be executed, or {@code null} for default + * + * @see SourceRoot#targetVersion() + */ + @Nullable + Version getTargetVersion(); + @Nullable List getRepositories(); @@ -181,6 +205,7 @@ class DependencyResolverRequestBuilder { boolean verbose; PathScope pathScope; Predicate pathTypeFilter; + Version targetVersion; List repositories; DependencyResolverRequestBuilder() {} @@ -345,6 +370,18 @@ public DependencyResolverRequestBuilder pathTypeFilter(@Nonnull Collection repositories) { this.repositories = repositories; @@ -365,6 +402,7 @@ public DependencyResolverRequest build() { verbose, pathScope, pathTypeFilter, + targetVersion, repositories); } @@ -404,6 +442,7 @@ public String toString() { private final boolean verbose; private final PathScope pathScope; private final Predicate pathTypeFilter; + private final Version targetVersion; private final List repositories; /** @@ -426,6 +465,7 @@ public String toString() { boolean verbose, @Nullable PathScope pathScope, @Nullable Predicate pathTypeFilter, + @Nullable Version targetVersion, @Nullable List repositories) { super(session, trace); this.requestType = requireNonNull(requestType, "requestType cannot be null"); @@ -438,6 +478,7 @@ public String toString() { this.verbose = verbose; this.pathScope = requireNonNull(pathScope, "pathScope cannot be null"); this.pathTypeFilter = (pathTypeFilter != null) ? pathTypeFilter : DEFAULT_FILTER; + this.targetVersion = targetVersion; this.repositories = repositories; if (verbose && requestType != RequestType.COLLECT) { throw new IllegalArgumentException("verbose cannot only be true when collecting dependencies"); @@ -495,6 +536,11 @@ public Predicate getPathTypeFilter() { return pathTypeFilter; } + @Override + public Version getTargetVersion() { + return targetVersion; + } + @Override public List getRepositories() { return repositories; @@ -512,6 +558,7 @@ public boolean equals(Object o) { && Objects.equals(managedDependencies, that.managedDependencies) && Objects.equals(pathScope, that.pathScope) && Objects.equals(pathTypeFilter, that.pathTypeFilter) + && Objects.equals(targetVersion, that.targetVersion) && Objects.equals(repositories, that.repositories); } @@ -527,6 +574,7 @@ public int hashCode() { verbose, pathScope, pathTypeFilter, + targetVersion, repositories); } @@ -541,7 +589,8 @@ public String toString() { + managedDependencies + ", verbose=" + verbose + ", pathScope=" + pathScope + ", pathTypeFilter=" - + pathTypeFilter + ", repositories=" + + pathTypeFilter + ", targetVersion=" + + targetVersion + ", repositories=" + repositories + ']'; } } diff --git a/impl/maven-impl/src/main/java/org/apache/maven/impl/DefaultDependencyResolver.java b/impl/maven-impl/src/main/java/org/apache/maven/impl/DefaultDependencyResolver.java index 814e71bace23..278d7feb7e84 100644 --- a/impl/maven-impl/src/main/java/org/apache/maven/impl/DefaultDependencyResolver.java +++ b/impl/maven-impl/src/main/java/org/apache/maven/impl/DefaultDependencyResolver.java @@ -21,7 +21,9 @@ import java.io.IOException; import java.nio.file.Path; import java.util.Collection; +import java.util.HashMap; import java.util.List; +import java.util.Map; import java.util.Objects; import java.util.Set; import java.util.function.Predicate; @@ -37,6 +39,7 @@ import org.apache.maven.api.Project; import org.apache.maven.api.RemoteRepository; import org.apache.maven.api.Session; +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.di.Named; @@ -70,18 +73,41 @@ public class DefaultDependencyResolver implements DependencyResolver { /** * Cache of information about the modules contained in a path element. + * Keys are the Java versions targeted by the project. * *

TODO: This field should not be in this class, because the cache should be global to the session. * This field exists here only temporarily, until clarified where to store session-wide caches.

*/ - private final PathModularizationCache moduleCache; + private final Map moduleCaches; /** * Creates an initially empty resolver. */ public DefaultDependencyResolver() { // TODO: the cache should not be instantiated here, but should rather be session-wide. - moduleCache = new PathModularizationCache(); + moduleCaches = new HashMap<>(); + } + + /** + * {@return the cache for the given request}. + * + * @param request the request for which to get the target version + * @throws IllegalArgumentException if the version string cannot be interpreted as a valid version + */ + private PathModularizationCache moduleCache(DependencyResolverRequest request) { + return moduleCaches.computeIfAbsent(getTargetVersion(request), PathModularizationCache::new); + } + + /** + * Returns the target version of the given request as a Java version object. + * + * @param request the request for which to get the target version + * @return the target version as a Java object + * @throws IllegalArgumentException if the version string cannot be interpreted as a valid version + */ + static Runtime.Version getTargetVersion(DependencyResolverRequest request) { + Version target = request.getTargetVersion(); + return (target != null) ? Runtime.Version.parse(target.toString()) : Runtime.version(); } @Nonnull @@ -143,7 +169,7 @@ public DependencyResolverResult collect(@Nonnull DependencyResolverRequest reque session.getRepositorySystem().collectDependencies(systemSession, collectRequest); return new DefaultDependencyResolverResult( null, - moduleCache, + moduleCache(request), result.getExceptions(), session.getNode(result.getRoot(), request.getVerbose()), 0); @@ -212,7 +238,11 @@ public DependencyResolverResult resolve(DependencyResolverRequest request) .collect(Collectors.toList()); Predicate filter = request.getPathTypeFilter(); DefaultDependencyResolverResult resolverResult = new DefaultDependencyResolverResult( - null, moduleCache, collectorResult.getExceptions(), collectorResult.getRoot(), nodes.size()); + null, + moduleCache(request), + collectorResult.getExceptions(), + collectorResult.getRoot(), + nodes.size()); if (request.getRequestType() == DependencyResolverRequest.RequestType.FLATTEN) { for (Node node : nodes) { resolverResult.addNode(node); diff --git a/impl/maven-impl/src/main/java/org/apache/maven/impl/DefaultDependencyResolverResult.java b/impl/maven-impl/src/main/java/org/apache/maven/impl/DefaultDependencyResolverResult.java index 4b67c9ee32c6..a97062ae5a3b 100644 --- a/impl/maven-impl/src/main/java/org/apache/maven/impl/DefaultDependencyResolverResult.java +++ b/impl/maven-impl/src/main/java/org/apache/maven/impl/DefaultDependencyResolverResult.java @@ -114,7 +114,12 @@ public class DefaultDependencyResolverResult implements DependencyResolverResult */ public DefaultDependencyResolverResult( DependencyResolverRequest request, List exceptions, Node root, int count) { - this(request, new PathModularizationCache(), exceptions, root, count); + this( + request, + new PathModularizationCache(DefaultDependencyResolver.getTargetVersion(request)), + exceptions, + root, + count); } /** diff --git a/impl/maven-impl/src/main/java/org/apache/maven/impl/PathModularization.java b/impl/maven-impl/src/main/java/org/apache/maven/impl/PathModularization.java index db0c95099578..a40e57215a1d 100644 --- a/impl/maven-impl/src/main/java/org/apache/maven/impl/PathModularization.java +++ b/impl/maven-impl/src/main/java/org/apache/maven/impl/PathModularization.java @@ -43,7 +43,7 @@ * or module hierarchy, but not module source hierarchy. The latter is excluded because this class * is for path elements of compiled codes. */ -class PathModularization { +final class PathModularization { /** * A unique constant for all non-modular dependencies. */ @@ -132,10 +132,11 @@ private PathModularization() { * Otherwise builds an empty map. * * @param path directory or JAR file to test + * @param target the target Java release for which the project is built * @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(Path path, boolean resolve) throws IOException { + PathModularization(Path path, Runtime.Version target, boolean resolve) throws IOException { filename = path.getFileName().toString(); if (Files.isDirectory(path)) { /* @@ -192,7 +193,7 @@ private PathModularization() { * If no descriptor, the "Automatic-Module-Name" manifest attribute is * taken as a fallback. */ - try (JarFile jar = new JarFile(path.toFile())) { + try (JarFile jar = new JarFile(path.toFile(), false, JarFile.OPEN_READ, target)) { ZipEntry entry = jar.getEntry(MODULE_INFO); if (entry != null) { ModuleDescriptor descriptor = null; diff --git a/impl/maven-impl/src/main/java/org/apache/maven/impl/PathModularizationCache.java b/impl/maven-impl/src/main/java/org/apache/maven/impl/PathModularizationCache.java index e04ce136da24..3ab71dc33c37 100644 --- a/impl/maven-impl/src/main/java/org/apache/maven/impl/PathModularizationCache.java +++ b/impl/maven-impl/src/main/java/org/apache/maven/impl/PathModularizationCache.java @@ -24,6 +24,7 @@ import java.util.Collection; import java.util.HashMap; import java.util.Map; +import java.util.Objects; import java.util.Optional; import java.util.Set; import java.util.StringJoiner; @@ -38,7 +39,7 @@ * same dependency is used for different scope. For example a path used for compilation * is typically also used for tests. */ -class PathModularizationCache { +final class PathModularizationCache { /** * Module information for each JAR file or output directories. * Cached when first requested to avoid decoding the module descriptors multiple times. @@ -55,12 +56,21 @@ class PathModularizationCache { */ private final Map pathTypes; + /** + * The target Java version for which the project is built. + * If unknown, it should be {@link Runtime#version()}. + */ + private final Runtime.Version targetVersion; + /** * Creates an initially empty cache. + * + * @param target the target Java release for which the project is built */ - PathModularizationCache() { + PathModularizationCache(Runtime.Version target) { moduleInfo = new HashMap<>(); pathTypes = new HashMap<>(); + targetVersion = Objects.requireNonNull(target); } /** @@ -70,7 +80,7 @@ class PathModularizationCache { PathModularization getModuleInfo(Path path) throws IOException { PathModularization info = moduleInfo.get(path); if (info == null) { - info = new PathModularization(path, true); + info = new PathModularization(path, targetVersion, true); moduleInfo.put(path, info); pathTypes.put(path, info.getPathType()); } @@ -85,7 +95,7 @@ PathModularization getModuleInfo(Path path) throws IOException { private PathType getPathType(Path path) throws IOException { PathType type = pathTypes.get(path); if (type == null) { - type = new PathModularization(path, false).getPathType(); + type = new PathModularization(path, targetVersion, false).getPathType(); pathTypes.put(path, type); } return type;