38 import java.nio.file.NoSuchFileException; |
38 import java.nio.file.NoSuchFileException; |
39 import java.nio.file.Path; |
39 import java.nio.file.Path; |
40 import java.nio.file.attribute.BasicFileAttributes; |
40 import java.nio.file.attribute.BasicFileAttributes; |
41 import java.util.Collections; |
41 import java.util.Collections; |
42 import java.util.HashMap; |
42 import java.util.HashMap; |
43 import java.util.HashSet; |
43 import java.util.LinkedHashSet; |
44 import java.util.Map; |
44 import java.util.Map; |
45 import java.util.Objects; |
45 import java.util.Objects; |
46 import java.util.Optional; |
46 import java.util.Optional; |
47 import java.util.Set; |
47 import java.util.Set; |
48 import java.util.jar.Attributes; |
48 import java.util.jar.Attributes; |
50 import java.util.jar.JarFile; |
50 import java.util.jar.JarFile; |
51 import java.util.jar.Manifest; |
51 import java.util.jar.Manifest; |
52 import java.util.regex.Matcher; |
52 import java.util.regex.Matcher; |
53 import java.util.regex.Pattern; |
53 import java.util.regex.Pattern; |
54 import java.util.stream.Collectors; |
54 import java.util.stream.Collectors; |
55 import java.util.stream.Stream; |
|
56 import java.util.zip.ZipEntry; |
55 import java.util.zip.ZipEntry; |
57 import java.util.zip.ZipFile; |
56 import java.util.zip.ZipFile; |
58 |
57 |
59 import jdk.internal.module.Checks; |
58 import jdk.internal.module.Checks; |
60 import jdk.internal.module.ConfigurableModuleFinder; |
59 import jdk.internal.module.ConfigurableModuleFinder; |
188 // directory of modules |
187 // directory of modules |
189 return scanDirectory(entry); |
188 return scanDirectory(entry); |
190 } |
189 } |
191 } |
190 } |
192 |
191 |
193 if (attrs.isRegularFile() || attrs.isDirectory()) { |
192 // packaged or exploded module |
194 // packaged or exploded module |
193 ModuleReference mref = readModule(entry, attrs); |
195 ModuleReference mref = readModule(entry, attrs); |
194 if (mref != null) { |
196 if (mref != null) { |
195 String name = mref.descriptor().name(); |
197 String name = mref.descriptor().name(); |
196 return Collections.singletonMap(name, mref); |
198 return Collections.singletonMap(name, mref); |
197 } else { |
199 } |
198 // skipped |
200 } |
199 return Collections.emptyMap(); |
201 |
200 } |
202 // not recognized |
|
203 throw new FindException("Unrecognized module: " + entry); |
|
204 |
201 |
205 } catch (IOException ioe) { |
202 } catch (IOException ioe) { |
206 throw new FindException(ioe); |
203 throw new FindException(ioe); |
207 } |
204 } |
208 } |
205 } |
236 |
233 |
237 ModuleReference mref = readModule(entry, attrs); |
234 ModuleReference mref = readModule(entry, attrs); |
238 |
235 |
239 // module found |
236 // module found |
240 if (mref != null) { |
237 if (mref != null) { |
241 |
|
242 // can have at most one version of a module in the directory |
238 // can have at most one version of a module in the directory |
243 String name = mref.descriptor().name(); |
239 String name = mref.descriptor().name(); |
244 if (nameToReference.put(name, mref) != null) { |
240 if (nameToReference.put(name, mref) != null) { |
245 throw new FindException("Two versions of module " |
241 throw new FindException("Two versions of module " |
246 + name + " found in " + dir); |
242 + name + " found in " + dir); |
247 } |
243 } |
248 |
|
249 } |
244 } |
250 |
|
251 } |
245 } |
252 } |
246 } |
253 |
247 |
254 return nameToReference; |
248 return nameToReference; |
255 } |
249 } |
256 |
250 |
257 |
251 |
258 /** |
252 /** |
259 * Locates a packaged or exploded module, returning a {@code ModuleReference} |
253 * Locates a packaged or exploded module, returning a {@code ModuleReference} |
260 * to the module. Returns {@code null} if the module is not recognized |
254 * to the module. Returns {@code null} if the entry is skipped because it is |
261 * as a packaged or exploded module. |
255 * to a directory that does not contain a module-info.class or it's a hidden |
|
256 * file. |
262 * |
257 * |
263 * @throws IOException if an I/O error occurs |
258 * @throws IOException if an I/O error occurs |
264 * @throws FindException if an error occurs parsing the module descriptor |
259 * @throws FindException if the file is not recognized as a module or an |
|
260 * error occurs parsing its module descriptor |
265 */ |
261 */ |
266 private ModuleReference readModule(Path entry, BasicFileAttributes attrs) |
262 private ModuleReference readModule(Path entry, BasicFileAttributes attrs) |
267 throws IOException |
263 throws IOException |
268 { |
264 { |
269 try { |
265 try { |
270 |
266 |
271 ModuleReference mref = null; |
|
272 if (attrs.isDirectory()) { |
267 if (attrs.isDirectory()) { |
273 mref = readExplodedModule(entry); |
268 return readExplodedModule(entry); // may return null |
274 } if (attrs.isRegularFile()) { |
269 } |
275 if (entry.toString().endsWith(".jar")) { |
270 |
276 mref = readJar(entry); |
271 String fn = entry.getFileName().toString(); |
277 } else if (isLinkPhase && entry.toString().endsWith(".jmod")) { |
272 if (attrs.isRegularFile()) { |
278 mref = readJMod(entry); |
273 if (fn.endsWith(".jar")) { |
|
274 return readJar(entry); |
|
275 } else if (fn.endsWith(".jmod")) { |
|
276 if (isLinkPhase) |
|
277 return readJMod(entry); |
|
278 throw new FindException("JMOD files not supported: " + entry); |
279 } |
279 } |
280 } |
280 } |
281 return mref; |
281 |
|
282 // skip hidden files |
|
283 if (fn.startsWith(".") || Files.isHidden(entry)) { |
|
284 return null; |
|
285 } else { |
|
286 throw new FindException("Unrecognized module: " + entry); |
|
287 } |
282 |
288 |
283 } catch (InvalidModuleDescriptorException e) { |
289 } catch (InvalidModuleDescriptorException e) { |
284 throw new FindException("Error reading module: " + entry, e); |
290 throw new FindException("Error reading module: " + entry, e); |
285 } |
291 } |
286 } |
292 } |
290 |
296 |
291 private Set<String> jmodPackages(ZipFile zf) { |
297 private Set<String> jmodPackages(ZipFile zf) { |
292 return zf.stream() |
298 return zf.stream() |
293 .filter(e -> e.getName().startsWith("classes/") && |
299 .filter(e -> e.getName().startsWith("classes/") && |
294 e.getName().endsWith(".class")) |
300 e.getName().endsWith(".class")) |
295 .map(e -> toPackageName(e)) |
301 .map(e -> toPackageName(e.getName().substring(8))) |
296 .filter(pkg -> pkg.length() > 0) // module-info |
302 .filter(pkg -> pkg.length() > 0) // module-info |
297 .distinct() |
|
298 .collect(Collectors.toSet()); |
303 .collect(Collectors.toSet()); |
299 } |
304 } |
300 |
305 |
301 /** |
306 /** |
302 * Returns a {@code ModuleReference} to a module in jmod file on the |
307 * Returns a {@code ModuleReference} to a module in jmod file on the |
303 * file system. |
308 * file system. |
|
309 * |
|
310 * @throws IOException |
|
311 * @throws InvalidModuleDescriptorException |
304 */ |
312 */ |
305 private ModuleReference readJMod(Path file) throws IOException { |
313 private ModuleReference readJMod(Path file) throws IOException { |
306 try (ZipFile zf = new ZipFile(file.toString())) { |
314 try (ZipFile zf = new ZipFile(file.toString())) { |
307 ZipEntry ze = zf.getEntry("classes/" + MODULE_INFO); |
315 ZipEntry ze = zf.getEntry("classes/" + MODULE_INFO); |
308 if (ze == null) { |
316 if (ze == null) { |
417 if (vs != null) |
425 if (vs != null) |
418 builder.version(vs); |
426 builder.version(vs); |
419 |
427 |
420 // scan the entries in the JAR file to locate the .class and service |
428 // scan the entries in the JAR file to locate the .class and service |
421 // configuration file |
429 // configuration file |
422 Stream<String> stream = jf.stream() |
430 Map<Boolean, Set<String>> map = |
423 .map(e -> e.getName()) |
431 jf.stream() |
424 .filter(e -> (e.endsWith(".class") || e.startsWith(SERVICES_PREFIX))) |
432 .map(JarEntry::getName) |
425 .distinct(); |
433 .filter(s -> (s.endsWith(".class") ^ s.startsWith(SERVICES_PREFIX))) |
426 Map<Boolean, Set<String>> map |
434 .collect(Collectors.partitioningBy(s -> s.endsWith(".class"), |
427 = stream.collect(Collectors.partitioningBy(s -> s.endsWith(".class"), |
435 Collectors.toSet())); |
428 Collectors.toSet())); |
|
429 Set<String> classFiles = map.get(Boolean.TRUE); |
436 Set<String> classFiles = map.get(Boolean.TRUE); |
430 Set<String> configFiles = map.get(Boolean.FALSE); |
437 Set<String> configFiles = map.get(Boolean.FALSE); |
431 |
438 |
432 // all packages are exported |
439 // all packages are exported |
433 classFiles.stream() |
440 classFiles.stream() |
434 .map(c -> toPackageName(c)) |
441 .map(c -> toPackageName(c)) |
435 .distinct() |
442 .distinct() |
436 .forEach(p -> builder.exports(p)); |
443 .forEach(builder::exports); |
437 |
444 |
438 // map names of service configuration files to service names |
445 // map names of service configuration files to service names |
439 Set<String> serviceNames = configFiles.stream() |
446 Set<String> serviceNames = configFiles.stream() |
440 .map(this::toServiceName) |
447 .map(this::toServiceName) |
441 .filter(Optional::isPresent) |
448 .flatMap(Optional::stream) |
442 .map(Optional::get) |
|
443 .collect(Collectors.toSet()); |
449 .collect(Collectors.toSet()); |
444 |
450 |
445 // parse each service configuration file |
451 // parse each service configuration file |
446 for (String sn : serviceNames) { |
452 for (String sn : serviceNames) { |
447 JarEntry entry = jf.getJarEntry(SERVICES_PREFIX + sn); |
453 JarEntry entry = jf.getJarEntry(SERVICES_PREFIX + sn); |
448 Set<String> providerClasses = new HashSet<>(); |
454 Set<String> providerClasses = new LinkedHashSet<>(); |
449 try (InputStream in = jf.getInputStream(entry)) { |
455 try (InputStream in = jf.getInputStream(entry)) { |
450 BufferedReader reader |
456 BufferedReader reader |
451 = new BufferedReader(new InputStreamReader(in, "UTF-8")); |
457 = new BufferedReader(new InputStreamReader(in, "UTF-8")); |
452 String cn; |
458 String cn; |
453 while ((cn = nextLine(reader)) != null) { |
459 while ((cn = nextLine(reader)) != null) { |
473 } |
479 } |
474 |
480 |
475 private Set<String> jarPackages(JarFile jf) { |
481 private Set<String> jarPackages(JarFile jf) { |
476 return jf.stream() |
482 return jf.stream() |
477 .filter(e -> e.getName().endsWith(".class")) |
483 .filter(e -> e.getName().endsWith(".class")) |
478 .map(e -> toPackageName(e)) |
484 .map(e -> toPackageName(e.getName())) |
479 .filter(pkg -> pkg.length() > 0) // module-info |
485 .filter(pkg -> pkg.length() > 0) // module-info |
480 .distinct() |
|
481 .collect(Collectors.toSet()); |
486 .collect(Collectors.toSet()); |
482 } |
487 } |
483 |
488 |
484 /** |
489 /** |
485 * Returns a {@code ModuleReference} to a module in modular JAR file on |
490 * Returns a {@code ModuleReference} to a module in modular JAR file on |
486 * the file system. |
491 * the file system. |
|
492 * |
|
493 * @throws IOException |
|
494 * @throws FindException |
|
495 * @throws InvalidModuleDescriptorException |
487 */ |
496 */ |
488 private ModuleReference readJar(Path file) throws IOException { |
497 private ModuleReference readJar(Path file) throws IOException { |
489 try (JarFile jf = new JarFile(file.toString())) { |
498 try (JarFile jf = new JarFile(file.toFile(), |
490 |
499 true, // verify |
|
500 ZipFile.OPEN_READ, |
|
501 JarFile.Release.RUNTIME)) |
|
502 { |
491 ModuleDescriptor md; |
503 ModuleDescriptor md; |
492 JarEntry entry = jf.getJarEntry(MODULE_INFO); |
504 JarEntry entry = jf.getJarEntry(MODULE_INFO); |
493 if (entry == null) { |
505 if (entry == null) { |
494 |
506 |
495 // no module-info.class so treat it as automatic module |
507 // no module-info.class so treat it as automatic module |
518 return Files.find(dir, Integer.MAX_VALUE, |
530 return Files.find(dir, Integer.MAX_VALUE, |
519 ((path, attrs) -> attrs.isRegularFile() && |
531 ((path, attrs) -> attrs.isRegularFile() && |
520 path.toString().endsWith(".class"))) |
532 path.toString().endsWith(".class"))) |
521 .map(path -> toPackageName(dir.relativize(path))) |
533 .map(path -> toPackageName(dir.relativize(path))) |
522 .filter(pkg -> pkg.length() > 0) // module-info |
534 .filter(pkg -> pkg.length() > 0) // module-info |
523 .distinct() |
|
524 .collect(Collectors.toSet()); |
535 .collect(Collectors.toSet()); |
525 } catch (IOException x) { |
536 } catch (IOException x) { |
526 throw new UncheckedIOException(x); |
537 throw new UncheckedIOException(x); |
527 } |
538 } |
528 } |
539 } |
529 |
540 |
530 /** |
541 /** |
531 * Returns a {@code ModuleReference} to an exploded module on the file |
542 * Returns a {@code ModuleReference} to an exploded module on the file |
532 * system or {@code null} if {@code module-info.class} not found. |
543 * system or {@code null} if {@code module-info.class} not found. |
|
544 * |
|
545 * @throws IOException |
|
546 * @throws InvalidModuleDescriptorException |
533 */ |
547 */ |
534 private ModuleReference readExplodedModule(Path dir) throws IOException { |
548 private ModuleReference readExplodedModule(Path dir) throws IOException { |
535 Path mi = dir.resolve(MODULE_INFO); |
549 Path mi = dir.resolve(MODULE_INFO); |
536 ModuleDescriptor md; |
550 ModuleDescriptor md; |
537 try (InputStream in = Files.newInputStream(mi)) { |
551 try (InputStream in = Files.newInputStream(mi)) { |
557 } else { |
571 } else { |
558 return ""; |
572 return ""; |
559 } |
573 } |
560 } |
574 } |
561 |
575 |
562 private String toPackageName(ZipEntry entry) { |
|
563 String name = entry.getName(); |
|
564 assert name.endsWith(".class"); |
|
565 // jmod classes in classes/, jar in / |
|
566 int start = name.startsWith("classes/") ? 8 : 0; |
|
567 int index = name.lastIndexOf("/"); |
|
568 if (index > start) { |
|
569 return name.substring(start, index).replace('/', '.'); |
|
570 } else { |
|
571 return ""; |
|
572 } |
|
573 } |
|
574 |
|
575 private String toPackageName(Path path) { |
576 private String toPackageName(Path path) { |
576 String name = path.toString(); |
577 String name = path.toString(); |
577 assert name.endsWith(".class"); |
578 assert name.endsWith(".class"); |
578 int index = name.lastIndexOf(File.separatorChar); |
579 int index = name.lastIndexOf(File.separatorChar); |
579 if (index != -1) { |
580 if (index != -1) { |