1 /* 2 * Copyright (c) 2003, 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 23 * questions. 24 */ 25 26 package com.sun.tools.javac.file; 27 28 import java.io.Closeable; 29 import java.io.File; 30 import java.io.FileNotFoundException; 31 import java.io.IOException; 32 import java.io.UncheckedIOException; 33 import java.net.URI; 34 import java.net.URL; 35 import java.net.URLClassLoader; 36 import java.nio.file.DirectoryIteratorException; 37 import java.nio.file.DirectoryStream; 38 import java.nio.file.FileSystem; 39 import java.nio.file.FileSystemNotFoundException; 40 import java.nio.file.FileSystems; 41 import java.nio.file.Files; 42 import java.nio.file.InvalidPathException; 43 import java.nio.file.Path; 44 import java.nio.file.Paths; 45 import java.nio.file.ProviderNotFoundException; 46 import java.nio.file.spi.FileSystemProvider; 47 import java.util.ArrayList; 48 import java.util.Arrays; 49 import java.util.Collection; 50 import java.util.Collections; 51 import java.util.EnumMap; 52 import java.util.EnumSet; 53 import java.util.HashMap; 54 import java.util.HashSet; 55 import java.util.Iterator; 56 import java.util.LinkedHashMap; 57 import java.util.LinkedHashSet; 58 import java.util.List; 59 import java.util.Map; 60 import java.util.Objects; 61 import java.util.NoSuchElementException; 62 import java.util.Set; 63 import java.util.regex.Matcher; 64 import java.util.regex.Pattern; 65 import java.util.stream.Collectors; 66 import java.util.stream.Stream; 67 68 import javax.lang.model.SourceVersion; 69 import javax.tools.JavaFileManager; 70 import javax.tools.JavaFileManager.Location; 71 import javax.tools.JavaFileObject; 72 import javax.tools.StandardJavaFileManager; 73 import javax.tools.StandardJavaFileManager.PathFactory; 74 import javax.tools.StandardLocation; 75 76 import com.sun.tools.javac.code.Lint; 77 import com.sun.tools.javac.code.Lint.LintCategory; 78 import com.sun.tools.javac.main.Option; 79 import com.sun.tools.javac.resources.CompilerProperties.Errors; 80 import com.sun.tools.javac.resources.CompilerProperties.Warnings; 81 import com.sun.tools.javac.util.DefinedBy; 82 import com.sun.tools.javac.util.DefinedBy.Api; 83 import com.sun.tools.javac.util.JDK9Wrappers; 84 import com.sun.tools.javac.util.ListBuffer; 85 import com.sun.tools.javac.util.Log; 86 import com.sun.tools.javac.jvm.ModuleNameReader; 87 import com.sun.tools.javac.util.Pair; 88 import com.sun.tools.javac.util.StringUtils; 89 90 import static javax.tools.StandardLocation.PLATFORM_CLASS_PATH; 91 92 import static com.sun.tools.javac.main.Option.BOOT_CLASS_PATH; 93 import static com.sun.tools.javac.main.Option.DJAVA_ENDORSED_DIRS; 94 import static com.sun.tools.javac.main.Option.DJAVA_EXT_DIRS; 95 import static com.sun.tools.javac.main.Option.ENDORSEDDIRS; 96 import static com.sun.tools.javac.main.Option.EXTDIRS; 97 import static com.sun.tools.javac.main.Option.XBOOTCLASSPATH; 98 import static com.sun.tools.javac.main.Option.XBOOTCLASSPATH_APPEND; 99 import static com.sun.tools.javac.main.Option.XBOOTCLASSPATH_PREPEND; 100 101 /** 102 * This class converts command line arguments, environment variables and system properties (in 103 * File.pathSeparator-separated String form) into a boot class path, user class path, and source 104 * path (in {@code Collection<String>} form). 105 * 106 * <p> 107 * <b>This is NOT part of any supported API. If you write code that depends on this, you do so at 108 * your own risk. This code and its internal interfaces are subject to change or deletion without 109 * notice.</b> 110 */ 111 public class Locations { 112 113 /** 114 * The log to use for warning output 115 */ 116 private Log log; 117 118 /** 119 * Access to (possibly cached) file info 120 */ 121 private FSInfo fsInfo; 122 123 /** 124 * Whether to warn about non-existent path elements 125 */ 126 private boolean warn; 127 128 private ModuleNameReader moduleNameReader; 129 130 private PathFactory pathFactory = Paths::get; 131 132 static final Path javaHome = FileSystems.getDefault().getPath(System.getProperty("java.home")); 133 static final Path thisSystemModules = javaHome.resolve("lib").resolve("modules"); 134 135 Map<Path, FileSystem> fileSystems = new LinkedHashMap<>(); 136 List<Closeable> closeables = new ArrayList<>(); 137 private Map<String,String> fsEnv = Collections.emptyMap(); 138 139 Locations() { 140 initHandlers(); 141 } 142 143 Path getPath(String first, String... more) { 144 try { 145 return pathFactory.getPath(first, more); 146 } catch (InvalidPathException ipe) { 147 throw new IllegalArgumentException(ipe); 148 } 149 } 150 151 public void close() throws IOException { 152 ListBuffer<IOException> list = new ListBuffer<>(); 153 closeables.forEach(closeable -> { 154 try { 155 closeable.close(); 156 } catch (IOException ex) { 157 list.add(ex); 158 } 159 }); 160 if (list.nonEmpty()) { 161 IOException ex = new IOException(); 162 for (IOException e: list) 163 ex.addSuppressed(e); 164 throw ex; 165 } 166 } 167 168 void update(Log log, boolean warn, FSInfo fsInfo) { 169 this.log = log; 170 this.warn = warn; 171 this.fsInfo = fsInfo; 172 } 173 174 void setPathFactory(PathFactory f) { 175 pathFactory = f; 176 } 177 178 boolean isDefaultBootClassPath() { 179 BootClassPathLocationHandler h 180 = (BootClassPathLocationHandler) getHandler(PLATFORM_CLASS_PATH); 181 return h.isDefault(); 182 } 183 184 /** 185 * Split a search path into its elements. Empty path elements will be ignored. 186 * 187 * @param searchPath The search path to be split 188 * @return The elements of the path 189 */ 190 private Iterable<Path> getPathEntries(String searchPath) { 191 return getPathEntries(searchPath, null); 192 } 193 194 /** 195 * Split a search path into its elements. If emptyPathDefault is not null, all empty elements in the 196 * path, including empty elements at either end of the path, will be replaced with the value of 197 * emptyPathDefault. 198 * 199 * @param searchPath The search path to be split 200 * @param emptyPathDefault The value to substitute for empty path elements, or null, to ignore 201 * empty path elements 202 * @return The elements of the path 203 */ 204 private Iterable<Path> getPathEntries(String searchPath, Path emptyPathDefault) { 205 ListBuffer<Path> entries = new ListBuffer<>(); 206 for (String s: searchPath.split(Pattern.quote(File.pathSeparator), -1)) { 207 if (s.isEmpty()) { 208 if (emptyPathDefault != null) { 209 entries.add(emptyPathDefault); 210 } 211 } else { 212 try { 213 entries.add(getPath(s)); 214 } catch (IllegalArgumentException e) { 215 if (warn) { 216 log.warning(LintCategory.PATH, "invalid.path", s); 217 } 218 } 219 } 220 } 221 return entries; 222 } 223 224 public void setMultiReleaseValue(String multiReleaseValue) { 225 fsEnv = Collections.singletonMap("multi-release", multiReleaseValue); 226 } 227 228 /** 229 * Utility class to help evaluate a path option. Duplicate entries are ignored, jar class paths 230 * can be expanded. 231 */ 232 private class SearchPath extends LinkedHashSet<Path> { 233 234 private static final long serialVersionUID = 0; 235 236 private boolean expandJarClassPaths = false; 237 private final Set<Path> canonicalValues = new HashSet<>(); 238 239 public SearchPath expandJarClassPaths(boolean x) { 240 expandJarClassPaths = x; 241 return this; 242 } 243 244 /** 245 * What to use when path element is the empty string 246 */ 247 private Path emptyPathDefault = null; 248 249 public SearchPath emptyPathDefault(Path x) { 250 emptyPathDefault = x; 251 return this; 252 } 253 254 public SearchPath addDirectories(String dirs, boolean warn) { 255 boolean prev = expandJarClassPaths; 256 expandJarClassPaths = true; 257 try { 258 if (dirs != null) { 259 for (Path dir : getPathEntries(dirs)) { 260 addDirectory(dir, warn); 261 } 262 } 263 return this; 264 } finally { 265 expandJarClassPaths = prev; 266 } 267 } 268 269 public SearchPath addDirectories(String dirs) { 270 return addDirectories(dirs, warn); 271 } 272 273 private void addDirectory(Path dir, boolean warn) { 274 if (!Files.isDirectory(dir)) { 275 if (warn) { 276 log.warning(Lint.LintCategory.PATH, 277 "dir.path.element.not.found", dir); 278 } 279 return; 280 } 281 282 try (Stream<Path> s = Files.list(dir)) { 283 s.filter(Locations.this::isArchive) 284 .forEach(dirEntry -> addFile(dirEntry, warn)); 285 } catch (IOException ignore) { 286 } 287 } 288 289 public SearchPath addFiles(String files, boolean warn) { 290 if (files != null) { 291 addFiles(getPathEntries(files, emptyPathDefault), warn); 292 } 293 return this; 294 } 295 296 public SearchPath addFiles(String files) { 297 return addFiles(files, warn); 298 } 299 300 public SearchPath addFiles(Iterable<? extends Path> files, boolean warn) { 301 if (files != null) { 302 for (Path file : files) { 303 addFile(file, warn); 304 } 305 } 306 return this; 307 } 308 309 public SearchPath addFiles(Iterable<? extends Path> files) { 310 return addFiles(files, warn); 311 } 312 313 public void addFile(Path file, boolean warn) { 314 if (contains(file)) { 315 // discard duplicates 316 return; 317 } 318 319 if (!fsInfo.exists(file)) { 320 /* No such file or directory exists */ 321 if (warn) { 322 log.warning(Lint.LintCategory.PATH, 323 "path.element.not.found", file); 324 } 325 super.add(file); 326 return; 327 } 328 329 Path canonFile = fsInfo.getCanonicalFile(file); 330 if (canonicalValues.contains(canonFile)) { 331 /* Discard duplicates and avoid infinite recursion */ 332 return; 333 } 334 335 if (fsInfo.isFile(file)) { 336 /* File is an ordinary file. */ 337 if ( !file.getFileName().toString().endsWith(".jmod") 338 && !file.endsWith("modules")) { 339 if (!isArchive(file)) { 340 /* Not a recognized extension; open it to see if 341 it looks like a valid zip file. */ 342 try { 343 FileSystems.newFileSystem(file, null).close(); 344 if (warn) { 345 log.warning(Lint.LintCategory.PATH, 346 "unexpected.archive.file", file); 347 } 348 } catch (IOException | ProviderNotFoundException e) { 349 // FIXME: include e.getLocalizedMessage in warning 350 if (warn) { 351 log.warning(Lint.LintCategory.PATH, 352 "invalid.archive.file", file); 353 } 354 return; 355 } 356 } else { 357 if (fsInfo.getJarFSProvider() == null) { 358 log.error(Errors.NoZipfsForArchive(file)); 359 return ; 360 } 361 } 362 } 363 } 364 365 /* Now what we have left is either a directory or a file name 366 conforming to archive naming convention */ 367 super.add(file); 368 canonicalValues.add(canonFile); 369 370 if (expandJarClassPaths && fsInfo.isFile(file) && !file.endsWith("modules")) { 371 addJarClassPath(file, warn); 372 } 373 } 374 375 // Adds referenced classpath elements from a jar's Class-Path 376 // Manifest entry. In some future release, we may want to 377 // update this code to recognize URLs rather than simple 378 // filenames, but if we do, we should redo all path-related code. 379 private void addJarClassPath(Path jarFile, boolean warn) { 380 try { 381 for (Path f : fsInfo.getJarClassPath(jarFile)) { 382 addFile(f, warn); 383 } 384 } catch (IOException e) { 385 log.error("error.reading.file", jarFile, JavacFileManager.getMessage(e)); 386 } 387 } 388 } 389 390 /** 391 * Base class for handling support for the representation of Locations. 392 * 393 * Locations are (by design) opaque handles that can easily be implemented 394 * by enums like StandardLocation. Within JavacFileManager, each Location 395 * has an associated LocationHandler, which provides much of the appropriate 396 * functionality for the corresponding Location. 397 * 398 * @see #initHandlers 399 * @see #getHandler 400 */ 401 protected abstract class LocationHandler { 402 403 /** 404 * @see JavaFileManager#handleOption 405 */ 406 abstract boolean handleOption(Option option, String value); 407 408 /** 409 * @see StandardJavaFileManager#hasLocation 410 */ 411 boolean isSet() { 412 return (getPaths() != null); 413 } 414 415 /** 416 * @see StandardJavaFileManager#getLocation 417 */ 418 abstract Collection<Path> getPaths(); 419 420 /** 421 * @see StandardJavaFileManager#setLocation 422 */ 423 abstract void setPaths(Iterable<? extends Path> files) throws IOException; 424 425 /** 426 * @see JavaFileManager#getLocationForModule(Location, String) 427 */ 428 Location getLocationForModule(String moduleName) throws IOException { 429 return null; 430 } 431 432 /** 433 * @see JavaFileManager#getLocationForModule(Location, JavaFileObject, String) 434 */ 435 Location getLocationForModule(Path dir) throws IOException { 436 return null; 437 } 438 439 /** 440 * @see JavaFileManager#inferModuleName 441 */ 442 String inferModuleName() { 443 return null; 444 } 445 446 /** 447 * @see JavaFileManager#listLocationsForModules 448 */ 449 Iterable<Set<Location>> listLocationsForModules() throws IOException { 450 return null; 451 } 452 } 453 454 /** 455 * A LocationHandler for a given Location, and associated set of options. 456 */ 457 private abstract class BasicLocationHandler extends LocationHandler { 458 459 final Location location; 460 final Set<Option> options; 461 462 /** 463 * Create a handler. The location and options provide a way to map from a location or an 464 * option to the corresponding handler. 465 * 466 * @param location the location for which this is the handler 467 * @param options the options affecting this location 468 * @see #initHandlers 469 */ 470 protected BasicLocationHandler(Location location, Option... options) { 471 this.location = location; 472 this.options = options.length == 0 473 ? EnumSet.noneOf(Option.class) 474 : EnumSet.copyOf(Arrays.asList(options)); 475 } 476 } 477 478 /** 479 * General purpose implementation for output locations, such as -d/CLASS_OUTPUT and 480 * -s/SOURCE_OUTPUT. All options are treated as equivalent (i.e. aliases.) 481 * The value is a single file, possibly null. 482 */ 483 private class OutputLocationHandler extends BasicLocationHandler { 484 485 private Path outputDir; 486 private Map<String, Location> moduleLocations; 487 private Map<Path, Location> pathLocations; 488 489 OutputLocationHandler(Location location, Option... options) { 490 super(location, options); 491 } 492 493 @Override 494 boolean handleOption(Option option, String value) { 495 if (!options.contains(option)) { 496 return false; 497 } 498 499 // TODO: could/should validate outputDir exists and is a directory 500 // need to decide how best to report issue for benefit of 501 // direct API call on JavaFileManager.handleOption(specifies IAE) 502 // vs. command line decoding. 503 outputDir = (value == null) ? null : getPath(value); 504 return true; 505 } 506 507 @Override 508 Collection<Path> getPaths() { 509 return (outputDir == null) ? null : Collections.singleton(outputDir); 510 } 511 512 @Override 513 void setPaths(Iterable<? extends Path> files) throws IOException { 514 if (files == null) { 515 outputDir = null; 516 } else { 517 Iterator<? extends Path> pathIter = files.iterator(); 518 if (!pathIter.hasNext()) { 519 throw new IllegalArgumentException("empty path for directory"); 520 } 521 Path dir = pathIter.next(); 522 if (pathIter.hasNext()) { 523 throw new IllegalArgumentException("path too long for directory"); 524 } 525 if (!Files.exists(dir)) { 526 throw new FileNotFoundException(dir + ": does not exist"); 527 } else if (!Files.isDirectory(dir)) { 528 throw new IOException(dir + ": not a directory"); 529 } 530 outputDir = dir; 531 } 532 moduleLocations = null; 533 pathLocations = null; 534 } 535 536 @Override 537 Location getLocationForModule(String name) { 538 if (moduleLocations == null) { 539 moduleLocations = new HashMap<>(); 540 pathLocations = new HashMap<>(); 541 } 542 Location l = moduleLocations.get(name); 543 if (l == null) { 544 Path out = outputDir.resolve(name); 545 l = new ModuleLocationHandler(location.getName() + "[" + name + "]", 546 name, 547 Collections.singleton(out), 548 true); 549 moduleLocations.put(name, l); 550 pathLocations.put(out.toAbsolutePath(), l); 551 } 552 return l; 553 } 554 555 @Override 556 Location getLocationForModule(Path dir) { 557 return (pathLocations == null) ? null : pathLocations.get(dir); 558 } 559 560 private boolean listed; 561 562 @Override 563 Iterable<Set<Location>> listLocationsForModules() throws IOException { 564 if (!listed && outputDir != null) { 565 try (DirectoryStream<Path> stream = Files.newDirectoryStream(outputDir)) { 566 for (Path p : stream) { 567 getLocationForModule(p.getFileName().toString()); 568 } 569 } 570 listed = true; 571 } 572 if (moduleLocations == null) 573 return Collections.emptySet(); 574 Set<Location> locns = new LinkedHashSet<>(); 575 moduleLocations.forEach((k, v) -> locns.add(v)); 576 return Collections.singleton(locns); 577 } 578 } 579 580 /** 581 * General purpose implementation for search path locations, 582 * such as -sourcepath/SOURCE_PATH and -processorPath/ANNOTATION_PROCESSOR_PATH. 583 * All options are treated as equivalent (i.e. aliases.) 584 * The value is an ordered set of files and/or directories. 585 */ 586 private class SimpleLocationHandler extends BasicLocationHandler { 587 588 protected Collection<Path> searchPath; 589 590 SimpleLocationHandler(Location location, Option... options) { 591 super(location, options); 592 } 593 594 @Override 595 boolean handleOption(Option option, String value) { 596 if (!options.contains(option)) { 597 return false; 598 } 599 searchPath = value == null ? null 600 : Collections.unmodifiableCollection(createPath().addFiles(value)); 601 return true; 602 } 603 604 @Override 605 Collection<Path> getPaths() { 606 return searchPath; 607 } 608 609 @Override 610 void setPaths(Iterable<? extends Path> files) { 611 SearchPath p; 612 if (files == null) { 613 p = computePath(null); 614 } else { 615 p = createPath().addFiles(files); 616 } 617 searchPath = Collections.unmodifiableCollection(p); 618 } 619 620 protected SearchPath computePath(String value) { 621 return createPath().addFiles(value); 622 } 623 624 protected SearchPath createPath() { 625 return new SearchPath(); 626 } 627 } 628 629 /** 630 * Subtype of SimpleLocationHandler for -classpath/CLASS_PATH. 631 * If no value is given, a default is provided, based on system properties and other values. 632 */ 633 private class ClassPathLocationHandler extends SimpleLocationHandler { 634 635 ClassPathLocationHandler() { 636 super(StandardLocation.CLASS_PATH, Option.CLASS_PATH); 637 } 638 639 @Override 640 Collection<Path> getPaths() { 641 lazy(); 642 return searchPath; 643 } 644 645 @Override 646 protected SearchPath computePath(String value) { 647 String cp = value; 648 649 // CLASSPATH environment variable when run from `javac'. 650 if (cp == null) { 651 cp = System.getProperty("env.class.path"); 652 } 653 654 // If invoked via a java VM (not the javac launcher), use the 655 // platform class path 656 if (cp == null && System.getProperty("application.home") == null) { 657 cp = System.getProperty("java.class.path"); 658 } 659 660 // Default to current working directory. 661 if (cp == null) { 662 cp = "."; 663 } 664 665 return createPath().addFiles(cp); 666 } 667 668 @Override 669 protected SearchPath createPath() { 670 return new SearchPath() 671 .expandJarClassPaths(true) // Only search user jars for Class-Paths 672 .emptyPathDefault(getPath(".")); // Empty path elt ==> current directory 673 } 674 675 private void lazy() { 676 if (searchPath == null) { 677 setPaths(null); 678 } 679 } 680 } 681 682 /** 683 * Custom subtype of LocationHandler for PLATFORM_CLASS_PATH. 684 * Various options are supported for different components of the 685 * platform class path. 686 * Setting a value with setLocation overrides all existing option values. 687 * Setting any option overrides any value set with setLocation, and 688 * reverts to using default values for options that have not been set. 689 * Setting -bootclasspath or -Xbootclasspath overrides any existing 690 * value for -Xbootclasspath/p: and -Xbootclasspath/a:. 691 */ 692 private class BootClassPathLocationHandler extends BasicLocationHandler { 693 694 private Collection<Path> searchPath; 695 final Map<Option, String> optionValues = new EnumMap<>(Option.class); 696 697 /** 698 * Is the bootclasspath the default? 699 */ 700 private boolean isDefault; 701 702 BootClassPathLocationHandler() { 703 super(StandardLocation.PLATFORM_CLASS_PATH, 704 Option.BOOT_CLASS_PATH, Option.XBOOTCLASSPATH, 705 Option.XBOOTCLASSPATH_PREPEND, 706 Option.XBOOTCLASSPATH_APPEND, 707 Option.ENDORSEDDIRS, Option.DJAVA_ENDORSED_DIRS, 708 Option.EXTDIRS, Option.DJAVA_EXT_DIRS); 709 } 710 711 boolean isDefault() { 712 lazy(); 713 return isDefault; 714 } 715 716 @Override 717 boolean handleOption(Option option, String value) { 718 if (!options.contains(option)) { 719 return false; 720 } 721 722 option = canonicalize(option); 723 optionValues.put(option, value); 724 if (option == BOOT_CLASS_PATH) { 725 optionValues.remove(XBOOTCLASSPATH_PREPEND); 726 optionValues.remove(XBOOTCLASSPATH_APPEND); 727 } 728 searchPath = null; // reset to "uninitialized" 729 return true; 730 } 731 // where 732 // TODO: would be better if option aliasing was handled at a higher 733 // level 734 private Option canonicalize(Option option) { 735 switch (option) { 736 case XBOOTCLASSPATH: 737 return Option.BOOT_CLASS_PATH; 738 case DJAVA_ENDORSED_DIRS: 739 return Option.ENDORSEDDIRS; 740 case DJAVA_EXT_DIRS: 741 return Option.EXTDIRS; 742 default: 743 return option; 744 } 745 } 746 747 @Override 748 Collection<Path> getPaths() { 749 lazy(); 750 return searchPath; 751 } 752 753 @Override 754 void setPaths(Iterable<? extends Path> files) { 755 if (files == null) { 756 searchPath = null; // reset to "uninitialized" 757 } else { 758 isDefault = false; 759 SearchPath p = new SearchPath().addFiles(files, false); 760 searchPath = Collections.unmodifiableCollection(p); 761 optionValues.clear(); 762 } 763 } 764 765 SearchPath computePath() throws IOException { 766 SearchPath path = new SearchPath(); 767 768 String bootclasspathOpt = optionValues.get(BOOT_CLASS_PATH); 769 String endorseddirsOpt = optionValues.get(ENDORSEDDIRS); 770 String extdirsOpt = optionValues.get(EXTDIRS); 771 String xbootclasspathPrependOpt = optionValues.get(XBOOTCLASSPATH_PREPEND); 772 String xbootclasspathAppendOpt = optionValues.get(XBOOTCLASSPATH_APPEND); 773 path.addFiles(xbootclasspathPrependOpt); 774 775 if (endorseddirsOpt != null) { 776 path.addDirectories(endorseddirsOpt); 777 } else { 778 path.addDirectories(System.getProperty("java.endorsed.dirs"), false); 779 } 780 781 if (bootclasspathOpt != null) { 782 path.addFiles(bootclasspathOpt); 783 } else { 784 // Standard system classes for this compiler's release. 785 Collection<Path> systemClasses = systemClasses(); 786 if (systemClasses != null) { 787 path.addFiles(systemClasses, false); 788 } else { 789 // fallback to the value of sun.boot.class.path 790 String files = System.getProperty("sun.boot.class.path"); 791 path.addFiles(files, false); 792 } 793 } 794 795 path.addFiles(xbootclasspathAppendOpt); 796 797 // Strictly speaking, standard extensions are not bootstrap 798 // classes, but we treat them identically, so we'll pretend 799 // that they are. 800 if (extdirsOpt != null) { 801 path.addDirectories(extdirsOpt); 802 } else { 803 // Add lib/jfxrt.jar to the search path 804 Path jfxrt = javaHome.resolve("lib/jfxrt.jar"); 805 if (Files.exists(jfxrt)) { 806 path.addFile(jfxrt, false); 807 } 808 path.addDirectories(System.getProperty("java.ext.dirs"), false); 809 } 810 811 isDefault = 812 (xbootclasspathPrependOpt == null) 813 && (bootclasspathOpt == null) 814 && (xbootclasspathAppendOpt == null); 815 816 return path; 817 } 818 819 /** 820 * Return a collection of files containing system classes. 821 * Returns {@code null} if not running on a modular image. 822 * 823 * @throws UncheckedIOException if an I/O errors occurs 824 */ 825 private Collection<Path> systemClasses() throws IOException { 826 // Return "modules" jimage file if available 827 if (Files.isRegularFile(thisSystemModules)) { 828 return Collections.singleton(thisSystemModules); 829 } 830 831 // Exploded module image 832 Path modules = javaHome.resolve("modules"); 833 if (Files.isDirectory(modules.resolve("java.base"))) { 834 try (Stream<Path> listedModules = Files.list(modules)) { 835 return listedModules.collect(Collectors.toList()); 836 } 837 } 838 839 // not a modular image that we know about 840 return null; 841 } 842 843 private void lazy() { 844 if (searchPath == null) { 845 try { 846 searchPath = Collections.unmodifiableCollection(computePath()); 847 } catch (IOException e) { 848 // TODO: need better handling here, e.g. javac Abort? 849 throw new UncheckedIOException(e); 850 } 851 } 852 } 853 } 854 855 /** 856 * A LocationHander to represent modules found from a module-oriented 857 * location such as MODULE_SOURCE_PATH, UPGRADE_MODULE_PATH, 858 * SYSTEM_MODULES and MODULE_PATH. 859 * 860 * The Location can be specified to accept overriding classes from the 861 * {@code --patch-module <module>=<path> } parameter. 862 */ 863 private class ModuleLocationHandler extends LocationHandler implements Location { 864 protected final String name; 865 protected final String moduleName; 866 protected final Collection<Path> searchPath; 867 protected final boolean output; 868 869 ModuleLocationHandler(String name, String moduleName, Collection<Path> searchPath, 870 boolean output) { 871 this.name = name; 872 this.moduleName = moduleName; 873 this.searchPath = searchPath; 874 this.output = output; 875 } 876 877 @Override @DefinedBy(Api.COMPILER) 878 public String getName() { 879 return name; 880 } 881 882 @Override @DefinedBy(Api.COMPILER) 883 public boolean isOutputLocation() { 884 return output; 885 } 886 887 @Override // defined by LocationHandler 888 boolean handleOption(Option option, String value) { 889 throw new UnsupportedOperationException(); 890 } 891 892 @Override // defined by LocationHandler 893 Collection<Path> getPaths() { 894 // For now, we always return searchPathWithOverrides. This may differ from the 895 // JVM behavior if there is a module-info.class to be found in the overriding 896 // classes. 897 return searchPath; 898 } 899 900 @Override // defined by LocationHandler 901 void setPaths(Iterable<? extends Path> files) throws IOException { 902 throw new UnsupportedOperationException(); 903 } 904 905 @Override // defined by LocationHandler 906 String inferModuleName() { 907 return moduleName; 908 } 909 } 910 911 /** 912 * A LocationHandler for simple module-oriented search paths, 913 * like UPGRADE_MODULE_PATH and MODULE_PATH. 914 */ 915 private class ModulePathLocationHandler extends SimpleLocationHandler { 916 private Map<String, ModuleLocationHandler> pathModules; 917 918 ModulePathLocationHandler(Location location, Option... options) { 919 super(location, options); 920 } 921 922 @Override 923 public boolean handleOption(Option option, String value) { 924 if (!options.contains(option)) { 925 return false; 926 } 927 setPaths(value == null ? null : getPathEntries(value)); 928 return true; 929 } 930 931 @Override 932 public Location getLocationForModule(String moduleName) { 933 initPathModules(); 934 return pathModules.get(moduleName); 935 } 936 937 @Override 938 Iterable<Set<Location>> listLocationsForModules() { 939 if (searchPath == null) 940 return Collections.emptyList(); 941 942 return ModulePathIterator::new; 943 } 944 945 @Override 946 void setPaths(Iterable<? extends Path> paths) { 947 if (paths != null) { 948 for (Path p: paths) { 949 checkValidModulePathEntry(p); 950 } 951 } 952 super.setPaths(paths); 953 } 954 955 private void initPathModules() { 956 if (pathModules != null) { 957 return; 958 } 959 960 pathModules = new LinkedHashMap<>(); 961 962 for (Set<Location> set : listLocationsForModules()) { 963 for (Location locn : set) { 964 if (locn instanceof ModuleLocationHandler) { 965 ModuleLocationHandler h = (ModuleLocationHandler) locn; 966 pathModules.put(h.moduleName, h); 967 } 968 } 969 } 970 } 971 972 private void checkValidModulePathEntry(Path p) { 973 if (Files.isDirectory(p)) { 974 // either an exploded module or a directory of modules 975 return; 976 } 977 978 String name = p.getFileName().toString(); 979 int lastDot = name.lastIndexOf("."); 980 if (lastDot > 0) { 981 switch (name.substring(lastDot)) { 982 case ".jar": 983 case ".jmod": 984 return; 985 } 986 } 987 throw new IllegalArgumentException(p.toString()); 988 } 989 990 class ModulePathIterator implements Iterator<Set<Location>> { 991 Iterator<Path> pathIter = searchPath.iterator(); 992 int pathIndex = 0; 993 Set<Location> next = null; 994 995 @Override 996 public boolean hasNext() { 997 if (next != null) 998 return true; 999 1000 while (next == null) { 1001 if (pathIter.hasNext()) { 1002 Path path = pathIter.next(); 1003 if (Files.isDirectory(path)) { 1004 next = scanDirectory(path); 1005 } else { 1006 next = scanFile(path); 1007 } 1008 pathIndex++; 1009 } else 1010 return false; 1011 } 1012 return true; 1013 } 1014 1015 @Override 1016 public Set<Location> next() { 1017 hasNext(); 1018 if (next != null) { 1019 Set<Location> result = next; 1020 next = null; 1021 return result; 1022 } 1023 throw new NoSuchElementException(); 1024 } 1025 1026 private Set<Location> scanDirectory(Path path) { 1027 Set<Path> paths = new LinkedHashSet<>(); 1028 Path moduleInfoClass = null; 1029 try (DirectoryStream<Path> stream = Files.newDirectoryStream(path)) { 1030 for (Path entry: stream) { 1031 if (entry.endsWith("module-info.class")) { 1032 moduleInfoClass = entry; 1033 break; // no need to continue scanning 1034 } 1035 paths.add(entry); 1036 } 1037 } catch (DirectoryIteratorException | IOException ignore) { 1038 log.error(Errors.LocnCantReadDirectory(path)); 1039 return Collections.emptySet(); 1040 } 1041 1042 if (moduleInfoClass != null) { 1043 // It's an exploded module directly on the module path. 1044 // We can't infer module name from the directory name, so have to 1045 // read module-info.class. 1046 try { 1047 String moduleName = readModuleName(moduleInfoClass); 1048 String name = location.getName() 1049 + "[" + pathIndex + ":" + moduleName + "]"; 1050 ModuleLocationHandler l = new ModuleLocationHandler(name, moduleName, 1051 Collections.singleton(path), false); 1052 return Collections.singleton(l); 1053 } catch (ModuleNameReader.BadClassFile e) { 1054 log.error(Errors.LocnBadModuleInfo(path)); 1055 return Collections.emptySet(); 1056 } catch (IOException e) { 1057 log.error(Errors.LocnCantReadFile(path)); 1058 return Collections.emptySet(); 1059 } 1060 } 1061 1062 // A directory of modules 1063 Set<Location> result = new LinkedHashSet<>(); 1064 int index = 0; 1065 for (Path entry : paths) { 1066 Pair<String,Path> module = inferModuleName(entry); 1067 if (module == null) { 1068 // diagnostic reported if necessary; skip to next 1069 continue; 1070 } 1071 String moduleName = module.fst; 1072 Path modulePath = module.snd; 1073 String name = location.getName() 1074 + "[" + pathIndex + "." + (index++) + ":" + moduleName + "]"; 1075 ModuleLocationHandler l = new ModuleLocationHandler(name, moduleName, 1076 Collections.singleton(modulePath), false); 1077 result.add(l); 1078 } 1079 return result; 1080 } 1081 1082 private Set<Location> scanFile(Path path) { 1083 Pair<String,Path> module = inferModuleName(path); 1084 if (module == null) { 1085 // diagnostic reported if necessary 1086 return Collections.emptySet(); 1087 } 1088 String moduleName = module.fst; 1089 Path modulePath = module.snd; 1090 String name = location.getName() 1091 + "[" + pathIndex + ":" + moduleName + "]"; 1092 ModuleLocationHandler l = new ModuleLocationHandler(name, moduleName, 1093 Collections.singleton(modulePath), false); 1094 return Collections.singleton(l); 1095 } 1096 1097 private Pair<String,Path> inferModuleName(Path p) { 1098 if (Files.isDirectory(p)) { 1099 if (Files.exists(p.resolve("module-info.class"))) { 1100 String name = p.getFileName().toString(); 1101 if (SourceVersion.isName(name)) 1102 return new Pair<>(name, p); 1103 } 1104 return null; 1105 } 1106 1107 if (p.getFileName().toString().endsWith(".jar") && fsInfo.exists(p)) { 1108 FileSystemProvider jarFSProvider = fsInfo.getJarFSProvider(); 1109 if (jarFSProvider == null) { 1110 log.error(Errors.NoZipfsForArchive(p)); 1111 return null; 1112 } 1113 try (FileSystem fs = jarFSProvider.newFileSystem(p, fsEnv)) { 1114 Path moduleInfoClass = fs.getPath("module-info.class"); 1115 if (Files.exists(moduleInfoClass)) { 1116 String moduleName = readModuleName(moduleInfoClass); 1117 return new Pair<>(moduleName, p); 1118 } 1119 } catch (ModuleNameReader.BadClassFile e) { 1120 log.error(Errors.LocnBadModuleInfo(p)); 1121 return null; 1122 } catch (IOException e) { 1123 log.error(Errors.LocnCantReadFile(p)); 1124 return null; 1125 } 1126 1127 //automatic module: 1128 String fn = p.getFileName().toString(); 1129 //from ModulePath.deriveModuleDescriptor: 1130 1131 // drop .jar 1132 String mn = fn.substring(0, fn.length()-4); 1133 1134 // find first occurrence of -${NUMBER}. or -${NUMBER}$ 1135 Matcher matcher = Pattern.compile("-(\\d+(\\.|$))").matcher(mn); 1136 if (matcher.find()) { 1137 int start = matcher.start(); 1138 1139 mn = mn.substring(0, start); 1140 } 1141 1142 // finally clean up the module name 1143 mn = mn.replaceAll("(\\.|\\d)*$", "") // remove trailing version 1144 .replaceAll("[^A-Za-z0-9]", ".") // replace non-alphanumeric 1145 .replaceAll("(\\.)(\\1)+", ".") // collapse repeating dots 1146 .replaceAll("^\\.", "") // drop leading dots 1147 .replaceAll("\\.$", ""); // drop trailing dots 1148 1149 1150 if (!mn.isEmpty()) { 1151 return new Pair<>(mn, p); 1152 } 1153 1154 log.error(Errors.LocnCantGetModuleNameForJar(p)); 1155 return null; 1156 } 1157 1158 if (p.getFileName().toString().endsWith(".jmod")) { 1159 try { 1160 // check if the JMOD file is valid 1161 JDK9Wrappers.JmodFile.checkMagic(p); 1162 1163 // No JMOD file system. Use JarFileSystem to 1164 // workaround for now 1165 FileSystem fs = fileSystems.get(p); 1166 if (fs == null) { 1167 FileSystemProvider jarFSProvider = fsInfo.getJarFSProvider(); 1168 if (jarFSProvider == null) { 1169 log.error(Errors.LocnCantReadFile(p)); 1170 return null; 1171 } 1172 fs = jarFSProvider.newFileSystem(p, Collections.emptyMap()); 1173 try { 1174 Path moduleInfoClass = fs.getPath("classes/module-info.class"); 1175 String moduleName = readModuleName(moduleInfoClass); 1176 Path modulePath = fs.getPath("classes"); 1177 fileSystems.put(p, fs); 1178 closeables.add(fs); 1179 fs = null; // prevent fs being closed in the finally clause 1180 return new Pair<>(moduleName, modulePath); 1181 } finally { 1182 if (fs != null) 1183 fs.close(); 1184 } 1185 } 1186 } catch (ModuleNameReader.BadClassFile e) { 1187 log.error(Errors.LocnBadModuleInfo(p)); 1188 } catch (IOException e) { 1189 log.error(Errors.LocnCantReadFile(p)); 1190 return null; 1191 } 1192 } 1193 1194 if (warn && false) { // temp disable, when enabled, massage examples.not-yet.txt suitably. 1195 log.warning(Warnings.LocnUnknownFileOnModulePath(p)); 1196 } 1197 return null; 1198 } 1199 1200 private String readModuleName(Path path) throws IOException, ModuleNameReader.BadClassFile { 1201 if (moduleNameReader == null) 1202 moduleNameReader = new ModuleNameReader(); 1203 return moduleNameReader.readModuleName(path); 1204 } 1205 } 1206 1207 } 1208 1209 private class ModuleSourcePathLocationHandler extends BasicLocationHandler { 1210 1211 private Map<String, Location> moduleLocations; 1212 private Map<Path, Location> pathLocations; 1213 1214 ModuleSourcePathLocationHandler() { 1215 super(StandardLocation.MODULE_SOURCE_PATH, 1216 Option.MODULE_SOURCE_PATH); 1217 } 1218 1219 @Override 1220 boolean handleOption(Option option, String value) { 1221 init(value); 1222 return true; 1223 } 1224 1225 void init(String value) { 1226 Collection<String> segments = new ArrayList<>(); 1227 for (String s: value.split(File.pathSeparator)) { 1228 expandBraces(s, segments); 1229 } 1230 1231 Map<String, Collection<Path>> map = new LinkedHashMap<>(); 1232 final String MARKER = "*"; 1233 for (String seg: segments) { 1234 int markStart = seg.indexOf(MARKER); 1235 if (markStart == -1) { 1236 add(map, getPath(seg), null); 1237 } else { 1238 if (markStart == 0 || !isSeparator(seg.charAt(markStart - 1))) { 1239 throw new IllegalArgumentException("illegal use of " + MARKER + " in " + seg); 1240 } 1241 Path prefix = getPath(seg.substring(0, markStart - 1)); 1242 Path suffix; 1243 int markEnd = markStart + MARKER.length(); 1244 if (markEnd == seg.length()) { 1245 suffix = null; 1246 } else if (!isSeparator(seg.charAt(markEnd)) 1247 || seg.indexOf(MARKER, markEnd) != -1) { 1248 throw new IllegalArgumentException("illegal use of " + MARKER + " in " + seg); 1249 } else { 1250 suffix = getPath(seg.substring(markEnd + 1)); 1251 } 1252 add(map, prefix, suffix); 1253 } 1254 } 1255 1256 moduleLocations = new LinkedHashMap<>(); 1257 pathLocations = new LinkedHashMap<>(); 1258 map.forEach((k, v) -> { 1259 String name = location.getName() + "[" + k + "]"; 1260 ModuleLocationHandler h = new ModuleLocationHandler(name, k, v, false); 1261 moduleLocations.put(k, h); 1262 v.forEach(p -> pathLocations.put(normalize(p), h)); 1263 }); 1264 } 1265 1266 private boolean isSeparator(char ch) { 1267 // allow both separators on Windows 1268 return (ch == File.separatorChar) || (ch == '/'); 1269 } 1270 1271 void add(Map<String, Collection<Path>> map, Path prefix, Path suffix) { 1272 if (!Files.isDirectory(prefix)) { 1273 if (warn) { 1274 String key = Files.exists(prefix) 1275 ? "dir.path.element.not.directory" 1276 : "dir.path.element.not.found"; 1277 log.warning(Lint.LintCategory.PATH, key, prefix); 1278 } 1279 return; 1280 } 1281 try (DirectoryStream<Path> stream = Files.newDirectoryStream(prefix, path -> Files.isDirectory(path))) { 1282 for (Path entry: stream) { 1283 Path path = (suffix == null) ? entry : entry.resolve(suffix); 1284 if (Files.isDirectory(path)) { 1285 String name = entry.getFileName().toString(); 1286 Collection<Path> paths = map.get(name); 1287 if (paths == null) 1288 map.put(name, paths = new ArrayList<>()); 1289 paths.add(path); 1290 } 1291 } 1292 } catch (IOException e) { 1293 // TODO? What to do? 1294 System.err.println(e); 1295 } 1296 } 1297 1298 private void expandBraces(String value, Collection<String> results) { 1299 int depth = 0; 1300 int start = -1; 1301 String prefix = null; 1302 String suffix = null; 1303 for (int i = 0; i < value.length(); i++) { 1304 switch (value.charAt(i)) { 1305 case '{': 1306 depth++; 1307 if (depth == 1) { 1308 prefix = value.substring(0, i); 1309 suffix = value.substring(getMatchingBrace(value, i) + 1); 1310 start = i + 1; 1311 } 1312 break; 1313 1314 case ',': 1315 if (depth == 1) { 1316 String elem = value.substring(start, i); 1317 expandBraces(prefix + elem + suffix, results); 1318 start = i + 1; 1319 } 1320 break; 1321 1322 case '}': 1323 switch (depth) { 1324 case 0: 1325 throw new IllegalArgumentException("mismatched braces"); 1326 1327 case 1: 1328 String elem = value.substring(start, i); 1329 expandBraces(prefix + elem + suffix, results); 1330 return; 1331 1332 default: 1333 depth--; 1334 } 1335 break; 1336 } 1337 } 1338 if (depth > 0) 1339 throw new IllegalArgumentException("mismatched braces"); 1340 results.add(value); 1341 } 1342 1343 int getMatchingBrace(String value, int offset) { 1344 int depth = 1; 1345 for (int i = offset + 1; i < value.length(); i++) { 1346 switch (value.charAt(i)) { 1347 case '{': 1348 depth++; 1349 break; 1350 1351 case '}': 1352 if (--depth == 0) 1353 return i; 1354 break; 1355 } 1356 } 1357 throw new IllegalArgumentException("mismatched braces"); 1358 } 1359 1360 @Override 1361 boolean isSet() { 1362 return (moduleLocations != null); 1363 } 1364 1365 @Override 1366 Collection<Path> getPaths() { 1367 throw new UnsupportedOperationException(); 1368 } 1369 1370 @Override 1371 void setPaths(Iterable<? extends Path> files) throws IOException { 1372 throw new UnsupportedOperationException(); 1373 } 1374 1375 @Override 1376 Location getLocationForModule(String name) { 1377 return (moduleLocations == null) ? null : moduleLocations.get(name); 1378 } 1379 1380 @Override 1381 Location getLocationForModule(Path dir) { 1382 return (pathLocations == null) ? null : pathLocations.get(dir); 1383 } 1384 1385 @Override 1386 Iterable<Set<Location>> listLocationsForModules() { 1387 if (moduleLocations == null) 1388 return Collections.emptySet(); 1389 Set<Location> locns = new LinkedHashSet<>(); 1390 moduleLocations.forEach((k, v) -> locns.add(v)); 1391 return Collections.singleton(locns); 1392 } 1393 1394 } 1395 1396 private class SystemModulesLocationHandler extends BasicLocationHandler { 1397 private Path systemJavaHome; 1398 private Path modules; 1399 private Map<String, ModuleLocationHandler> systemModules; 1400 private Map<Path, Location> pathLocations; 1401 1402 SystemModulesLocationHandler() { 1403 super(StandardLocation.SYSTEM_MODULES, Option.SYSTEM); 1404 systemJavaHome = Locations.javaHome; 1405 } 1406 1407 @Override 1408 boolean handleOption(Option option, String value) { 1409 if (!options.contains(option)) { 1410 return false; 1411 } 1412 1413 if (value == null) { 1414 systemJavaHome = Locations.javaHome; 1415 } else if (value.equals("none")) { 1416 systemJavaHome = null; 1417 } else { 1418 update(getPath(value)); 1419 } 1420 1421 modules = null; 1422 return true; 1423 } 1424 1425 @Override 1426 Collection<Path> getPaths() { 1427 return (systemJavaHome == null) ? null : Collections.singleton(systemJavaHome); 1428 } 1429 1430 @Override 1431 void setPaths(Iterable<? extends Path> files) throws IOException { 1432 if (files == null) { 1433 systemJavaHome = null; 1434 } else { 1435 Iterator<? extends Path> pathIter = files.iterator(); 1436 if (!pathIter.hasNext()) { 1437 throw new IllegalArgumentException("empty path for directory"); // TODO: FIXME 1438 } 1439 Path dir = pathIter.next(); 1440 if (pathIter.hasNext()) { 1441 throw new IllegalArgumentException("path too long for directory"); // TODO: FIXME 1442 } 1443 if (!Files.exists(dir)) { 1444 throw new FileNotFoundException(dir + ": does not exist"); 1445 } else if (!Files.isDirectory(dir)) { 1446 throw new IOException(dir + ": not a directory"); 1447 } 1448 update(dir); 1449 } 1450 } 1451 1452 private void update(Path p) { 1453 if (!isCurrentPlatform(p) && !Files.exists(p.resolve("lib").resolve("jrt-fs.jar")) && 1454 !Files.exists(systemJavaHome.resolve("modules"))) 1455 throw new IllegalArgumentException(p.toString()); 1456 systemJavaHome = p; 1457 modules = null; 1458 } 1459 1460 private boolean isCurrentPlatform(Path p) { 1461 try { 1462 return Files.isSameFile(p, Locations.javaHome); 1463 } catch (IOException ex) { 1464 throw new IllegalArgumentException(p.toString(), ex); 1465 } 1466 } 1467 1468 @Override 1469 Location getLocationForModule(String name) throws IOException { 1470 initSystemModules(); 1471 return systemModules.get(name); 1472 } 1473 1474 @Override 1475 Location getLocationForModule(Path dir) throws IOException { 1476 initSystemModules(); 1477 return (pathLocations == null) ? null : pathLocations.get(dir); 1478 } 1479 1480 @Override 1481 Iterable<Set<Location>> listLocationsForModules() throws IOException { 1482 initSystemModules(); 1483 Set<Location> locns = new LinkedHashSet<>(); 1484 for (Location l: systemModules.values()) 1485 locns.add(l); 1486 return Collections.singleton(locns); 1487 } 1488 1489 private void initSystemModules() throws IOException { 1490 if (systemModules != null) { 1491 return; 1492 } 1493 1494 if (systemJavaHome == null) { 1495 systemModules = Collections.emptyMap(); 1496 return; 1497 } 1498 1499 if (modules == null) { 1500 try { 1501 URI jrtURI = URI.create("jrt:/"); 1502 FileSystem jrtfs; 1503 1504 if (isCurrentPlatform(systemJavaHome)) { 1505 jrtfs = FileSystems.getFileSystem(jrtURI); 1506 } else { 1507 try { 1508 Map<String, String> attrMap = 1509 Collections.singletonMap("java.home", systemJavaHome.toString()); 1510 jrtfs = FileSystems.newFileSystem(jrtURI, attrMap); 1511 } catch (ProviderNotFoundException ex) { 1512 URL javaHomeURL = systemJavaHome.resolve("jrt-fs.jar").toUri().toURL(); 1513 ClassLoader currentLoader = Locations.class.getClassLoader(); 1514 URLClassLoader fsLoader = 1515 new URLClassLoader(new URL[] {javaHomeURL}, currentLoader); 1516 1517 jrtfs = FileSystems.newFileSystem(jrtURI, Collections.emptyMap(), fsLoader); 1518 1519 closeables.add(fsLoader); 1520 } 1521 1522 closeables.add(jrtfs); 1523 } 1524 1525 modules = jrtfs.getPath("/modules"); 1526 } catch (FileSystemNotFoundException | ProviderNotFoundException e) { 1527 modules = systemJavaHome.resolve("modules"); 1528 if (!Files.exists(modules)) 1529 throw new IOException("can't find system classes", e); 1530 } 1531 } 1532 1533 systemModules = new LinkedHashMap<>(); 1534 pathLocations = new LinkedHashMap<>(); 1535 try (DirectoryStream<Path> stream = Files.newDirectoryStream(modules, Files::isDirectory)) { 1536 for (Path entry : stream) { 1537 String moduleName = entry.getFileName().toString(); 1538 String name = location.getName() + "[" + moduleName + "]"; 1539 ModuleLocationHandler h = new ModuleLocationHandler(name, moduleName, 1540 Collections.singleton(entry), false); 1541 systemModules.put(moduleName, h); 1542 pathLocations.put(normalize(entry), h); 1543 } 1544 } 1545 } 1546 } 1547 1548 private class PatchModulesLocationHandler extends BasicLocationHandler { 1549 private final Map<String, ModuleLocationHandler> moduleLocations = new HashMap<>(); 1550 private final Map<Path, Location> pathLocations = new HashMap<>(); 1551 1552 PatchModulesLocationHandler() { 1553 super(StandardLocation.PATCH_MODULE_PATH, Option.PATCH_MODULE); 1554 } 1555 1556 @Override 1557 boolean handleOption(Option option, String value) { 1558 if (!options.contains(option)) { 1559 return false; 1560 } 1561 1562 // Allow an extended syntax for --patch-module consisting of a series 1563 // of values separated by NULL characters. This is to facilitate 1564 // supporting deferred file manager options on the command line. 1565 // See Option.PATCH_MODULE for the code that composes these multiple 1566 // values. 1567 for (String v : value.split("\0")) { 1568 int eq = v.indexOf('='); 1569 if (eq > 0) { 1570 String moduleName = v.substring(0, eq); 1571 SearchPath mPatchPath = new SearchPath() 1572 .addFiles(v.substring(eq + 1)); 1573 String name = location.getName() + "[" + moduleName + "]"; 1574 ModuleLocationHandler h = new ModuleLocationHandler(name, moduleName, mPatchPath, false); 1575 moduleLocations.put(moduleName, h); 1576 for (Path r : mPatchPath) { 1577 pathLocations.put(normalize(r), h); 1578 } 1579 } else { 1580 // Should not be able to get here; 1581 // this should be caught and handled in Option.PATCH_MODULE 1582 log.error(Errors.LocnInvalidArgForXpatch(value)); 1583 } 1584 } 1585 1586 return true; 1587 } 1588 1589 @Override 1590 boolean isSet() { 1591 return !moduleLocations.isEmpty(); 1592 } 1593 1594 @Override 1595 Collection<Path> getPaths() { 1596 throw new UnsupportedOperationException(); 1597 } 1598 1599 @Override 1600 void setPaths(Iterable<? extends Path> files) throws IOException { 1601 throw new UnsupportedOperationException(); 1602 } 1603 1604 @Override 1605 Location getLocationForModule(String name) throws IOException { 1606 return moduleLocations.get(name); 1607 } 1608 1609 @Override 1610 Location getLocationForModule(Path dir) throws IOException { 1611 return (pathLocations == null) ? null : pathLocations.get(dir); 1612 } 1613 1614 @Override 1615 Iterable<Set<Location>> listLocationsForModules() throws IOException { 1616 Set<Location> locns = new LinkedHashSet<>(); 1617 for (Location l: moduleLocations.values()) 1618 locns.add(l); 1619 return Collections.singleton(locns); 1620 } 1621 1622 } 1623 1624 Map<Location, LocationHandler> handlersForLocation; 1625 Map<Option, LocationHandler> handlersForOption; 1626 1627 void initHandlers() { 1628 handlersForLocation = new HashMap<>(); 1629 handlersForOption = new EnumMap<>(Option.class); 1630 1631 BasicLocationHandler[] handlers = { 1632 new BootClassPathLocationHandler(), 1633 new ClassPathLocationHandler(), 1634 new SimpleLocationHandler(StandardLocation.SOURCE_PATH, Option.SOURCE_PATH), 1635 new SimpleLocationHandler(StandardLocation.ANNOTATION_PROCESSOR_PATH, Option.PROCESSOR_PATH), 1636 new SimpleLocationHandler(StandardLocation.ANNOTATION_PROCESSOR_MODULE_PATH, Option.PROCESSOR_MODULE_PATH), 1637 new OutputLocationHandler(StandardLocation.CLASS_OUTPUT, Option.D), 1638 new OutputLocationHandler(StandardLocation.SOURCE_OUTPUT, Option.S), 1639 new OutputLocationHandler(StandardLocation.NATIVE_HEADER_OUTPUT, Option.H), 1640 new ModuleSourcePathLocationHandler(), 1641 new PatchModulesLocationHandler(), 1642 // TODO: should UPGRADE_MODULE_PATH be merged with SYSTEM_MODULES? 1643 new ModulePathLocationHandler(StandardLocation.UPGRADE_MODULE_PATH, Option.UPGRADE_MODULE_PATH), 1644 new ModulePathLocationHandler(StandardLocation.MODULE_PATH, Option.MODULE_PATH), 1645 new SystemModulesLocationHandler(), 1646 }; 1647 1648 for (BasicLocationHandler h : handlers) { 1649 handlersForLocation.put(h.location, h); 1650 for (Option o : h.options) { 1651 handlersForOption.put(o, h); 1652 } 1653 } 1654 } 1655 1656 boolean handleOption(Option option, String value) { 1657 LocationHandler h = handlersForOption.get(option); 1658 return (h == null ? false : h.handleOption(option, value)); 1659 } 1660 1661 boolean hasLocation(Location location) { 1662 LocationHandler h = getHandler(location); 1663 return (h == null ? false : h.isSet()); 1664 } 1665 1666 Collection<Path> getLocation(Location location) { 1667 LocationHandler h = getHandler(location); 1668 return (h == null ? null : h.getPaths()); 1669 } 1670 1671 Path getOutputLocation(Location location) { 1672 if (!location.isOutputLocation()) { 1673 throw new IllegalArgumentException(); 1674 } 1675 LocationHandler h = getHandler(location); 1676 return ((OutputLocationHandler) h).outputDir; 1677 } 1678 1679 void setLocation(Location location, Iterable<? extends Path> files) throws IOException { 1680 LocationHandler h = getHandler(location); 1681 if (h == null) { 1682 if (location.isOutputLocation()) { 1683 h = new OutputLocationHandler(location); 1684 } else { 1685 h = new SimpleLocationHandler(location); 1686 } 1687 handlersForLocation.put(location, h); 1688 } 1689 h.setPaths(files); 1690 } 1691 1692 Location getLocationForModule(Location location, String name) throws IOException { 1693 LocationHandler h = getHandler(location); 1694 return (h == null ? null : h.getLocationForModule(name)); 1695 } 1696 1697 Location getLocationForModule(Location location, Path dir) throws IOException { 1698 LocationHandler h = getHandler(location); 1699 return (h == null ? null : h.getLocationForModule(dir)); 1700 } 1701 1702 String inferModuleName(Location location) { 1703 LocationHandler h = getHandler(location); 1704 return (h == null ? null : h.inferModuleName()); 1705 } 1706 1707 Iterable<Set<Location>> listLocationsForModules(Location location) throws IOException { 1708 LocationHandler h = getHandler(location); 1709 return (h == null ? null : h.listLocationsForModules()); 1710 } 1711 1712 protected LocationHandler getHandler(Location location) { 1713 Objects.requireNonNull(location); 1714 return (location instanceof LocationHandler) 1715 ? (LocationHandler) location 1716 : handlersForLocation.get(location); 1717 } 1718 1719 /** 1720 * Is this the name of an archive file? 1721 */ 1722 private boolean isArchive(Path file) { 1723 String n = StringUtils.toLowerCase(file.getFileName().toString()); 1724 return fsInfo.isFile(file) 1725 && (n.endsWith(".jar") || n.endsWith(".zip")); 1726 } 1727 1728 static Path normalize(Path p) { 1729 try { 1730 return p.toRealPath(); 1731 } catch (IOException e) { 1732 return p.toAbsolutePath().normalize(); 1733 } 1734 } 1735 1736 }