jdk/src/java.base/share/classes/java/lang/module/ModulePath.java
changeset 41817 b90ad1de93ea
parent 41352 f9844bad9052
child 42338 a60f280f803c
equal deleted inserted replaced
41816:07e906f1a20b 41817:b90ad1de93ea
    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);
   237 
   243 
   238                 // module found
   244                 // module found
   239                 if (mref != null) {
   245                 if (mref != null) {
   240                     // can have at most one version of a module in the directory
   246                     // can have at most one version of a module in the directory
   241                     String name = mref.descriptor().name();
   247                     String name = mref.descriptor().name();
   242                     if (nameToReference.put(name, mref) != null) {
   248                     ModuleReference previous = nameToReference.put(name, mref);
       
   249                     if (previous != null) {
       
   250                         String fn1 = fileName(mref);
       
   251                         String fn2 = fileName(previous);
   243                         throw new FindException("Two versions of module "
   252                         throw new FindException("Two versions of module "
   244                                                   + name + " found in " + dir);
   253                                                  + name + " found in " + dir
       
   254                                                  + " (" + fn1 + " and " + fn2 + ")");
   245                     }
   255                     }
   246                 }
   256                 }
   247             }
   257             }
   248         }
   258         }
   249 
   259 
   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
   326     // -- JAR files --
   355     // -- JAR files --
   327 
   356 
   328     private static final String SERVICES_PREFIX = "META-INF/services/";
   357     private static final String SERVICES_PREFIX = "META-INF/services/";
   329 
   358 
   330     /**
   359     /**
   331      * Returns a container with the service type corresponding to the name of
   360      * Returns the service type corresponding to the name of a services
   332      * a services configuration file.
   361      * configuration file if it is a valid Java identifier.
   333      *
   362      *
   334      * For example, if called with "META-INF/services/p.S" then this method
   363      * For example, if called with "META-INF/services/p.S" then this method
   335      * returns a container with the value "p.S".
   364      * returns a container with the value "p.S".
   336      */
   365      */
   337     private Optional<String> toServiceName(String cf) {
   366     private Optional<String> toServiceName(String cf) {
   339         int index = cf.lastIndexOf("/") + 1;
   368         int index = cf.lastIndexOf("/") + 1;
   340         if (index < cf.length()) {
   369         if (index < cf.length()) {
   341             String prefix = cf.substring(0, index);
   370             String prefix = cf.substring(0, index);
   342             if (prefix.equals(SERVICES_PREFIX)) {
   371             if (prefix.equals(SERVICES_PREFIX)) {
   343                 String sn = cf.substring(index);
   372                 String sn = cf.substring(index);
   344                 return Optional.of(sn);
   373                 if (Checks.isJavaIdentifier(sn))
       
   374                     return Optional.of(sn);
   345             }
   375             }
   346         }
   376         }
   347         return Optional.empty();
   377         return Optional.empty();
   348     }
   378     }
   349 
   379 
   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.
   533      */
   551      */
   534     private ModuleReference readJar(Path file) throws IOException {
   552     private ModuleReference readJar(Path file) throws IOException {
   535         try (JarFile jf = new JarFile(file.toFile(),
   553         try (JarFile jf = new JarFile(file.toFile(),
   536                                       true,               // verify
   554                                       true,               // verify
   537                                       ZipFile.OPEN_READ,
   555                                       ZipFile.OPEN_READ,
   538                                       JarFile.runtimeVersion()))
   556                                       releaseVersion))
   539         {
   557         {
   540             ModuleDescriptor md;
   558             ModuleDescriptor md;
   541             JarEntry entry = jf.getJarEntry(MODULE_INFO);
   559             JarEntry entry = jf.getJarEntry(MODULE_INFO);
   542             if (entry == null) {
   560             if (entry == null) {
   543 
   561 
   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");