< prev index next >

src/java.base/share/classes/jdk/internal/module/ModulePath.java

Print this page


   1 /*
   2  * Copyright (c) 2014, 2016, Oracle and/or its affiliates. All rights reserved.
   3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
   4  *
   5  * This code is free software; you can redistribute it and/or modify it
   6  * under the terms of the GNU General Public License version 2 only, as
   7  * published by the Free Software Foundation.  Oracle designates this
   8  * particular file as subject to the "Classpath" exception as provided
   9  * by Oracle in the LICENSE file that accompanied this code.
  10  *
  11  * This code is distributed in the hope that it will be useful, but WITHOUT
  12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
  14  * version 2 for more details (a copy is included in the LICENSE file that
  15  * accompanied this code).
  16  *
  17  * You should have received a copy of the GNU General Public License version
  18  * 2 along with this work; if not, write to the Free Software Foundation,
  19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  20  *
  21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
  22  * or visit www.oracle.com if you need additional information or have any


 178      * file then it is assumed to be a packaged module.
 179      *
 180      * @throws FindException if an error occurs scanning the entry
 181      */
 182     private Map<String, ModuleReference> scan(Path entry) {
 183 
 184         BasicFileAttributes attrs;
 185         try {
 186             attrs = Files.readAttributes(entry, BasicFileAttributes.class);
 187         } catch (NoSuchFileException e) {
 188             return Collections.emptyMap();
 189         } catch (IOException ioe) {
 190             throw new FindException(ioe);
 191         }
 192 
 193         try {
 194 
 195             if (attrs.isDirectory()) {
 196                 Path mi = entry.resolve(MODULE_INFO);
 197                 if (!Files.exists(mi)) {
 198                     // does not exist or unable to determine so assume a
 199                     // directory of modules
 200                     return scanDirectory(entry);
 201                 }
 202             }
 203 
 204             // packaged or exploded module
 205             ModuleReference mref = readModule(entry, attrs);
 206             if (mref != null) {
 207                 String name = mref.descriptor().name();
 208                 return Collections.singletonMap(name, mref);






 209             } else {
 210                 // skipped
 211                 return Collections.emptyMap();
 212             }

 213 
 214         } catch (IOException ioe) {
 215             throw new FindException(ioe);
 216         }
 217     }
 218 
 219 
 220     /**
 221      * Scans the given directory for packaged or exploded modules.
 222      *
 223      * @return a map of module name to ModuleReference for the modules found
 224      *         in the directory
 225      *
 226      * @throws IOException if an I/O error occurs
 227      * @throws FindException if an error occurs scanning the entry or the
 228      *         directory contains two or more modules with the same name
 229      */
 230     private Map<String, ModuleReference> scanDirectory(Path dir)
 231         throws IOException
 232     {


 249                 if (mref != null) {
 250                     // can have at most one version of a module in the directory
 251                     String name = mref.descriptor().name();
 252                     ModuleReference previous = nameToReference.put(name, mref);
 253                     if (previous != null) {
 254                         String fn1 = fileName(mref);
 255                         String fn2 = fileName(previous);
 256                         throw new FindException("Two versions of module "
 257                                                  + name + " found in " + dir
 258                                                  + " (" + fn1 + " and " + fn2 + ")");
 259                     }
 260                 }
 261             }
 262         }
 263 
 264         return nameToReference;
 265     }
 266 
 267 
 268     /**
 269      * Locates a packaged or exploded module, returning a {@code ModuleReference}
 270      * to the module. Returns {@code null} if the entry is skipped because it is
 271      * to a directory that does not contain a module-info.class or it's a hidden
 272      * file.
 273      *
 274      * @throws IOException if an I/O error occurs
 275      * @throws FindException if the file is not recognized as a module or an
 276      *         error occurs parsing its module descriptor
 277      */
 278     private ModuleReference readModule(Path entry, BasicFileAttributes attrs)
 279         throws IOException
 280     {
 281         try {
 282 
 283             if (attrs.isDirectory()) {
 284                 return readExplodedModule(entry); // may return null
 285             }
 286 
 287             String fn = entry.getFileName().toString();
 288             if (attrs.isRegularFile()) {
 289                 if (fn.endsWith(".jar")) {
 290                     return readJar(entry);
 291                 } else if (fn.endsWith(".jmod")) {
 292                     if (isLinkPhase)
 293                         return readJMod(entry);
 294                     throw new FindException("JMOD files not supported: " + entry);
 295                 }
 296             }
 297 
 298             // skip hidden files
 299             if (fn.startsWith(".") || Files.isHidden(entry)) {
 300                 return null;
 301             } else {
 302                 throw new FindException("Unrecognized module: " + entry);
 303             }
 304 
 305         } catch (InvalidModuleDescriptorException e) {
 306             throw new FindException("Error reading module: " + entry, e);
 307         }
 308     }
 309 
 310 
 311     /**
 312      * Returns a string with the file name of the module if possible.
 313      * If the module location is not a file URI then return the URI
 314      * as a string.
 315      */
 316     private String fileName(ModuleReference mref) {
 317         URI uri = mref.location().orElse(null);
 318         if (uri != null) {
 319             if (uri.getScheme().equalsIgnoreCase("file")) {
 320                 Path file = Paths.get(uri);
 321                 return file.getFileName().toString();
 322             } else {


 345      * @throws IOException
 346      * @throws InvalidModuleDescriptorException
 347      */
 348     private ModuleReference readJMod(Path file) throws IOException {
 349         try (JmodFile jf = new JmodFile(file)) {
 350             ModuleInfo.Attributes attrs;
 351             try (InputStream in = jf.getInputStream(Section.CLASSES, MODULE_INFO)) {
 352                 attrs  = ModuleInfo.read(in, () -> jmodPackages(jf));
 353             }
 354             return ModuleReferences.newJModModule(attrs, file);
 355         }
 356     }
 357 
 358 
 359     // -- JAR files --
 360 
 361     private static final String SERVICES_PREFIX = "META-INF/services/";
 362 
 363     /**
 364      * Returns the service type corresponding to the name of a services
 365      * configuration file if it is a valid Java identifier.
 366      *
 367      * For example, if called with "META-INF/services/p.S" then this method
 368      * returns a container with the value "p.S".
 369      */
 370     private Optional<String> toServiceName(String cf) {
 371         assert cf.startsWith(SERVICES_PREFIX);
 372         int index = cf.lastIndexOf("/") + 1;
 373         if (index < cf.length()) {
 374             String prefix = cf.substring(0, index);
 375             if (prefix.equals(SERVICES_PREFIX)) {
 376                 String sn = cf.substring(index);
 377                 if (Checks.isJavaIdentifier(sn))
 378                     return Optional.of(sn);
 379             }
 380         }
 381         return Optional.empty();
 382     }
 383 
 384     /**
 385      * Reads the next line from the given reader and trims it of comments and
 386      * leading/trailing white space.
 387      *
 388      * Returns null if the reader is at EOF.
 389      */
 390     private String nextLine(BufferedReader reader) throws IOException {
 391         String ln = reader.readLine();
 392         if (ln != null) {
 393             int ci = ln.indexOf('#');
 394             if (ci >= 0)
 395                 ln = ln.substring(0, ci);
 396             ln = ln.trim();
 397         }
 398         return ln;
 399     }
 400 
 401     /**
 402      * Treat the given JAR file as a module as follows:
 403      *
 404      * 1. The module name (and optionally the version) is derived from the file
 405      *    name of the JAR file
 406      * 2. All packages are exported and open
 407      * 3. It has no non-exported/non-open packages
 408      * 4. The contents of any META-INF/services configuration files are mapped
 409      *    to "provides" declarations
 410      * 5. The Main-Class attribute in the main attributes of the JAR manifest
 411      *    is mapped to the module descriptor mainClass
 412      */
 413     private ModuleDescriptor deriveModuleDescriptor(JarFile jf)
 414         throws IOException
 415     {
 416         // Derive module name and version from JAR file name
 417 
 418         String fn = jf.getName();
 419         int i = fn.lastIndexOf(File.separator);
 420         if (i != -1)
 421             fn = fn.substring(i+1);
 422 
 423         // drop .jar
 424         String mn = fn.substring(0, fn.length()-4);
 425         String vs = null;
 426 
 427         // find first occurrence of -${NUMBER}. or -${NUMBER}$
 428         Matcher matcher = Patterns.DASH_VERSION.matcher(mn);
 429         if (matcher.find()) {
 430             int start = matcher.start();
 431 
 432             // attempt to parse the tail as a version string
 433             try {
 434                 String tail = mn.substring(start+1);
 435                 ModuleDescriptor.Version.parse(tail);
 436                 vs = tail;
 437             } catch (IllegalArgumentException ignore) { }
 438 
 439             mn = mn.substring(0, start);
 440         }
 441 
 442         // finally clean up the module name
 443         mn = cleanModuleName(mn);
 444 
 445         // Builder throws IAE if module name is empty or invalid
 446         ModuleDescriptor.Builder builder
 447             = ModuleDescriptor.automaticModule(mn)
 448                 .requires(Set.of(Requires.Modifier.MANDATED), "java.base");
 449         if (vs != null)
 450             builder.version(vs);
 451 
 452         // scan the names of the entries in the JAR file
 453         Map<Boolean, Set<String>> map = VersionedStream.stream(jf)
 454                 .filter(e -> !e.isDirectory())
 455                 .map(JarEntry::getName)

 456                 .collect(Collectors.partitioningBy(e -> e.startsWith(SERVICES_PREFIX),
 457                                                    Collectors.toSet()));
 458 
 459         Set<String> resources = map.get(Boolean.FALSE);
 460         Set<String> configFiles = map.get(Boolean.TRUE);
 461         // all packages are exported and open
 462         resources.stream()

 463                 .map(this::toPackageName)
 464                 .flatMap(Optional::stream)
 465                 .distinct()
 466                 .forEach(pn -> builder.exports(pn).opens(pn));



 467 
 468         // map names of service configuration files to service names
 469         Set<String> serviceNames = configFiles.stream()
 470                 .map(this::toServiceName)
 471                 .flatMap(Optional::stream)
 472                 .collect(Collectors.toSet());
 473 
 474         // parse each service configuration file
 475         for (String sn : serviceNames) {
 476             JarEntry entry = jf.getJarEntry(SERVICES_PREFIX + sn);
 477             List<String> providerClasses = new ArrayList<>();
 478             try (InputStream in = jf.getInputStream(entry)) {
 479                 BufferedReader reader
 480                     = new BufferedReader(new InputStreamReader(in, "UTF-8"));
 481                 String cn;
 482                 while ((cn = nextLine(reader)) != null) {
 483                     if (cn.length() > 0) {





 484                         providerClasses.add(cn);
 485                     }
 486                 }
 487             }
 488             if (!providerClasses.isEmpty())
 489                 builder.provides(sn, providerClasses);
 490         }
 491 
 492         // Main-Class attribute if it exists
 493         Manifest man = jf.getManifest();
 494         if (man != null) {
 495             Attributes attrs = man.getMainAttributes();
 496             String mainClass = attrs.getValue(Attributes.Name.MAIN_CLASS);
 497             if (mainClass != null)
 498                 builder.mainClass(mainClass.replace("/", "."));







 499         }
 500 
 501         return builder.build();
 502     }
 503 
 504     /**
 505      * Patterns used to derive the module name from a JAR file name.
 506      */
 507     private static class Patterns {
 508         static final Pattern DASH_VERSION = Pattern.compile("-(\\d+(\\.|$))");
 509         static final Pattern TRAILING_VERSION = Pattern.compile("(\\.|\\d)*$");
 510         static final Pattern NON_ALPHANUM = Pattern.compile("[^A-Za-z0-9]");
 511         static final Pattern REPEATING_DOTS = Pattern.compile("(\\.)(\\1)+");
 512         static final Pattern LEADING_DOTS = Pattern.compile("^\\.");
 513         static final Pattern TRAILING_DOTS = Pattern.compile("\\.$");
 514     }
 515 
 516     /**
 517      * Clean up candidate module name derived from a JAR file name.
 518      */


 552      * the file system.
 553      *
 554      * @throws IOException
 555      * @throws FindException
 556      * @throws InvalidModuleDescriptorException
 557      */
 558     private ModuleReference readJar(Path file) throws IOException {
 559         try (JarFile jf = new JarFile(file.toFile(),
 560                                       true,               // verify
 561                                       ZipFile.OPEN_READ,
 562                                       releaseVersion))
 563         {
 564             ModuleInfo.Attributes attrs;
 565             JarEntry entry = jf.getJarEntry(MODULE_INFO);
 566             if (entry == null) {
 567 
 568                 // no module-info.class so treat it as automatic module
 569                 try {
 570                     ModuleDescriptor md = deriveModuleDescriptor(jf);
 571                     attrs = new ModuleInfo.Attributes(md, null, null);
 572                 } catch (IllegalArgumentException iae) {
 573                     throw new FindException(
 574                         "Unable to derive module descriptor for: "
 575                         + jf.getName(), iae);
 576                 }
 577 
 578             } else {
 579                 attrs = ModuleInfo.read(jf.getInputStream(entry),
 580                                         () -> jarPackages(jf));
 581             }
 582 
 583             return ModuleReferences.newJarModule(attrs, file);
 584         }
 585     }
 586 
 587 
 588     // -- exploded directories --
 589 
 590     private Set<String> explodedPackages(Path dir) {
 591         try {
 592             return Files.find(dir, Integer.MAX_VALUE,
 593                               ((path, attrs) -> attrs.isRegularFile()))
 594                     .map(path -> dir.relativize(path))
 595                     .map(this::toPackageName)


 604      * Returns a {@code ModuleReference} to an exploded module on the file
 605      * system or {@code null} if {@code module-info.class} not found.
 606      *
 607      * @throws IOException
 608      * @throws InvalidModuleDescriptorException
 609      */
 610     private ModuleReference readExplodedModule(Path dir) throws IOException {
 611         Path mi = dir.resolve(MODULE_INFO);
 612         ModuleInfo.Attributes attrs;
 613         try (InputStream in = Files.newInputStream(mi)) {
 614             attrs = ModuleInfo.read(new BufferedInputStream(in),
 615                                     () -> explodedPackages(dir));
 616         } catch (NoSuchFileException e) {
 617             // for now
 618             return null;
 619         }
 620         return ModuleReferences.newExplodedModule(attrs, dir);
 621     }
 622 
 623     /**








 624      * Maps the name of an entry in a JAR or ZIP file to a package name.
 625      *
 626      * @throws IllegalArgumentException if the name is a class file in
 627      *         the top-level directory of the JAR/ZIP file (and it's
 628      *         not module-info.class)
 629      */
 630     private Optional<String> toPackageName(String name) {
 631         assert !name.endsWith("/");
 632 
 633         int index = name.lastIndexOf("/");
 634         if (index == -1) {
 635             if (name.endsWith(".class") && !name.equals(MODULE_INFO)) {
 636                 throw new IllegalArgumentException(name
 637                         + " found in top-level directory:"
 638                         + " (unnamed package not allowed in module)");
 639             }
 640             return Optional.empty();
 641         }
 642 
 643         String pn = name.substring(0, index).replace('/', '.');
 644         if (Checks.isJavaIdentifier(pn)) {
 645             return Optional.of(pn);
 646         } else {
 647             // not a valid package name
 648             return Optional.empty();
 649         }
 650     }
 651 
 652     /**
 653      * Maps the relative path of an entry in an exploded module to a package
 654      * name.
 655      *
 656      * @throws IllegalArgumentException if the name is a class file in
 657      *         the top-level directory (and it's not module-info.class)
 658      */
 659     private Optional<String> toPackageName(Path file) {
 660         assert file.getRoot() == null;
 661 
 662         Path parent = file.getParent();
 663         if (parent == null) {
 664             String name = file.toString();
 665             if (name.endsWith(".class") && !name.equals(MODULE_INFO)) {
 666                 throw new IllegalArgumentException(name
 667                         + " found in in top-level directory"
 668                         + " (unnamed package not allowed in module)");
 669             }
 670             return Optional.empty();
 671         }
 672 
 673         String pn = parent.toString().replace(File.separatorChar, '.');
 674         if (Checks.isJavaIdentifier(pn)) {
 675             return Optional.of(pn);
 676         } else {
 677             // not a valid package name
 678             return Optional.empty();
 679         }
 680     }
 681 
 682     private static final PerfCounter scanTime
 683         = PerfCounter.newPerfCounter("jdk.module.finder.modulepath.scanTime");
 684     private static final PerfCounter moduleCount
 685         = PerfCounter.newPerfCounter("jdk.module.finder.modulepath.modules");
 686 }
   1 /*
   2  * Copyright (c) 2014, 2017, Oracle and/or its affiliates. All rights reserved.
   3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
   4  *
   5  * This code is free software; you can redistribute it and/or modify it
   6  * under the terms of the GNU General Public License version 2 only, as
   7  * published by the Free Software Foundation.  Oracle designates this
   8  * particular file as subject to the "Classpath" exception as provided
   9  * by Oracle in the LICENSE file that accompanied this code.
  10  *
  11  * This code is distributed in the hope that it will be useful, but WITHOUT
  12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
  14  * version 2 for more details (a copy is included in the LICENSE file that
  15  * accompanied this code).
  16  *
  17  * You should have received a copy of the GNU General Public License version
  18  * 2 along with this work; if not, write to the Free Software Foundation,
  19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  20  *
  21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
  22  * or visit www.oracle.com if you need additional information or have any


 178      * file then it is assumed to be a packaged module.
 179      *
 180      * @throws FindException if an error occurs scanning the entry
 181      */
 182     private Map<String, ModuleReference> scan(Path entry) {
 183 
 184         BasicFileAttributes attrs;
 185         try {
 186             attrs = Files.readAttributes(entry, BasicFileAttributes.class);
 187         } catch (NoSuchFileException e) {
 188             return Collections.emptyMap();
 189         } catch (IOException ioe) {
 190             throw new FindException(ioe);
 191         }
 192 
 193         try {
 194 
 195             if (attrs.isDirectory()) {
 196                 Path mi = entry.resolve(MODULE_INFO);
 197                 if (!Files.exists(mi)) {
 198                     // assume a directory of modules

 199                     return scanDirectory(entry);
 200                 }
 201             }
 202 
 203             // packaged or exploded module
 204             ModuleReference mref = readModule(entry, attrs);
 205             if (mref != null) {
 206                 String name = mref.descriptor().name();
 207                 return Collections.singletonMap(name, mref);
 208             }
 209 
 210             // not recognized
 211             String msg;
 212             if (!isLinkPhase && entry.toString().endsWith(".jmod")) {
 213                 msg = "JMOD format not supported at execution time";
 214             } else {
 215                 msg = "Module format not recognized";

 216             }
 217             throw new FindException(msg + ": " + entry);
 218 
 219         } catch (IOException ioe) {
 220             throw new FindException(ioe);
 221         }
 222     }
 223 
 224 
 225     /**
 226      * Scans the given directory for packaged or exploded modules.
 227      *
 228      * @return a map of module name to ModuleReference for the modules found
 229      *         in the directory
 230      *
 231      * @throws IOException if an I/O error occurs
 232      * @throws FindException if an error occurs scanning the entry or the
 233      *         directory contains two or more modules with the same name
 234      */
 235     private Map<String, ModuleReference> scanDirectory(Path dir)
 236         throws IOException
 237     {


 254                 if (mref != null) {
 255                     // can have at most one version of a module in the directory
 256                     String name = mref.descriptor().name();
 257                     ModuleReference previous = nameToReference.put(name, mref);
 258                     if (previous != null) {
 259                         String fn1 = fileName(mref);
 260                         String fn2 = fileName(previous);
 261                         throw new FindException("Two versions of module "
 262                                                  + name + " found in " + dir
 263                                                  + " (" + fn1 + " and " + fn2 + ")");
 264                     }
 265                 }
 266             }
 267         }
 268 
 269         return nameToReference;
 270     }
 271 
 272 
 273     /**
 274      * Reads a packaged or exploded module, returning a {@code ModuleReference}
 275      * to the module. Returns {@code null} if the entry is not recognized.


 276      *
 277      * @throws IOException if an I/O error occurs
 278      * @throws FindException if an error occurs parsing its module descriptor

 279      */
 280     private ModuleReference readModule(Path entry, BasicFileAttributes attrs)
 281         throws IOException
 282     {
 283         try {
 284 
 285             if (attrs.isDirectory()) {
 286                 return readExplodedModule(entry); // may return null
 287             } else {

 288                 String fn = entry.getFileName().toString();
 289                 if (attrs.isRegularFile()) {
 290                     if (fn.endsWith(".jar")) {
 291                         return readJar(entry);
 292                     } else if (isLinkPhase && fn.endsWith(".jmod")) {

 293                         return readJMod(entry);

 294                     }
 295                 }



 296                 return null;


 297             }
 298 
 299         } catch (InvalidModuleDescriptorException e) {
 300             throw new FindException("Error reading module: " + entry, e);
 301         }
 302     }
 303 
 304 
 305     /**
 306      * Returns a string with the file name of the module if possible.
 307      * If the module location is not a file URI then return the URI
 308      * as a string.
 309      */
 310     private String fileName(ModuleReference mref) {
 311         URI uri = mref.location().orElse(null);
 312         if (uri != null) {
 313             if (uri.getScheme().equalsIgnoreCase("file")) {
 314                 Path file = Paths.get(uri);
 315                 return file.getFileName().toString();
 316             } else {


 339      * @throws IOException
 340      * @throws InvalidModuleDescriptorException
 341      */
 342     private ModuleReference readJMod(Path file) throws IOException {
 343         try (JmodFile jf = new JmodFile(file)) {
 344             ModuleInfo.Attributes attrs;
 345             try (InputStream in = jf.getInputStream(Section.CLASSES, MODULE_INFO)) {
 346                 attrs  = ModuleInfo.read(in, () -> jmodPackages(jf));
 347             }
 348             return ModuleReferences.newJModModule(attrs, file);
 349         }
 350     }
 351 
 352 
 353     // -- JAR files --
 354 
 355     private static final String SERVICES_PREFIX = "META-INF/services/";
 356 
 357     /**
 358      * Returns the service type corresponding to the name of a services
 359      * configuration file if it is a legal type name.
 360      *
 361      * For example, if called with "META-INF/services/p.S" then this method
 362      * returns a container with the value "p.S".
 363      */
 364     private Optional<String> toServiceName(String cf) {
 365         assert cf.startsWith(SERVICES_PREFIX);
 366         int index = cf.lastIndexOf("/") + 1;
 367         if (index < cf.length()) {
 368             String prefix = cf.substring(0, index);
 369             if (prefix.equals(SERVICES_PREFIX)) {
 370                 String sn = cf.substring(index);
 371                 if (Checks.isClassName(sn))
 372                     return Optional.of(sn);
 373             }
 374         }
 375         return Optional.empty();
 376     }
 377 
 378     /**
 379      * Reads the next line from the given reader and trims it of comments and
 380      * leading/trailing white space.
 381      *
 382      * Returns null if the reader is at EOF.
 383      */
 384     private String nextLine(BufferedReader reader) throws IOException {
 385         String ln = reader.readLine();
 386         if (ln != null) {
 387             int ci = ln.indexOf('#');
 388             if (ci >= 0)
 389                 ln = ln.substring(0, ci);
 390             ln = ln.trim();
 391         }
 392         return ln;
 393     }
 394 
 395     /**
 396      * Treat the given JAR file as a module as follows:
 397      *
 398      * 1. The module name (and optionally the version) is derived from the file
 399      *    name of the JAR file
 400      * 2. All packages are derived from the .class files in the JAR file
 401      * 3. The contents of any META-INF/services configuration files are mapped

 402      *    to "provides" declarations
 403      * 4. The Main-Class attribute in the main attributes of the JAR manifest
 404      *    is mapped to the module descriptor mainClass
 405      */
 406     private ModuleDescriptor deriveModuleDescriptor(JarFile jf)
 407         throws IOException
 408     {
 409         // Derive module name and version from JAR file name
 410 
 411         String fn = jf.getName();
 412         int i = fn.lastIndexOf(File.separator);
 413         if (i != -1)
 414             fn = fn.substring(i+1);
 415 
 416         // drop .jar
 417         String mn = fn.substring(0, fn.length()-4);
 418         String vs = null;
 419 
 420         // find first occurrence of -${NUMBER}. or -${NUMBER}$
 421         Matcher matcher = Patterns.DASH_VERSION.matcher(mn);
 422         if (matcher.find()) {
 423             int start = matcher.start();
 424 
 425             // attempt to parse the tail as a version string
 426             try {
 427                 String tail = mn.substring(start+1);
 428                 ModuleDescriptor.Version.parse(tail);
 429                 vs = tail;
 430             } catch (IllegalArgumentException ignore) { }
 431 
 432             mn = mn.substring(0, start);
 433         }
 434 
 435         // finally clean up the module name
 436         mn = cleanModuleName(mn);
 437 
 438         // Builder throws IAE if module name is empty or invalid
 439         ModuleDescriptor.Builder builder = ModuleDescriptor.newAutomaticModule(mn);


 440         if (vs != null)
 441             builder.version(vs);
 442 
 443         // scan the names of the entries in the JAR file
 444         Map<Boolean, Set<String>> map = VersionedStream.stream(jf)
 445                 .filter(e -> !e.isDirectory())
 446                 .map(JarEntry::getName)
 447                 .filter(e -> (e.endsWith(".class") ^ e.startsWith(SERVICES_PREFIX)))
 448                 .collect(Collectors.partitioningBy(e -> e.startsWith(SERVICES_PREFIX),
 449                                                    Collectors.toSet()));
 450 
 451         Set<String> classFiles = map.get(Boolean.FALSE);
 452         Set<String> configFiles = map.get(Boolean.TRUE);
 453 
 454         // the packages containing class files
 455         Set<String> packages = classFiles.stream()
 456                 .map(this::toPackageName)
 457                 .flatMap(Optional::stream)
 458                 .distinct()
 459                 .collect(Collectors.toSet());
 460 
 461         // all packages are exported and open
 462         builder.packages(packages);
 463 
 464         // map names of service configuration files to service names
 465         Set<String> serviceNames = configFiles.stream()
 466                 .map(this::toServiceName)
 467                 .flatMap(Optional::stream)
 468                 .collect(Collectors.toSet());
 469 
 470         // parse each service configuration file
 471         for (String sn : serviceNames) {
 472             JarEntry entry = jf.getJarEntry(SERVICES_PREFIX + sn);
 473             List<String> providerClasses = new ArrayList<>();
 474             try (InputStream in = jf.getInputStream(entry)) {
 475                 BufferedReader reader
 476                     = new BufferedReader(new InputStreamReader(in, "UTF-8"));
 477                 String cn;
 478                 while ((cn = nextLine(reader)) != null) {
 479                     if (cn.length() > 0) {
 480                         String pn = packageName(cn);
 481                         if (!packages.contains(pn)) {
 482                             String msg = "Provider class " + cn + " not in module";
 483                             throw new IOException(msg);
 484                         }
 485                         providerClasses.add(cn);
 486                     }
 487                 }
 488             }
 489             if (!providerClasses.isEmpty())
 490                 builder.provides(sn, providerClasses);
 491         }
 492 
 493         // Main-Class attribute if it exists
 494         Manifest man = jf.getManifest();
 495         if (man != null) {
 496             Attributes attrs = man.getMainAttributes();
 497             String mainClass = attrs.getValue(Attributes.Name.MAIN_CLASS);
 498             if (mainClass != null) {
 499                 mainClass = mainClass.replace("/", ".");
 500                 String pn = packageName(mainClass);
 501                 if (!packages.contains(pn)) {
 502                     String msg = "Main-Class " + mainClass + " not in module";
 503                     throw new IOException(msg);
 504                 }
 505                 builder.mainClass(mainClass);
 506             }
 507         }
 508 
 509         return builder.build();
 510     }
 511 
 512     /**
 513      * Patterns used to derive the module name from a JAR file name.
 514      */
 515     private static class Patterns {
 516         static final Pattern DASH_VERSION = Pattern.compile("-(\\d+(\\.|$))");
 517         static final Pattern TRAILING_VERSION = Pattern.compile("(\\.|\\d)*$");
 518         static final Pattern NON_ALPHANUM = Pattern.compile("[^A-Za-z0-9]");
 519         static final Pattern REPEATING_DOTS = Pattern.compile("(\\.)(\\1)+");
 520         static final Pattern LEADING_DOTS = Pattern.compile("^\\.");
 521         static final Pattern TRAILING_DOTS = Pattern.compile("\\.$");
 522     }
 523 
 524     /**
 525      * Clean up candidate module name derived from a JAR file name.
 526      */


 560      * the file system.
 561      *
 562      * @throws IOException
 563      * @throws FindException
 564      * @throws InvalidModuleDescriptorException
 565      */
 566     private ModuleReference readJar(Path file) throws IOException {
 567         try (JarFile jf = new JarFile(file.toFile(),
 568                                       true,               // verify
 569                                       ZipFile.OPEN_READ,
 570                                       releaseVersion))
 571         {
 572             ModuleInfo.Attributes attrs;
 573             JarEntry entry = jf.getJarEntry(MODULE_INFO);
 574             if (entry == null) {
 575 
 576                 // no module-info.class so treat it as automatic module
 577                 try {
 578                     ModuleDescriptor md = deriveModuleDescriptor(jf);
 579                     attrs = new ModuleInfo.Attributes(md, null, null);
 580                 } catch (IllegalArgumentException e) {
 581                     throw new FindException(
 582                         "Unable to derive module descriptor for: "
 583                         + jf.getName(), e);
 584                 }
 585 
 586             } else {
 587                 attrs = ModuleInfo.read(jf.getInputStream(entry),
 588                                         () -> jarPackages(jf));
 589             }
 590 
 591             return ModuleReferences.newJarModule(attrs, file);
 592         }
 593     }
 594 
 595 
 596     // -- exploded directories --
 597 
 598     private Set<String> explodedPackages(Path dir) {
 599         try {
 600             return Files.find(dir, Integer.MAX_VALUE,
 601                               ((path, attrs) -> attrs.isRegularFile()))
 602                     .map(path -> dir.relativize(path))
 603                     .map(this::toPackageName)


 612      * Returns a {@code ModuleReference} to an exploded module on the file
 613      * system or {@code null} if {@code module-info.class} not found.
 614      *
 615      * @throws IOException
 616      * @throws InvalidModuleDescriptorException
 617      */
 618     private ModuleReference readExplodedModule(Path dir) throws IOException {
 619         Path mi = dir.resolve(MODULE_INFO);
 620         ModuleInfo.Attributes attrs;
 621         try (InputStream in = Files.newInputStream(mi)) {
 622             attrs = ModuleInfo.read(new BufferedInputStream(in),
 623                                     () -> explodedPackages(dir));
 624         } catch (NoSuchFileException e) {
 625             // for now
 626             return null;
 627         }
 628         return ModuleReferences.newExplodedModule(attrs, dir);
 629     }
 630 
 631     /**
 632      * Maps a type name to its package name.
 633      */
 634     private static String packageName(String cn) {
 635         int index = cn.lastIndexOf('.');
 636         return (index == -1) ? "" : cn.substring(0, index);
 637     }
 638 
 639     /**
 640      * Maps the name of an entry in a JAR or ZIP file to a package name.
 641      *
 642      * @throws IllegalArgumentException if the name is a class file in
 643      *         the top-level directory of the JAR/ZIP file (and it's
 644      *         not module-info.class)
 645      */
 646     private Optional<String> toPackageName(String name) {
 647         assert !name.endsWith("/");

 648         int index = name.lastIndexOf("/");
 649         if (index == -1) {
 650             if (name.endsWith(".class") && !name.equals(MODULE_INFO)) {
 651                 throw new IllegalArgumentException(name
 652                         + " found in top-level directory"
 653                         + " (unnamed package not allowed in module)");
 654             }
 655             return Optional.empty();
 656         }
 657 
 658         String pn = name.substring(0, index).replace('/', '.');
 659         if (Checks.isPackageName(pn)) {
 660             return Optional.of(pn);
 661         } else {
 662             // not a valid package name
 663             return Optional.empty();
 664         }
 665     }
 666 
 667     /**
 668      * Maps the relative path of an entry in an exploded module to a package
 669      * name.
 670      *
 671      * @throws IllegalArgumentException if the name is a class file in
 672      *          the top-level directory (and it's not module-info.class)
 673      */
 674     private Optional<String> toPackageName(Path file) {
 675         assert file.getRoot() == null;
 676 
 677         Path parent = file.getParent();
 678         if (parent == null) {
 679             String name = file.toString();
 680             if (name.endsWith(".class") && !name.equals(MODULE_INFO)) {
 681                 throw new IllegalArgumentException(name
 682                         + " found in top-level directory"
 683                         + " (unnamed package not allowed in module)");
 684             }
 685             return Optional.empty();
 686         }
 687 
 688         String pn = parent.toString().replace(File.separatorChar, '.');
 689         if (Checks.isPackageName(pn)) {
 690             return Optional.of(pn);
 691         } else {
 692             // not a valid package name
 693             return Optional.empty();
 694         }
 695     }
 696 
 697     private static final PerfCounter scanTime
 698         = PerfCounter.newPerfCounter("jdk.module.finder.modulepath.scanTime");
 699     private static final PerfCounter moduleCount
 700         = PerfCounter.newPerfCounter("jdk.module.finder.modulepath.modules");
 701 }
< prev index next >