This is an automated email from the ASF dual-hosted git repository. gnodet pushed a commit to branch master in repository https://gitbox.apache.org/repos/asf/maven.git
The following commit(s) were added to refs/heads/master by this push: new 008d0b4d5b Add `getModuleName(Path)` and `getModuleDescription(Path)` methods in `DependencyResolverResult`. (#1625) 008d0b4d5b is described below commit 008d0b4d5b3640f372e72587d2be42dde498b5b8 Author: Martin Desruisseaux <martin.desruisse...@geomatys.com> AuthorDate: Fri Aug 16 11:19:34 2024 +0200 Add `getModuleName(Path)` and `getModuleDescription(Path)` methods in `DependencyResolverResult`. (#1625) Those methods are helpful for plugins that need to provide `--add-reads` and similar options, as they allow to reuse the cached values instead of decoding `module-info.class` many times. --- .../api/services/DependencyResolverResult.java | 35 +++++++++++ .../impl/DefaultDependencyResolverResult.java | 49 +++++++++++---- .../maven/internal/impl/PathModularization.java | 73 ++++++---------------- 3 files changed, 93 insertions(+), 64 deletions(-) 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 e7ab86d011..c8f125eb7e 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 @@ -18,12 +18,15 @@ */ package org.apache.maven.api.services; +import java.io.IOException; +import java.lang.module.ModuleDescriptor; 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.DependencyScope; import org.apache.maven.api.JavaPathType; import org.apache.maven.api.Node; import org.apache.maven.api.PathType; @@ -100,6 +103,38 @@ public interface DependencyResolverResult { @Nonnull Map<Dependency, Path> getDependencies(); + /** + * Returns the Java module name of the dependency at the given path. + * The given dependency should be one of the paths returned by {@link #getDependencies()}. + * The module name is extracted from the {@code module-info.class} file if present, otherwise from + * the {@code "Automatic-Module-Name"} attribute of the {@code META-INF/MANIFEST.MF} file if present. + * + * <p>A typical usage is to invoke this method for all dependencies having a + * {@link DependencyScope#TEST TEST} or {@link DependencyScope#TEST_ONLY TEST_ONLY} + * {@linkplain Dependency#getScope() scope}. An {@code --add-reads} option may need + * to be generated for compiling and running the test classes that use such dependencies.</p> + * + * @param dependency path to the dependency for which to get the module name + * @return module name of the dependency at the given path, or empty if the dependency is not modular + * @throws IOException if the module information of the specified dependency cannot be read + */ + Optional<String> getModuleName(@Nonnull Path dependency) throws IOException; + + /** + * Returns the Java module descriptor of the dependency at the given path. + * The given dependency should be one of the paths returned by {@link #getDependencies()}. + * The module descriptor is extracted from the {@code module-info.class} file if present. + * + * <p>{@link #getModuleName(Path)} is preferred when only the module name is desired, + * because a name may be present even if the descriptor is absent. This method is for + * cases when more information is desired, such as the set of exported packages.</p> + * + * @param dependency path to the dependency for which to get the module name + * @return module name of the dependency at the given path, or empty if the dependency is not modular + * @throws IOException if the module information of the specified dependency cannot be read + */ + Optional<ModuleDescriptor> getModuleDescriptor(@Nonnull Path dependency) throws IOException; + /** * If the module-path contains at least one filename-based auto-module, prepares a warning message. * The module path is the collection of dependencies associated to {@link JavaPathType#MODULES}. diff --git a/maven-api-impl/src/main/java/org/apache/maven/internal/impl/DefaultDependencyResolverResult.java b/maven-api-impl/src/main/java/org/apache/maven/internal/impl/DefaultDependencyResolverResult.java index ffad2adab5..be6a8cbb3a 100644 --- a/maven-api-impl/src/main/java/org/apache/maven/internal/impl/DefaultDependencyResolverResult.java +++ b/maven-api-impl/src/main/java/org/apache/maven/internal/impl/DefaultDependencyResolverResult.java @@ -19,6 +19,7 @@ package org.apache.maven.internal.impl; import java.io.IOException; +import java.lang.module.ModuleDescriptor; import java.nio.file.Files; import java.nio.file.Path; import java.util.ArrayList; @@ -154,7 +155,7 @@ class DefaultDependencyResolverResult implements DependencyResolverResult { * @param test the test output directory, or {@code null} if none * @throws IOException if an error occurred while reading module information * - * TODO: this is currently not called + * TODO: this is currently not called. This is intended for use by Surefire and may move there. */ void addOutputDirectory(Path main, Path test) throws IOException { if (outputModules != null) { @@ -169,8 +170,9 @@ class DefaultDependencyResolverResult implements DependencyResolverResult { if (test != null) { boolean addToClasspath = true; PathModularization testModules = cache.getModuleInfo(test); - boolean isModuleHierarchy = outputModules.isModuleHierarchy() || testModules.isModuleHierarchy(); - for (String moduleName : outputModules.getModuleNames().values()) { + boolean isModuleHierarchy = outputModules.isModuleHierarchy || testModules.isModuleHierarchy; + for (Object value : outputModules.descriptors.values()) { + String moduleName = name(value); Path subdir = test; if (isModuleHierarchy) { // If module hierarchy is used, the directory names shall be the module names. @@ -189,8 +191,8 @@ class DefaultDependencyResolverResult implements DependencyResolverResult { * 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 (Map.Entry<Path, String> entry : testModules.getModuleNames().entrySet()) { - if (!outputModules.containsModule(entry.getValue())) { + for (Map.Entry<Path, Object> entry : testModules.descriptors.entrySet()) { + if (!outputModules.containsModule(name(entry.getValue()))) { addPathElement(JavaPathType.MODULES, entry.getKey()); addToClasspath = false; } @@ -246,9 +248,9 @@ class DefaultDependencyResolverResult implements DependencyResolverResult { outputModules = PathModularization.NONE; } PathType type = null; - for (Map.Entry<Path, String> info : - cache.getModuleInfo(path).getModuleNames().entrySet()) { - String moduleName = info.getValue(); + for (Map.Entry<Path, Object> info : + cache.getModuleInfo(path).descriptors.entrySet()) { + String moduleName = name(info.getValue()); type = JavaPathType.patchModule(moduleName); if (!containsModule(moduleName)) { /* @@ -269,9 +271,9 @@ class DefaultDependencyResolverResult implements DependencyResolverResult { if (type == null) { Path main = findArtifactPath(dep.getGroupId(), dep.getArtifactId()); if (main != null) { - for (Map.Entry<Path, String> info : - cache.getModuleInfo(main).getModuleNames().entrySet()) { - type = JavaPathType.patchModule(info.getValue()); + for (Map.Entry<Path, Object> info : + cache.getModuleInfo(main).descriptors.entrySet()) { + type = JavaPathType.patchModule(name(info.getValue())); addPathElement(type, info.getKey()); // There is usually no more than one element, but nevertheless allow multi-modules. } @@ -360,6 +362,31 @@ class DefaultDependencyResolverResult implements DependencyResolverResult { return dependencies; } + @Override + public Optional<ModuleDescriptor> getModuleDescriptor(Path dependency) throws IOException { + Object value = cache.getModuleInfo(dependency).descriptors.get(dependency); + return (value instanceof ModuleDescriptor) ? Optional.of((ModuleDescriptor) value) : Optional.empty(); + } + + @Override + public Optional<String> getModuleName(Path dependency) throws IOException { + return Optional.ofNullable( + name(cache.getModuleInfo(dependency).descriptors.get(dependency))); + } + + /** + * Returns the module name for the given value of the {@link PathModularization#descriptors} map. + */ + private static String name(final Object value) { + if (value instanceof String) { + return (String) value; + } else if (value instanceof ModuleDescriptor) { + return ((ModuleDescriptor) value).name(); + } else { + return null; + } + } + @Override public Optional<String> warningForFilenameBasedAutomodules() { try { diff --git a/maven-api-impl/src/main/java/org/apache/maven/internal/impl/PathModularization.java b/maven-api-impl/src/main/java/org/apache/maven/internal/impl/PathModularization.java index 2728fb4aca..d8e9e941dd 100644 --- a/maven-api-impl/src/main/java/org/apache/maven/internal/impl/PathModularization.java +++ b/maven-api-impl/src/main/java/org/apache/maven/internal/impl/PathModularization.java @@ -70,23 +70,24 @@ class PathModularization { * It may however contain more than one entry if module hierarchy was detected, * in which case there is one key per sub-directory. * + * <p>Values are instances of either {@link ModuleDescriptor} or {@link String}. + * The latter case happens when a JAR file has no {@code module-info.class} entry + * but has an automatic name declared in {@code META-INF/MANIFEST.MF}.</p> + * * <p>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.</p> - * - * @see #getModuleNames() */ - private final Map<Path, String> descriptors; + @Nonnull + final Map<Path, Object> 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; + final boolean isModuleHierarchy; /** * Constructs an empty instance for non-modular dependencies. @@ -144,13 +145,13 @@ class PathModularization { */ Path file = path.resolve(MODULE_INFO); if (Files.isRegularFile(file)) { - String name = null; + ModuleDescriptor descriptor = null; if (resolve) { try (InputStream in = Files.newInputStream(file)) { - name = getModuleName(in); + descriptor = ModuleDescriptor.read(in); } } - descriptors = Collections.singletonMap(file, name); + descriptors = Collections.singletonMap(file, descriptor); isModuleHierarchy = false; return; } @@ -160,27 +161,27 @@ class PathModularization { * source files. */ if (Files.isDirectory(file)) { - Map<Path, String> names = new HashMap<>(); + var multi = new HashMap<Path, ModuleDescriptor>(); try (Stream<Path> subdirs = Files.list(file)) { subdirs.filter(Files::isDirectory).forEach((subdir) -> { Path mf = subdir.resolve(MODULE_INFO); if (Files.isRegularFile(mf)) { - String name = null; + ModuleDescriptor descriptor = null; if (resolve) { try (InputStream in = Files.newInputStream(mf)) { - name = getModuleName(in); + descriptor = ModuleDescriptor.read(in); } catch (IOException e) { throw new UncheckedIOException(e); } } - names.put(mf, name); + multi.put(mf, descriptor); } }); } catch (UncheckedIOException e) { throw e.getCause(); } - if (!names.isEmpty()) { - descriptors = Collections.unmodifiableMap(names); + if (!multi.isEmpty()) { + descriptors = Collections.unmodifiableMap(multi); isModuleHierarchy = true; return; } @@ -194,13 +195,13 @@ class PathModularization { try (JarFile jar = new JarFile(path.toFile())) { ZipEntry entry = jar.getEntry(MODULE_INFO); if (entry != null) { - String name = null; + ModuleDescriptor descriptor = null; if (resolve) { try (InputStream in = jar.getInputStream(entry)) { - name = getModuleName(in); + descriptor = ModuleDescriptor.read(in); } } - descriptors = Collections.singletonMap(path, name); + descriptors = Collections.singletonMap(path, descriptor); isModuleHierarchy = false; return; } @@ -209,7 +210,7 @@ class PathModularization { if (mf != null) { Object name = mf.getMainAttributes().get(AUTO_MODULE_NAME); if (name instanceof String) { - descriptors = Collections.singletonMap(path, (String) name); + descriptors = Collections.singletonMap(path, name); isModuleHierarchy = false; return; } @@ -220,15 +221,6 @@ class PathModularization { isModuleHierarchy = false; } - /** - * {@return 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(InputStream in) throws IOException { - return ModuleDescriptor.read(in).name(); - } - /** * {@return 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), @@ -253,31 +245,6 @@ class PathModularization { } } - /** - * {@return whether module hierarchy was detected}. If {@code 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; - } - - /** - * {@return 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. - * - * <p>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.</p> - */ - @Nonnull - public Map<Path, String> getModuleNames() { - return descriptors; - } - /** * {@return whether the dependency contains a module of the given name}. */