1 package org.codehaus.plexus.languages.java.jpms;
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22 import javax.inject.Named;
23 import javax.inject.Singleton;
24
25 import java.io.File;
26 import java.io.IOException;
27 import java.nio.file.Files;
28 import java.nio.file.Path;
29 import java.nio.file.Paths;
30 import java.util.Collections;
31 import java.util.HashMap;
32 import java.util.HashSet;
33 import java.util.LinkedHashMap;
34 import java.util.Map;
35 import java.util.Map.Entry;
36 import java.util.Set;
37
38 import org.codehaus.plexus.languages.java.jpms.JavaModuleDescriptor.JavaProvides;
39
40
41
42
43
44
45
46 @Named
47 @Singleton
48 public class LocationManager {
49 private SourceModuleInfoParser sourceParser;
50
51 private ManifestModuleNameExtractor manifestModuleNameExtractor;
52
53 public LocationManager() {
54 this.sourceParser = new SourceModuleInfoParser();
55 this.manifestModuleNameExtractor = new ManifestModuleNameExtractor();
56 }
57
58 LocationManager(SourceModuleInfoParser sourceParser) {
59 this.sourceParser = sourceParser;
60 this.manifestModuleNameExtractor = new ManifestModuleNameExtractor();
61 }
62
63
64
65
66
67
68 public ResolvePathResult parseModuleDescriptor(Path descriptorPath) throws IOException {
69 JavaModuleDescriptor moduleDescriptor;
70 if (descriptorPath.endsWith("module-info.java")) {
71 moduleDescriptor = sourceParser.fromSourcePath(descriptorPath);
72 } else {
73 throw new IOException("Invalid path to module descriptor: " + descriptorPath);
74 }
75 return new ResolvePathResult()
76 .setModuleDescriptor(moduleDescriptor)
77 .setModuleNameSource(ModuleNameSource.MODULEDESCRIPTOR);
78 }
79
80
81
82
83
84
85 public ResolvePathResult parseModuleDescriptor(File descriptorPath) throws IOException {
86 return parseModuleDescriptor(descriptorPath.toPath());
87 }
88
89
90
91
92
93
94 public ResolvePathResult parseModuleDescriptor(String descriptorPath) throws IOException {
95 return parseModuleDescriptor(Paths.get(descriptorPath));
96 }
97
98
99
100
101
102
103
104
105 public <T> ResolvePathResult resolvePath(final ResolvePathRequest<T> request) throws IOException {
106 ModuleNameExtractor filenameExtractor = new ModuleNameExtractor() {
107 MainClassModuleNameExtractor extractor = new MainClassModuleNameExtractor(request.getJdkHome());
108
109 @Override
110 public String extract(Path file) throws IOException {
111 if (request.getJdkHome() != null) {
112 return extractor
113 .extract(Collections.singletonMap(file, file))
114 .get(file);
115 } else {
116 return CmdModuleNameExtractor.getModuleName(file);
117 }
118 }
119 };
120
121 return resolvePath(
122 request.toPath(request.getPathElement()),
123 filenameExtractor,
124 getBinaryModuleInfoParser(request.getJdkHome()));
125 }
126
127
128
129
130
131
132
133
134
135 public <T> ResolvePathsResult<T> resolvePaths(final ResolvePathsRequest<T> request) throws IOException {
136 final ResolvePathsResult<T> result = request.createResult();
137
138 Map<T, JavaModuleDescriptor> pathElements =
139 new LinkedHashMap<>(request.getPathElements().size());
140
141 final ModuleInfoParser binaryParser = getBinaryModuleInfoParser(request.getJdkHome());
142
143 JavaModuleDescriptor mainModuleDescriptor = getMainModuleDescriptor(request, binaryParser);
144
145 result.setMainModuleDescriptor(mainModuleDescriptor);
146
147
148 Map<String, Set<String>> availableProviders = new HashMap<>();
149
150 if (mainModuleDescriptor != null && request.isIncludeAllProviders()) {
151 collectProviders(mainModuleDescriptor, availableProviders);
152 }
153
154 Map<String, JavaModuleDescriptor> availableNamedModules = new HashMap<>();
155
156 Map<String, ModuleNameSource> moduleNameSources = new HashMap<>();
157
158 final Map<T, Path> filenameAutoModules = new HashMap<>();
159
160
161 for (final T t : request.getPathElements()) {
162 JavaModuleDescriptor moduleDescriptor;
163 ModuleNameSource source;
164
165 ModuleNameExtractor nameExtractor = path -> {
166 if (request.getJdkHome() != null) {
167 filenameAutoModules.put(t, path);
168 } else {
169 return CmdModuleNameExtractor.getModuleName(path);
170 }
171 return null;
172 };
173
174 try {
175 ResolvePathResult resolvedPath = resolvePath(request.toPath(t), nameExtractor, binaryParser);
176
177 moduleDescriptor = resolvedPath.getModuleDescriptor();
178
179 source = resolvedPath.getModuleNameSource();
180 } catch (Exception e) {
181 result.getPathExceptions().put(t, e);
182
183 pathElements.put(t, null);
184
185 continue;
186 }
187
188
189 if (moduleDescriptor != null && moduleNameSources.putIfAbsent(moduleDescriptor.name(), source) == null) {
190 availableNamedModules.put(moduleDescriptor.name(), moduleDescriptor);
191
192 if (request.isIncludeAllProviders()) {
193 collectProviders(moduleDescriptor, availableProviders);
194 }
195 }
196
197 pathElements.put(t, moduleDescriptor);
198 }
199 result.setPathElements(pathElements);
200
201 if (!filenameAutoModules.isEmpty()) {
202 MainClassModuleNameExtractor extractor = new MainClassModuleNameExtractor(request.getJdkHome());
203
204 Map<T, String> automodules = extractor.extract(filenameAutoModules);
205
206 for (Map.Entry<T, String> entry : automodules.entrySet()) {
207 String moduleName = entry.getValue();
208
209 if (moduleName != null) {
210 JavaModuleDescriptor moduleDescriptor =
211 JavaModuleDescriptor.newAutomaticModule(moduleName).build();
212
213 moduleNameSources.put(moduleDescriptor.name(), ModuleNameSource.FILENAME);
214
215 availableNamedModules.put(moduleDescriptor.name(), moduleDescriptor);
216
217 pathElements.put(entry.getKey(), moduleDescriptor);
218 }
219 }
220 }
221
222 Set<String> requiredNamedModules = new HashSet<>();
223
224 if (mainModuleDescriptor != null) {
225 requiredNamedModules.add(mainModuleDescriptor.name());
226
227 selectRequires(
228 mainModuleDescriptor,
229 Collections.unmodifiableMap(availableNamedModules),
230 Collections.unmodifiableMap(availableProviders),
231 requiredNamedModules,
232 true,
233 true,
234 request.isIncludeStatic());
235 }
236
237 for (String additionalModule : request.getAdditionalModules()) {
238 selectModule(
239 additionalModule,
240 Collections.unmodifiableMap(availableNamedModules),
241 Collections.unmodifiableMap(availableProviders),
242 requiredNamedModules,
243 true,
244 true,
245 request.isIncludeStatic());
246 }
247
248 Set<String> collectedModules = new HashSet<>(requiredNamedModules.size());
249
250 for (Entry<T, JavaModuleDescriptor> entry : pathElements.entrySet()) {
251 if (entry.getValue() != null
252 && requiredNamedModules.contains(entry.getValue().name())) {
253
254
255
256
257 if (collectedModules.add(entry.getValue().name())) {
258 result.getModulepathElements()
259 .put(
260 entry.getKey(),
261 moduleNameSources.get(entry.getValue().name()));
262 } else {
263 result.getPathExceptions()
264 .put(
265 entry.getKey(),
266 new IllegalStateException(
267 "Module '" + entry.getValue().name() + "' is already on the module path!"));
268 }
269 } else {
270 result.getClasspathElements().add(entry.getKey());
271 }
272 }
273
274 return result;
275 }
276
277
278
279
280
281
282
283
284 ModuleInfoParser getBinaryModuleInfoParser(final Path jdkHome) {
285 final ModuleInfoParser binaryParser;
286 if (jdkHome == null) {
287 binaryParser = new BinaryModuleInfoParser();
288 } else {
289 binaryParser = new AsmModuleInfoParser();
290 }
291 return binaryParser;
292 }
293
294 private <T> JavaModuleDescriptor getMainModuleDescriptor(
295 final ResolvePathsRequest<T> request, ModuleInfoParser binaryParser) throws IOException {
296 JavaModuleDescriptor mainModuleDescriptor;
297
298 Path descriptorPath = request.getMainModuleDescriptor();
299
300 if (descriptorPath != null) {
301 if (descriptorPath.endsWith("module-info.java")) {
302 mainModuleDescriptor = sourceParser.fromSourcePath(descriptorPath);
303 } else if (descriptorPath.endsWith("module-info.class")) {
304 mainModuleDescriptor = binaryParser.getModuleDescriptor(descriptorPath.getParent());
305 } else {
306 throw new IOException("Invalid path to module descriptor: " + descriptorPath);
307 }
308 } else {
309 mainModuleDescriptor = request.getModuleDescriptor();
310 }
311 return mainModuleDescriptor;
312 }
313
314 private ResolvePathResult resolvePath(
315 Path path, ModuleNameExtractor fileModulenameExtractor, ModuleInfoParser binaryParser) throws IOException {
316 ResolvePathResult result = new ResolvePathResult();
317
318 JavaModuleDescriptor moduleDescriptor = null;
319
320
321 if (Files.isRegularFile(path) && !path.getFileName().toString().endsWith(".jar")) {
322 throw new IllegalArgumentException(
323 "'" + path + "' not allowed on the path, only outputDirectories and jars are accepted");
324 }
325
326 if (Files.isRegularFile(path) || Files.exists(path.resolve("module-info.class"))) {
327 moduleDescriptor = binaryParser.getModuleDescriptor(path);
328 }
329
330 if (moduleDescriptor != null) {
331 result.setModuleNameSource(ModuleNameSource.MODULEDESCRIPTOR);
332 } else {
333 String moduleName = manifestModuleNameExtractor.extract(path);
334
335 if (moduleName != null) {
336 result.setModuleNameSource(ModuleNameSource.MANIFEST);
337 } else {
338 moduleName = fileModulenameExtractor.extract(path);
339
340 if (moduleName != null) {
341 result.setModuleNameSource(ModuleNameSource.FILENAME);
342 }
343 }
344
345 if (moduleName != null) {
346 moduleDescriptor =
347 JavaModuleDescriptor.newAutomaticModule(moduleName).build();
348 }
349 }
350 result.setModuleDescriptor(moduleDescriptor);
351
352 return result;
353 }
354
355 private void selectRequires(
356 JavaModuleDescriptor module,
357 Map<String, JavaModuleDescriptor> availableModules,
358 Map<String, Set<String>> availableProviders,
359 Set<String> namedModules,
360 boolean isRootModule,
361 boolean includeAsTransitive,
362 boolean includeStatic) {
363 for (JavaModuleDescriptor.JavaRequires requires : module.requires()) {
364
365 if (isRootModule
366 || includeStatic
367 || includeAsTransitive
368 || !requires.modifiers().contains(JavaModuleDescriptor.JavaRequires.JavaModifier.STATIC)
369 || requires.modifiers().contains(JavaModuleDescriptor.JavaRequires.JavaModifier.TRANSITIVE)) {
370 selectModule(
371 requires.name(),
372 availableModules,
373 availableProviders,
374 namedModules,
375 false,
376 includeStatic,
377 includeStatic);
378 }
379 }
380
381 for (String uses : module.uses()) {
382 if (availableProviders.containsKey(uses)) {
383 for (String providerModule : availableProviders.get(uses)) {
384 JavaModuleDescriptor requiredModule = availableModules.get(providerModule);
385
386 if (requiredModule != null && namedModules.add(providerModule)) {
387 selectRequires(
388 requiredModule,
389 availableModules,
390 availableProviders,
391 namedModules,
392 false,
393 includeAsTransitive,
394 includeStatic);
395 }
396 }
397 }
398 }
399 }
400
401 private void selectModule(
402 String module,
403 Map<String, JavaModuleDescriptor> availableModules,
404 Map<String, Set<String>> availableProviders,
405 Set<String> namedModules,
406 boolean isRootModule,
407 boolean includeTransitive,
408 boolean includeStatic) {
409 JavaModuleDescriptor requiredModule = availableModules.get(module);
410
411 if (requiredModule != null && namedModules.add(module)) {
412 selectRequires(
413 requiredModule,
414 availableModules,
415 availableProviders,
416 namedModules,
417 false,
418 includeTransitive,
419 includeStatic);
420 }
421 }
422
423 private void collectProviders(JavaModuleDescriptor moduleDescriptor, Map<String, Set<String>> availableProviders) {
424 for (JavaProvides provides : moduleDescriptor.provides()) {
425
426 final String serviceClassName = provides.service().replace('$', '.');
427
428 Set<String> providingModules = availableProviders.computeIfAbsent(serviceClassName, k -> new HashSet<>());
429
430 providingModules.add(moduleDescriptor.name());
431 }
432 }
433 }