Skip to content

Commit 80932d4

Browse files
committed
Add getModuleName(Path) and getModuleDescription(Path) methods in DependencyResolverResult.
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.
1 parent cf2934b commit 80932d4

File tree

3 files changed

+93
-64
lines changed

3 files changed

+93
-64
lines changed

api/maven-api-core/src/main/java/org/apache/maven/api/services/DependencyResolverResult.java

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,12 +18,15 @@
1818
*/
1919
package org.apache.maven.api.services;
2020

21+
import java.io.IOException;
22+
import java.lang.module.ModuleDescriptor;
2123
import java.nio.file.Path;
2224
import java.util.List;
2325
import java.util.Map;
2426
import java.util.Optional;
2527

2628
import org.apache.maven.api.Dependency;
29+
import org.apache.maven.api.DependencyScope;
2730
import org.apache.maven.api.JavaPathType;
2831
import org.apache.maven.api.Node;
2932
import org.apache.maven.api.PathType;
@@ -100,6 +103,38 @@ public interface DependencyResolverResult {
100103
@Nonnull
101104
Map<Dependency, Path> getDependencies();
102105

106+
/**
107+
* Returns the Java module name of the dependency at the given path.
108+
* The given dependency should be one of the paths returned by {@link #getDependencies()}.
109+
* The module name is extracted from the {@code module-info.class} file if present, otherwise from
110+
* the {@code "Automatic-Module-Name"} attribute of the {@code META-INF/MANIFEST.MF} file if present.
111+
*
112+
* <p>A typical usage is to invoke this method for all dependencies having a
113+
* {@link DependencyScope#TEST TEST} or {@link DependencyScope#TEST_ONLY TEST_ONLY}
114+
* {@linkplain Dependency#getScope() scope}. An {@code --add-reads} option may need
115+
* to be generated for compiling and running the test classes that use such dependencies.</p>
116+
*
117+
* @param dependency path to the dependency for which to get the module name
118+
* @return module name of the dependency at the given path, or empty if the dependency is not modular
119+
* @throws IOException if the module information of the specified dependency cannot be read
120+
*/
121+
Optional<String> getModuleName(@Nonnull Path dependency) throws IOException;
122+
123+
/**
124+
* Returns the Java module descriptor of the dependency at the given path.
125+
* The given dependency should be one of the paths returned by {@link #getDependencies()}.
126+
* The module descriptor is extracted from the {@code module-info.class} file if present.
127+
*
128+
* <p>{@link #getModuleName(Path)} is preferred when only the module name is desired,
129+
* because a name may be present even if the descriptor is absent. This method is for
130+
* cases when more information is desired, such as the set of exported packages.</p>
131+
*
132+
* @param dependency path to the dependency for which to get the module name
133+
* @return module name of the dependency at the given path, or empty if the dependency is not modular
134+
* @throws IOException if the module information of the specified dependency cannot be read
135+
*/
136+
Optional<ModuleDescriptor> getModuleDescriptor(@Nonnull Path dependency) throws IOException;
137+
103138
/**
104139
* If the module-path contains at least one filename-based auto-module, prepares a warning message.
105140
* The module path is the collection of dependencies associated to {@link JavaPathType#MODULES}.

maven-api-impl/src/main/java/org/apache/maven/internal/impl/DefaultDependencyResolverResult.java

Lines changed: 38 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
package org.apache.maven.internal.impl;
2020

2121
import java.io.IOException;
22+
import java.lang.module.ModuleDescriptor;
2223
import java.nio.file.Files;
2324
import java.nio.file.Path;
2425
import java.util.ArrayList;
@@ -154,7 +155,7 @@ private void addPathElement(PathType type, Path path) {
154155
* @param test the test output directory, or {@code null} if none
155156
* @throws IOException if an error occurred while reading module information
156157
*
157-
* TODO: this is currently not called
158+
* TODO: this is currently not called. This is intended for use by Surefire and may move there.
158159
*/
159160
void addOutputDirectory(Path main, Path test) throws IOException {
160161
if (outputModules != null) {
@@ -169,8 +170,9 @@ void addOutputDirectory(Path main, Path test) throws IOException {
169170
if (test != null) {
170171
boolean addToClasspath = true;
171172
PathModularization testModules = cache.getModuleInfo(test);
172-
boolean isModuleHierarchy = outputModules.isModuleHierarchy() || testModules.isModuleHierarchy();
173-
for (String moduleName : outputModules.getModuleNames().values()) {
173+
boolean isModuleHierarchy = outputModules.isModuleHierarchy || testModules.isModuleHierarchy;
174+
for (Object value : outputModules.descriptors.values()) {
175+
String moduleName = name(value);
174176
Path subdir = test;
175177
if (isModuleHierarchy) {
176178
// If module hierarchy is used, the directory names shall be the module names.
@@ -189,8 +191,8 @@ void addOutputDirectory(Path main, Path test) throws IOException {
189191
* If the test output directory provides some modules of its own, add them.
190192
* Except for this unusual case, tests should never be added to the module-path.
191193
*/
192-
for (Map.Entry<Path, String> entry : testModules.getModuleNames().entrySet()) {
193-
if (!outputModules.containsModule(entry.getValue())) {
194+
for (Map.Entry<Path, Object> entry : testModules.descriptors.entrySet()) {
195+
if (!outputModules.containsModule(name(entry.getValue()))) {
194196
addPathElement(JavaPathType.MODULES, entry.getKey());
195197
addToClasspath = false;
196198
}
@@ -246,9 +248,9 @@ void addDependency(Node node, Dependency dep, Predicate<PathType> filter, Path p
246248
outputModules = PathModularization.NONE;
247249
}
248250
PathType type = null;
249-
for (Map.Entry<Path, String> info :
250-
cache.getModuleInfo(path).getModuleNames().entrySet()) {
251-
String moduleName = info.getValue();
251+
for (Map.Entry<Path, Object> info :
252+
cache.getModuleInfo(path).descriptors.entrySet()) {
253+
String moduleName = name(info.getValue());
252254
type = JavaPathType.patchModule(moduleName);
253255
if (!containsModule(moduleName)) {
254256
/*
@@ -269,9 +271,9 @@ void addDependency(Node node, Dependency dep, Predicate<PathType> filter, Path p
269271
if (type == null) {
270272
Path main = findArtifactPath(dep.getGroupId(), dep.getArtifactId());
271273
if (main != null) {
272-
for (Map.Entry<Path, String> info :
273-
cache.getModuleInfo(main).getModuleNames().entrySet()) {
274-
type = JavaPathType.patchModule(info.getValue());
274+
for (Map.Entry<Path, Object> info :
275+
cache.getModuleInfo(main).descriptors.entrySet()) {
276+
type = JavaPathType.patchModule(name(info.getValue()));
275277
addPathElement(type, info.getKey());
276278
// There is usually no more than one element, but nevertheless allow multi-modules.
277279
}
@@ -360,6 +362,31 @@ public Map<Dependency, Path> getDependencies() {
360362
return dependencies;
361363
}
362364

365+
@Override
366+
public Optional<ModuleDescriptor> getModuleDescriptor(Path dependency) throws IOException {
367+
Object value = cache.getModuleInfo(dependency).descriptors.get(dependency);
368+
return (value instanceof ModuleDescriptor) ? Optional.of((ModuleDescriptor) value) : Optional.empty();
369+
}
370+
371+
@Override
372+
public Optional<String> getModuleName(Path dependency) throws IOException {
373+
return Optional.ofNullable(
374+
name(cache.getModuleInfo(dependency).descriptors.get(dependency)));
375+
}
376+
377+
/**
378+
* Returns the module name for the given value of the {@link PathModularization#descriptors} map.
379+
*/
380+
private static String name(final Object value) {
381+
if (value instanceof String) {
382+
return (String) value;
383+
} else if (value instanceof ModuleDescriptor) {
384+
return ((ModuleDescriptor) value).name();
385+
} else {
386+
return null;
387+
}
388+
}
389+
363390
@Override
364391
public Optional<String> warningForFilenameBasedAutomodules() {
365392
try {

maven-api-impl/src/main/java/org/apache/maven/internal/impl/PathModularization.java

Lines changed: 20 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -70,23 +70,24 @@ class PathModularization {
7070
* It may however contain more than one entry if module hierarchy was detected,
7171
* in which case there is one key per sub-directory.
7272
*
73+
* <p>Values are instances of either {@link ModuleDescriptor} or {@link String}.
74+
* The latter case happens when a JAR file has no {@code module-info.class} entry
75+
* but has an automatic name declared in {@code META-INF/MANIFEST.MF}.</p>
76+
*
7377
* <p>This map may contain null values if the constructor was invoked with {@code resolve}
7478
* parameter set to false. This is more efficient when only the module existence needs to
7579
* be tested, and module descriptors are not needed.</p>
76-
*
77-
* @see #getModuleNames()
7880
*/
79-
private final Map<Path, String> descriptors;
81+
@Nonnull
82+
final Map<Path, Object> descriptors;
8083

8184
/**
8285
* Whether module hierarchy was detected. If false, then package hierarchy is assumed.
8386
* In a package hierarchy, the {@linkplain #descriptors} map has either zero or one entry.
8487
* In a module hierarchy, the descriptors map may have an arbitrary number of entries,
8588
* including one (so the map size cannot be used as a criterion).
86-
*
87-
* @see #isModuleHierarchy()
8889
*/
89-
private final boolean isModuleHierarchy;
90+
final boolean isModuleHierarchy;
9091

9192
/**
9293
* Constructs an empty instance for non-modular dependencies.
@@ -144,13 +145,13 @@ private PathModularization() {
144145
*/
145146
Path file = path.resolve(MODULE_INFO);
146147
if (Files.isRegularFile(file)) {
147-
String name = null;
148+
ModuleDescriptor descriptor = null;
148149
if (resolve) {
149150
try (InputStream in = Files.newInputStream(file)) {
150-
name = getModuleName(in);
151+
descriptor = ModuleDescriptor.read(in);
151152
}
152153
}
153-
descriptors = Collections.singletonMap(file, name);
154+
descriptors = Collections.singletonMap(file, descriptor);
154155
isModuleHierarchy = false;
155156
return;
156157
}
@@ -160,27 +161,27 @@ private PathModularization() {
160161
* source files.
161162
*/
162163
if (Files.isDirectory(file)) {
163-
Map<Path, String> names = new HashMap<>();
164+
var multi = new HashMap<Path, ModuleDescriptor>();
164165
try (Stream<Path> subdirs = Files.list(file)) {
165166
subdirs.filter(Files::isDirectory).forEach((subdir) -> {
166167
Path mf = subdir.resolve(MODULE_INFO);
167168
if (Files.isRegularFile(mf)) {
168-
String name = null;
169+
ModuleDescriptor descriptor = null;
169170
if (resolve) {
170171
try (InputStream in = Files.newInputStream(mf)) {
171-
name = getModuleName(in);
172+
descriptor = ModuleDescriptor.read(in);
172173
} catch (IOException e) {
173174
throw new UncheckedIOException(e);
174175
}
175176
}
176-
names.put(mf, name);
177+
multi.put(mf, descriptor);
177178
}
178179
});
179180
} catch (UncheckedIOException e) {
180181
throw e.getCause();
181182
}
182-
if (!names.isEmpty()) {
183-
descriptors = Collections.unmodifiableMap(names);
183+
if (!multi.isEmpty()) {
184+
descriptors = Collections.unmodifiableMap(multi);
184185
isModuleHierarchy = true;
185186
return;
186187
}
@@ -194,13 +195,13 @@ private PathModularization() {
194195
try (JarFile jar = new JarFile(path.toFile())) {
195196
ZipEntry entry = jar.getEntry(MODULE_INFO);
196197
if (entry != null) {
197-
String name = null;
198+
ModuleDescriptor descriptor = null;
198199
if (resolve) {
199200
try (InputStream in = jar.getInputStream(entry)) {
200-
name = getModuleName(in);
201+
descriptor = ModuleDescriptor.read(in);
201202
}
202203
}
203-
descriptors = Collections.singletonMap(path, name);
204+
descriptors = Collections.singletonMap(path, descriptor);
204205
isModuleHierarchy = false;
205206
return;
206207
}
@@ -209,7 +210,7 @@ private PathModularization() {
209210
if (mf != null) {
210211
Object name = mf.getMainAttributes().get(AUTO_MODULE_NAME);
211212
if (name instanceof String) {
212-
descriptors = Collections.singletonMap(path, (String) name);
213+
descriptors = Collections.singletonMap(path, name);
213214
isModuleHierarchy = false;
214215
return;
215216
}
@@ -220,15 +221,6 @@ private PathModularization() {
220221
isModuleHierarchy = false;
221222
}
222223

223-
/**
224-
* {@return the module name declared in the given {@code module-info} descriptor}.
225-
* The input stream may be for a file or for an entry in a JAR file.
226-
*/
227-
@Nonnull
228-
private static String getModuleName(InputStream in) throws IOException {
229-
return ModuleDescriptor.read(in).name();
230-
}
231-
232224
/**
233225
* {@return the type of path detected}. The return value is {@link JavaPathType#MODULES}
234226
* if the dependency is a modular JAR file or a directory containing module descriptor(s),
@@ -253,31 +245,6 @@ public void addIfFilenameBasedAutomodules(Collection<String> automodulesDetected
253245
}
254246
}
255247

256-
/**
257-
* {@return whether module hierarchy was detected}. If {@code false}, then package hierarchy is assumed.
258-
* In a package hierarchy, the {@linkplain #getModuleNames()} map of modules has either zero or one entry.
259-
* In a module hierarchy, the descriptors map may have an arbitrary number of entries,
260-
* including one (so the map size cannot be used as a criterion).
261-
*/
262-
public boolean isModuleHierarchy() {
263-
return isModuleHierarchy;
264-
}
265-
266-
/**
267-
* {@return the module names for the path specified at construction time}.
268-
* This map is usually either empty if no module was found, or a singleton map.
269-
* It may however contain more than one entry if module hierarchy was detected,
270-
* in which case there is one key per sub-directory.
271-
*
272-
* <p>This map may contain null values if the constructor was invoked with {@code resolve}
273-
* parameter set to false. This is more efficient when only the module existence needs to
274-
* be tested, and module descriptors are not needed.</p>
275-
*/
276-
@Nonnull
277-
public Map<Path, String> getModuleNames() {
278-
return descriptors;
279-
}
280-
281248
/**
282249
* {@return whether the dependency contains a module of the given name}.
283250
*/

0 commit comments

Comments
 (0)