496 497 // map names of service configuration files to service names 498 Set<String> serviceNames = configFiles.stream() 499 .map(this::toServiceName) 500 .flatMap(Optional::stream) 501 .collect(Collectors.toSet()); 502 503 // parse each service configuration file 504 for (String sn : serviceNames) { 505 JarEntry entry = jf.getJarEntry(SERVICES_PREFIX + sn); 506 List<String> providerClasses = new ArrayList<>(); 507 try (InputStream in = jf.getInputStream(entry)) { 508 BufferedReader reader 509 = new BufferedReader(new InputStreamReader(in, "UTF-8")); 510 String cn; 511 while ((cn = nextLine(reader)) != null) { 512 if (cn.length() > 0) { 513 String pn = packageName(cn); 514 if (!packages.contains(pn)) { 515 String msg = "Provider class " + cn + " not in module"; 516 throw new IOException(msg); 517 } 518 providerClasses.add(cn); 519 } 520 } 521 } 522 if (!providerClasses.isEmpty()) 523 builder.provides(sn, providerClasses); 524 } 525 526 // Main-Class attribute if it exists 527 Manifest man = jf.getManifest(); 528 if (man != null) { 529 Attributes attrs = man.getMainAttributes(); 530 String mainClass = attrs.getValue(Attributes.Name.MAIN_CLASS); 531 if (mainClass != null) { 532 mainClass = mainClass.replace("/", "."); 533 String pn = packageName(mainClass); 534 if (!packages.contains(pn)) { 535 String msg = "Main-Class " + mainClass + " not in module"; 536 throw new IOException(msg); 537 } 538 builder.mainClass(mainClass); 539 } 540 } 541 542 return builder.build(); 543 } 544 545 /** 546 * Patterns used to derive the module name from a JAR file name. 547 */ 548 private static class Patterns { 549 static final Pattern DASH_VERSION = Pattern.compile("-(\\d+(\\.|$))"); 550 static final Pattern TRAILING_VERSION = Pattern.compile("(\\.|\\d)*$"); 551 static final Pattern NON_ALPHANUM = Pattern.compile("[^A-Za-z0-9]"); 552 static final Pattern REPEATING_DOTS = Pattern.compile("(\\.)(\\1)+"); 553 static final Pattern LEADING_DOTS = Pattern.compile("^\\."); 554 static final Pattern TRAILING_DOTS = Pattern.compile("\\.$"); 555 } 556 592 * Returns a {@code ModuleReference} to a module in modular JAR file on 593 * the file system. 594 * 595 * @throws IOException 596 * @throws FindException 597 * @throws InvalidModuleDescriptorException 598 */ 599 private ModuleReference readJar(Path file) throws IOException { 600 try (JarFile jf = new JarFile(file.toFile(), 601 true, // verify 602 ZipFile.OPEN_READ, 603 releaseVersion)) 604 { 605 ModuleInfo.Attributes attrs; 606 JarEntry entry = jf.getJarEntry(MODULE_INFO); 607 if (entry == null) { 608 609 // no module-info.class so treat it as automatic module 610 try { 611 ModuleDescriptor md = deriveModuleDescriptor(jf); 612 attrs = new ModuleInfo.Attributes(md, null, null); 613 } catch (IllegalArgumentException e) { 614 throw new FindException( 615 "Unable to derive module descriptor for: " 616 + jf.getName(), e); 617 } 618 619 } else { 620 attrs = ModuleInfo.read(jf.getInputStream(entry), 621 () -> jarPackages(jf)); 622 } 623 624 return ModuleReferences.newJarModule(attrs, patcher, file); 625 } 626 } 627 628 629 // -- exploded directories -- 630 631 private Set<String> explodedPackages(Path dir) { 632 try { 633 return Files.find(dir, Integer.MAX_VALUE, 634 ((path, attrs) -> attrs.isRegularFile())) 635 .map(path -> dir.relativize(path)) 655 attrs = ModuleInfo.read(new BufferedInputStream(in), 656 () -> explodedPackages(dir)); 657 } catch (NoSuchFileException e) { 658 // for now 659 return null; 660 } 661 return ModuleReferences.newExplodedModule(attrs, patcher, dir); 662 } 663 664 /** 665 * Maps a type name to its package name. 666 */ 667 private static String packageName(String cn) { 668 int index = cn.lastIndexOf('.'); 669 return (index == -1) ? "" : cn.substring(0, index); 670 } 671 672 /** 673 * Maps the name of an entry in a JAR or ZIP file to a package name. 674 * 675 * @throws IllegalArgumentException if the name is a class file in 676 * the top-level directory of the JAR/ZIP file (and it's 677 * not module-info.class) 678 */ 679 private Optional<String> toPackageName(String name) { 680 assert !name.endsWith("/"); 681 int index = name.lastIndexOf("/"); 682 if (index == -1) { 683 if (name.endsWith(".class") && !name.equals(MODULE_INFO)) { 684 throw new IllegalArgumentException(name 685 + " found in top-level directory" 686 + " (unnamed package not allowed in module)"); 687 } 688 return Optional.empty(); 689 } 690 691 String pn = name.substring(0, index).replace('/', '.'); 692 if (Checks.isPackageName(pn)) { 693 return Optional.of(pn); 694 } else { 695 // not a valid package name 696 return Optional.empty(); 697 } 698 } 699 700 /** 701 * Maps the relative path of an entry in an exploded module to a package 702 * name. 703 * 704 * @throws IllegalArgumentException if the name is a class file in 705 * the top-level directory (and it's not module-info.class) 706 */ 707 private Optional<String> toPackageName(Path file) { 708 assert file.getRoot() == null; 709 710 Path parent = file.getParent(); 711 if (parent == null) { 712 String name = file.toString(); 713 if (name.endsWith(".class") && !name.equals(MODULE_INFO)) { 714 throw new IllegalArgumentException(name 715 + " found in top-level directory" 716 + " (unnamed package not allowed in module)"); 717 } 718 return Optional.empty(); 719 } 720 721 String pn = parent.toString().replace(File.separatorChar, '.'); 722 if (Checks.isPackageName(pn)) { 723 return Optional.of(pn); 724 } else { 725 // not a valid package name 726 return Optional.empty(); 727 } 728 } 729 730 private static final PerfCounter scanTime 731 = PerfCounter.newPerfCounter("jdk.module.finder.modulepath.scanTime"); 732 private static final PerfCounter moduleCount 733 = PerfCounter.newPerfCounter("jdk.module.finder.modulepath.modules"); 734 } | 496 497 // map names of service configuration files to service names 498 Set<String> serviceNames = configFiles.stream() 499 .map(this::toServiceName) 500 .flatMap(Optional::stream) 501 .collect(Collectors.toSet()); 502 503 // parse each service configuration file 504 for (String sn : serviceNames) { 505 JarEntry entry = jf.getJarEntry(SERVICES_PREFIX + sn); 506 List<String> providerClasses = new ArrayList<>(); 507 try (InputStream in = jf.getInputStream(entry)) { 508 BufferedReader reader 509 = new BufferedReader(new InputStreamReader(in, "UTF-8")); 510 String cn; 511 while ((cn = nextLine(reader)) != null) { 512 if (cn.length() > 0) { 513 String pn = packageName(cn); 514 if (!packages.contains(pn)) { 515 String msg = "Provider class " + cn + " not in module"; 516 throw new InvalidModuleDescriptorException(msg); 517 } 518 providerClasses.add(cn); 519 } 520 } 521 } 522 if (!providerClasses.isEmpty()) 523 builder.provides(sn, providerClasses); 524 } 525 526 // Main-Class attribute if it exists 527 Manifest man = jf.getManifest(); 528 if (man != null) { 529 Attributes attrs = man.getMainAttributes(); 530 String mainClass = attrs.getValue(Attributes.Name.MAIN_CLASS); 531 if (mainClass != null) { 532 mainClass = mainClass.replace("/", "."); 533 String pn = packageName(mainClass); 534 if (!packages.contains(pn)) { 535 String msg = "Main-Class " + mainClass + " not in module"; 536 throw new InvalidModuleDescriptorException(msg); 537 } 538 builder.mainClass(mainClass); 539 } 540 } 541 542 return builder.build(); 543 } 544 545 /** 546 * Patterns used to derive the module name from a JAR file name. 547 */ 548 private static class Patterns { 549 static final Pattern DASH_VERSION = Pattern.compile("-(\\d+(\\.|$))"); 550 static final Pattern TRAILING_VERSION = Pattern.compile("(\\.|\\d)*$"); 551 static final Pattern NON_ALPHANUM = Pattern.compile("[^A-Za-z0-9]"); 552 static final Pattern REPEATING_DOTS = Pattern.compile("(\\.)(\\1)+"); 553 static final Pattern LEADING_DOTS = Pattern.compile("^\\."); 554 static final Pattern TRAILING_DOTS = Pattern.compile("\\.$"); 555 } 556 592 * Returns a {@code ModuleReference} to a module in modular JAR file on 593 * the file system. 594 * 595 * @throws IOException 596 * @throws FindException 597 * @throws InvalidModuleDescriptorException 598 */ 599 private ModuleReference readJar(Path file) throws IOException { 600 try (JarFile jf = new JarFile(file.toFile(), 601 true, // verify 602 ZipFile.OPEN_READ, 603 releaseVersion)) 604 { 605 ModuleInfo.Attributes attrs; 606 JarEntry entry = jf.getJarEntry(MODULE_INFO); 607 if (entry == null) { 608 609 // no module-info.class so treat it as automatic module 610 try { 611 ModuleDescriptor md = deriveModuleDescriptor(jf); 612 attrs = new ModuleInfo.Attributes(md, null, null, null); 613 } catch (RuntimeException e) { 614 throw new FindException("Unable to derive module descriptor for " 615 + jf.getName(), e); 616 } 617 618 } else { 619 attrs = ModuleInfo.read(jf.getInputStream(entry), 620 () -> jarPackages(jf)); 621 } 622 623 return ModuleReferences.newJarModule(attrs, patcher, file); 624 } 625 } 626 627 628 // -- exploded directories -- 629 630 private Set<String> explodedPackages(Path dir) { 631 try { 632 return Files.find(dir, Integer.MAX_VALUE, 633 ((path, attrs) -> attrs.isRegularFile())) 634 .map(path -> dir.relativize(path)) 654 attrs = ModuleInfo.read(new BufferedInputStream(in), 655 () -> explodedPackages(dir)); 656 } catch (NoSuchFileException e) { 657 // for now 658 return null; 659 } 660 return ModuleReferences.newExplodedModule(attrs, patcher, dir); 661 } 662 663 /** 664 * Maps a type name to its package name. 665 */ 666 private static String packageName(String cn) { 667 int index = cn.lastIndexOf('.'); 668 return (index == -1) ? "" : cn.substring(0, index); 669 } 670 671 /** 672 * Maps the name of an entry in a JAR or ZIP file to a package name. 673 * 674 * @throws InvalidModuleDescriptorException if the name is a class file in 675 * the top-level directory of the JAR/ZIP file (and it's not 676 * module-info.class) 677 */ 678 private Optional<String> toPackageName(String name) { 679 assert !name.endsWith("/"); 680 int index = name.lastIndexOf("/"); 681 if (index == -1) { 682 if (name.endsWith(".class") && !name.equals(MODULE_INFO)) { 683 String msg = name + " found in top-level directory" 684 + " (unnamed package not allowed in module)"; 685 throw new InvalidModuleDescriptorException(msg); 686 } 687 return Optional.empty(); 688 } 689 690 String pn = name.substring(0, index).replace('/', '.'); 691 if (Checks.isPackageName(pn)) { 692 return Optional.of(pn); 693 } else { 694 // not a valid package name 695 return Optional.empty(); 696 } 697 } 698 699 /** 700 * Maps the relative path of an entry in an exploded module to a package 701 * name. 702 * 703 * @throws InvalidModuleDescriptorException if the name is a class file in 704 * the top-level directory (and it's not module-info.class) 705 */ 706 private Optional<String> toPackageName(Path file) { 707 assert file.getRoot() == null; 708 709 Path parent = file.getParent(); 710 if (parent == null) { 711 String name = file.toString(); 712 if (name.endsWith(".class") && !name.equals(MODULE_INFO)) { 713 String msg = name + " found in top-level directory" 714 + " (unnamed package not allowed in module)"; 715 throw new InvalidModuleDescriptorException(msg); 716 } 717 return Optional.empty(); 718 } 719 720 String pn = parent.toString().replace(File.separatorChar, '.'); 721 if (Checks.isPackageName(pn)) { 722 return Optional.of(pn); 723 } else { 724 // not a valid package name 725 return Optional.empty(); 726 } 727 } 728 729 private static final PerfCounter scanTime 730 = PerfCounter.newPerfCounter("jdk.module.finder.modulepath.scanTime"); 731 private static final PerfCounter moduleCount 732 = PerfCounter.newPerfCounter("jdk.module.finder.modulepath.modules"); 733 } |