This is an automated email from the ASF dual-hosted git repository.

gnodet pushed a commit to branch maven-4.0.x
in repository https://gitbox.apache.org/repos/asf/maven.git


The following commit(s) were added to refs/heads/maven-4.0.x by this push:
     new b2e4fd4763 Enable the search for `module-info.class` file in the 
`META-INF/versions/` sub-directories of a JAR file. (#11153) (#11206)
b2e4fd4763 is described below

commit b2e4fd4763e216df3865c2b32dc568e439013bdf
Author: Guillaume Nodet <[email protected]>
AuthorDate: Mon Oct 6 23:14:21 2025 +0200

    Enable the search for `module-info.class` file in the `META-INF/versions/` 
sub-directories of a JAR file. (#11153) (#11206)
    
    If the project specifies a target Java release, only the directories for 
versions equal to lower to the target version will be scanned.
    
    (cherry picked from commit f032cfe067a29d40e3cdfd51a23926ad6a4f4a74)
    
    Co-authored-by: Martin Desruisseaux <[email protected]>
---
 .../api/services/DependencyResolverRequest.java    | 51 +++++++++++++++++++++-
 .../maven/impl/DefaultDependencyResolver.java      | 38 ++++++++++++++--
 .../impl/DefaultDependencyResolverResult.java      |  7 ++-
 .../org/apache/maven/impl/PathModularization.java  |  7 +--
 .../apache/maven/impl/PathModularizationCache.java | 18 ++++++--
 5 files changed, 108 insertions(+), 13 deletions(-)

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 f419d7ff60..e9b3ab956b 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<PathType> getPathTypeFilter();
 
+    /**
+     * Returns the version of the platform where the code will be executed.
+     * It should be the highest value of the {@code <targetVersion>} elements
+     * inside the {@code <source>} elements of a <abbr>POM</abbr> file.
+     *
+     * <h4>Application to Java</h4>
+     * 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 
<abbr>JAR</abbr> 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.
+     *
+     * <p>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()}.</p>
+     *
+     * @return version of the platform where the code will be executed, or 
{@code null} for default
+     *
+     * @see SourceRoot#targetVersion()
+     */
+    @Nullable
+    Version getTargetVersion();
+
     @Nullable
     List<RemoteRepository> getRepositories();
 
@@ -181,6 +205,7 @@ class DependencyResolverRequestBuilder {
         boolean verbose;
         PathScope pathScope;
         Predicate<PathType> pathTypeFilter;
+        Version targetVersion;
         List<RemoteRepository> repositories;
 
         DependencyResolverRequestBuilder() {}
@@ -345,6 +370,18 @@ public DependencyResolverRequestBuilder 
pathTypeFilter(@Nonnull Collection<? ext
             return pathTypeFilter(desiredTypes::contains);
         }
 
+        /**
+         * Sets the version of the platform where the code will be executed.
+         *
+         * @param target version of the platform where the code will be 
executed, or {@code null} for the default
+         * @return {@code this} for method call chaining
+         */
+        @Nonnull
+        public DependencyResolverRequestBuilder targetVersion(@Nullable 
Version target) {
+            targetVersion = target;
+            return this;
+        }
+
         @Nonnull
         public DependencyResolverRequestBuilder repositories(@Nonnull 
List<RemoteRepository> 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<PathType> pathTypeFilter;
+            private final Version targetVersion;
             private final List<RemoteRepository> repositories;
 
             /**
@@ -426,6 +465,7 @@ public String toString() {
                     boolean verbose,
                     @Nullable PathScope pathScope,
                     @Nullable Predicate<PathType> pathTypeFilter,
+                    @Nullable Version targetVersion,
                     @Nullable List<RemoteRepository> 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<PathType> getPathTypeFilter() {
                 return pathTypeFilter;
             }
 
+            @Override
+            public Version getTargetVersion() {
+                return targetVersion;
+            }
+
             @Override
             public List<RemoteRepository> 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 814e71bace..278d7feb7e 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.
      *
      * <p><b>TODO:</b> 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.</p>
      */
-    private final PathModularizationCache moduleCache;
+    private final Map<Runtime.Version, PathModularizationCache> 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<PathType> 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 4b67c9ee32..a97062ae5a 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<Exception> 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 db0c950995..a40e57215a 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 e04ce136da..3ab71dc33c 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<Path, PathType> 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;

Reply via email to