31 import java.io.IOException; |
31 import java.io.IOException; |
32 import java.io.InputStream; |
32 import java.io.InputStream; |
33 import java.io.InputStreamReader; |
33 import java.io.InputStreamReader; |
34 import java.io.UncheckedIOException; |
34 import java.io.UncheckedIOException; |
35 import java.lang.module.ModuleDescriptor.Requires; |
35 import java.lang.module.ModuleDescriptor.Requires; |
|
36 import java.net.URI; |
36 import java.nio.file.DirectoryStream; |
37 import java.nio.file.DirectoryStream; |
37 import java.nio.file.Files; |
38 import java.nio.file.Files; |
38 import java.nio.file.NoSuchFileException; |
39 import java.nio.file.NoSuchFileException; |
39 import java.nio.file.Path; |
40 import java.nio.file.Path; |
|
41 import java.nio.file.Paths; |
40 import java.nio.file.attribute.BasicFileAttributes; |
42 import java.nio.file.attribute.BasicFileAttributes; |
41 import java.util.Collections; |
43 import java.util.Collections; |
42 import java.util.HashMap; |
44 import java.util.HashMap; |
43 import java.util.LinkedHashSet; |
45 import java.util.LinkedHashSet; |
44 import java.util.Map; |
46 import java.util.Map; |
50 import java.util.jar.JarFile; |
52 import java.util.jar.JarFile; |
51 import java.util.jar.Manifest; |
53 import java.util.jar.Manifest; |
52 import java.util.regex.Matcher; |
54 import java.util.regex.Matcher; |
53 import java.util.regex.Pattern; |
55 import java.util.regex.Pattern; |
54 import java.util.stream.Collectors; |
56 import java.util.stream.Collectors; |
55 import java.util.stream.Stream; |
|
56 import java.util.zip.ZipEntry; |
57 import java.util.zip.ZipEntry; |
57 import java.util.zip.ZipFile; |
58 import java.util.zip.ZipFile; |
58 |
59 |
59 import jdk.internal.jmod.JmodFile; |
60 import jdk.internal.jmod.JmodFile; |
60 import jdk.internal.jmod.JmodFile.Section; |
61 import jdk.internal.jmod.JmodFile.Section; |
61 import jdk.internal.module.ConfigurableModuleFinder; |
62 import jdk.internal.module.Checks; |
62 import jdk.internal.perf.PerfCounter; |
63 import jdk.internal.perf.PerfCounter; |
|
64 import jdk.internal.util.jar.VersionedStream; |
63 |
65 |
64 |
66 |
65 /** |
67 /** |
66 * A {@code ModuleFinder} that locates modules on the file system by searching |
68 * A {@code ModuleFinder} that locates modules on the file system by searching |
67 * a sequence of directories or packaged modules. |
69 * a sequence of directories or packaged modules. |
68 * |
70 * |
69 * The {@code ModuleFinder} can be configured to work in either the run-time |
71 * The {@code ModuleFinder} can be created to work in either the run-time |
70 * or link-time phases. In both cases it locates modular JAR and exploded |
72 * or link-time phases. In both cases it locates modular JAR and exploded |
71 * modules. When configured for link-time then it additionally locates |
73 * modules. When created for link-time then it additionally locates |
72 * modules in JMOD files. |
74 * modules in JMOD files. |
73 */ |
75 */ |
74 |
76 |
75 class ModulePath implements ConfigurableModuleFinder { |
77 class ModulePath implements ModuleFinder { |
76 private static final String MODULE_INFO = "module-info.class"; |
78 private static final String MODULE_INFO = "module-info.class"; |
|
79 |
|
80 // the version to use for multi-release modular JARs |
|
81 private final Runtime.Version releaseVersion; |
|
82 |
|
83 // true for the link phase (supports modules packaged in JMOD format) |
|
84 private final boolean isLinkPhase; |
77 |
85 |
78 // the entries on this module path |
86 // the entries on this module path |
79 private final Path[] entries; |
87 private final Path[] entries; |
80 private int next; |
88 private int next; |
81 |
89 |
82 // true if in the link phase |
|
83 private boolean isLinkPhase; |
|
84 |
|
85 // map of module name to module reference map for modules already located |
90 // map of module name to module reference map for modules already located |
86 private final Map<String, ModuleReference> cachedModules = new HashMap<>(); |
91 private final Map<String, ModuleReference> cachedModules = new HashMap<>(); |
87 |
92 |
88 ModulePath(Path... entries) { |
93 ModulePath(Runtime.Version version, boolean isLinkPhase, Path... entries) { |
|
94 this.releaseVersion = version; |
|
95 this.isLinkPhase = isLinkPhase; |
89 this.entries = entries.clone(); |
96 this.entries = entries.clone(); |
90 for (Path entry : this.entries) { |
97 for (Path entry : this.entries) { |
91 Objects.requireNonNull(entry); |
98 Objects.requireNonNull(entry); |
92 } |
99 } |
93 } |
100 } |
94 |
101 |
95 @Override |
102 ModulePath(Path... entries) { |
96 public void configurePhase(Phase phase) { |
103 this(JarFile.runtimeVersion(), false, entries); |
97 isLinkPhase = (phase == Phase.LINK_TIME); |
|
98 } |
104 } |
99 |
105 |
100 @Override |
106 @Override |
101 public Optional<ModuleReference> find(String name) { |
107 public Optional<ModuleReference> find(String name) { |
102 Objects.requireNonNull(name); |
108 Objects.requireNonNull(name); |
292 throw new FindException("Error reading module: " + entry, e); |
302 throw new FindException("Error reading module: " + entry, e); |
293 } |
303 } |
294 } |
304 } |
295 |
305 |
296 |
306 |
|
307 /** |
|
308 * Returns a string with the file name of the module if possible. |
|
309 * If the module location is not a file URI then return the URI |
|
310 * as a string. |
|
311 */ |
|
312 private String fileName(ModuleReference mref) { |
|
313 URI uri = mref.location().orElse(null); |
|
314 if (uri != null) { |
|
315 if (uri.getScheme().equalsIgnoreCase("file")) { |
|
316 Path file = Paths.get(uri); |
|
317 return file.getFileName().toString(); |
|
318 } else { |
|
319 return uri.toString(); |
|
320 } |
|
321 } else { |
|
322 return "<unknown>"; |
|
323 } |
|
324 } |
|
325 |
297 // -- jmod files -- |
326 // -- jmod files -- |
298 |
327 |
299 private Set<String> jmodPackages(JmodFile jf) { |
328 private Set<String> jmodPackages(JmodFile jf) { |
300 return jf.stream() |
329 return jf.stream() |
301 .filter(e -> e.section() == Section.CLASSES) |
330 .filter(e -> e.section() == Section.CLASSES) |
302 .map(JmodFile.Entry::name) |
331 .map(JmodFile.Entry::name) |
303 .map(this::toPackageName) |
332 .map(this::toPackageName) |
304 .filter(pkg -> pkg.length() > 0) // module-info |
333 .flatMap(Optional::stream) |
305 .collect(Collectors.toSet()); |
334 .collect(Collectors.toSet()); |
306 } |
335 } |
307 |
336 |
308 /** |
337 /** |
309 * Returns a {@code ModuleReference} to a module in jmod file on the |
338 * Returns a {@code ModuleReference} to a module in jmod file on the |
414 .automatic() |
444 .automatic() |
415 .requires(Set.of(Requires.Modifier.MANDATED), "java.base"); |
445 .requires(Set.of(Requires.Modifier.MANDATED), "java.base"); |
416 if (vs != null) |
446 if (vs != null) |
417 builder.version(vs); |
447 builder.version(vs); |
418 |
448 |
419 // scan the entries in the JAR file to locate the .class and service |
449 // scan the names of the entries in the JAR file |
420 // configuration file |
450 Map<Boolean, Set<String>> map = VersionedStream.stream(jf) |
421 Map<Boolean, Set<String>> map = |
451 .filter(e -> !e.isDirectory()) |
422 versionedStream(jf) |
452 .map(JarEntry::getName) |
423 .map(JarEntry::getName) |
453 .collect(Collectors.partitioningBy(e -> e.startsWith(SERVICES_PREFIX), |
424 .filter(s -> (s.endsWith(".class") ^ s.startsWith(SERVICES_PREFIX))) |
454 Collectors.toSet())); |
425 .collect(Collectors.partitioningBy(s -> s.endsWith(".class"), |
455 |
426 Collectors.toSet())); |
456 Set<String> resources = map.get(Boolean.FALSE); |
427 Set<String> classFiles = map.get(Boolean.TRUE); |
457 Set<String> configFiles = map.get(Boolean.TRUE); |
428 Set<String> configFiles = map.get(Boolean.FALSE); |
|
429 |
458 |
430 // all packages are exported |
459 // all packages are exported |
431 classFiles.stream() |
460 resources.stream() |
432 .map(c -> toPackageName(c)) |
461 .map(this::toPackageName) |
433 .distinct() |
462 .flatMap(Optional::stream) |
434 .forEach(builder::exports); |
463 .distinct() |
|
464 .forEach(builder::exports); |
435 |
465 |
436 // map names of service configuration files to service names |
466 // map names of service configuration files to service names |
437 Set<String> serviceNames = configFiles.stream() |
467 Set<String> serviceNames = configFiles.stream() |
438 .map(this::toServiceName) |
468 .map(this::toServiceName) |
439 .flatMap(Optional::stream) |
469 .flatMap(Optional::stream) |
440 .collect(Collectors.toSet()); |
470 .collect(Collectors.toSet()); |
441 |
471 |
442 // parse each service configuration file |
472 // parse each service configuration file |
443 for (String sn : serviceNames) { |
473 for (String sn : serviceNames) { |
444 JarEntry entry = jf.getJarEntry(SERVICES_PREFIX + sn); |
474 JarEntry entry = jf.getJarEntry(SERVICES_PREFIX + sn); |
445 Set<String> providerClasses = new LinkedHashSet<>(); |
475 Set<String> providerClasses = new LinkedHashSet<>(); |
500 mn = Patterns.TRAILING_DOTS.matcher(mn).replaceAll(""); |
530 mn = Patterns.TRAILING_DOTS.matcher(mn).replaceAll(""); |
501 |
531 |
502 return mn; |
532 return mn; |
503 } |
533 } |
504 |
534 |
505 private Stream<JarEntry> versionedStream(JarFile jf) { |
|
506 if (jf.isMultiRelease()) { |
|
507 // a stream of JarEntries whose names are base names and whose |
|
508 // contents are from the corresponding versioned entries in |
|
509 // a multi-release jar file |
|
510 return jf.stream().map(JarEntry::getName) |
|
511 .filter(name -> !name.startsWith("META-INF/versions/")) |
|
512 .map(jf::getJarEntry); |
|
513 } else { |
|
514 return jf.stream(); |
|
515 } |
|
516 } |
|
517 |
|
518 private Set<String> jarPackages(JarFile jf) { |
535 private Set<String> jarPackages(JarFile jf) { |
519 return versionedStream(jf) |
536 return VersionedStream.stream(jf) |
520 .filter(e -> e.getName().endsWith(".class")) |
537 .filter(e -> !e.isDirectory()) |
521 .map(e -> toPackageName(e.getName())) |
538 .map(JarEntry::getName) |
522 .filter(pkg -> pkg.length() > 0) // module-info |
539 .map(this::toPackageName) |
523 .collect(Collectors.toSet()); |
540 .flatMap(Optional::stream) |
|
541 .collect(Collectors.toSet()); |
524 } |
542 } |
525 |
543 |
526 /** |
544 /** |
527 * Returns a {@code ModuleReference} to a module in modular JAR file on |
545 * Returns a {@code ModuleReference} to a module in modular JAR file on |
528 * the file system. |
546 * the file system. |
563 // -- exploded directories -- |
581 // -- exploded directories -- |
564 |
582 |
565 private Set<String> explodedPackages(Path dir) { |
583 private Set<String> explodedPackages(Path dir) { |
566 try { |
584 try { |
567 return Files.find(dir, Integer.MAX_VALUE, |
585 return Files.find(dir, Integer.MAX_VALUE, |
568 ((path, attrs) -> attrs.isRegularFile() && |
586 ((path, attrs) -> attrs.isRegularFile())) |
569 path.toString().endsWith(".class"))) |
587 .map(path -> dir.relativize(path)) |
570 .map(path -> toPackageName(dir.relativize(path))) |
588 .map(this::toPackageName) |
571 .filter(pkg -> pkg.length() > 0) // module-info |
589 .flatMap(Optional::stream) |
572 .collect(Collectors.toSet()); |
590 .collect(Collectors.toSet()); |
573 } catch (IOException x) { |
591 } catch (IOException x) { |
574 throw new UncheckedIOException(x); |
592 throw new UncheckedIOException(x); |
575 } |
593 } |
576 } |
594 } |
577 |
595 |
593 return null; |
611 return null; |
594 } |
612 } |
595 return ModuleReferences.newExplodedModule(md, dir); |
613 return ModuleReferences.newExplodedModule(md, dir); |
596 } |
614 } |
597 |
615 |
598 |
616 /** |
599 // |
617 * Maps the name of an entry in a JAR or ZIP file to a package name. |
600 |
618 * |
601 // p/q/T.class => p.q |
619 * @throws IllegalArgumentException if the name is a class file in |
602 private String toPackageName(String cn) { |
620 * the top-level directory of the JAR/ZIP file (and it's |
603 assert cn.endsWith(".class"); |
621 * not module-info.class) |
604 int start = 0; |
622 */ |
605 int index = cn.lastIndexOf("/"); |
623 private Optional<String> toPackageName(String name) { |
606 if (index > start) { |
624 assert !name.endsWith("/"); |
607 return cn.substring(start, index).replace('/', '.'); |
625 |
|
626 int index = name.lastIndexOf("/"); |
|
627 if (index == -1) { |
|
628 if (name.endsWith(".class") && !name.equals(MODULE_INFO)) { |
|
629 throw new IllegalArgumentException(name |
|
630 + " found in top-level directory:" |
|
631 + " (unnamed package not allowed in module)"); |
|
632 } |
|
633 return Optional.empty(); |
|
634 } |
|
635 |
|
636 String pn = name.substring(0, index).replace('/', '.'); |
|
637 if (Checks.isJavaIdentifier(pn)) { |
|
638 return Optional.of(pn); |
608 } else { |
639 } else { |
609 return ""; |
640 // not a valid package name |
610 } |
641 return Optional.empty(); |
611 } |
642 } |
612 |
643 } |
613 private String toPackageName(Path path) { |
644 |
614 String name = path.toString(); |
645 /** |
615 assert name.endsWith(".class"); |
646 * Maps the relative path of an entry in an exploded module to a package |
616 int index = name.lastIndexOf(File.separatorChar); |
647 * name. |
617 if (index != -1) { |
648 * |
618 return name.substring(0, index).replace(File.separatorChar, '.'); |
649 * @throws IllegalArgumentException if the name is a class file in |
|
650 * the top-level directory (and it's not module-info.class) |
|
651 */ |
|
652 private Optional<String> toPackageName(Path file) { |
|
653 assert file.getRoot() == null; |
|
654 |
|
655 Path parent = file.getParent(); |
|
656 if (parent == null) { |
|
657 String name = file.toString(); |
|
658 if (name.endsWith(".class") && !name.equals(MODULE_INFO)) { |
|
659 throw new IllegalArgumentException(name |
|
660 + " found in in top-level directory" |
|
661 + " (unnamed package not allowed in module)"); |
|
662 } |
|
663 return Optional.empty(); |
|
664 } |
|
665 |
|
666 String pn = parent.toString().replace(File.separatorChar, '.'); |
|
667 if (Checks.isJavaIdentifier(pn)) { |
|
668 return Optional.of(pn); |
619 } else { |
669 } else { |
620 return ""; |
670 // not a valid package name |
|
671 return Optional.empty(); |
621 } |
672 } |
622 } |
673 } |
623 |
674 |
624 private static final PerfCounter scanTime |
675 private static final PerfCounter scanTime |
625 = PerfCounter.newPerfCounter("jdk.module.finder.modulepath.scanTime"); |
676 = PerfCounter.newPerfCounter("jdk.module.finder.modulepath.scanTime"); |