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) { 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, false); 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 Collection<Path> searchPathWithOverrides; 868 protected final boolean output; 869 870 ModuleLocationHandler(String name, String moduleName, Collection<Path> searchPath, 871 boolean output, boolean allowOverrides) { 872 this.name = name; 873 this.moduleName = moduleName; 874 this.searchPath = searchPath; 875 this.output = output; 876 877 if (allowOverrides && patchMap != null) { 878 SearchPath mPatch = patchMap.get(moduleName); 879 if (mPatch != null) { 880 SearchPath sp = new SearchPath(); 881 sp.addAll(mPatch); 882 sp.addAll(searchPath); 883 searchPathWithOverrides = sp; 884 } else { 885 searchPathWithOverrides = searchPath; 886 } 887 } else { 888 searchPathWithOverrides = searchPath; 889 } 890 } 891 892 @Override @DefinedBy(Api.COMPILER) 893 public String getName() { 894 return name; 895 } 896 897 @Override @DefinedBy(Api.COMPILER) 898 public boolean isOutputLocation() { 899 return output; 900 } 901 902 @Override // defined by LocationHandler 903 boolean handleOption(Option option, String value) { 904 throw new UnsupportedOperationException(); 905 } 906 907 @Override // defined by LocationHandler 908 Collection<Path> getPaths() { 909 // For now, we always return searchPathWithOverrides. This may differ from the 910 // JVM behavior if there is a module-info.class to be found in the overriding 911 // classes. 912 return searchPathWithOverrides; 913 } 914 915 @Override // defined by LocationHandler 916 void setPaths(Iterable<? extends Path> files) throws IOException { 917 throw new UnsupportedOperationException(); 918 } 919 920 @Override // defined by LocationHandler 921 String inferModuleName() { 922 return moduleName; 923 } 924 } 925 926 /** 927 * A LocationHandler for simple module-oriented search paths, 928 * like UPGRADE_MODULE_PATH and MODULE_PATH. 929 */ 930 private class ModulePathLocationHandler extends SimpleLocationHandler { 931 private Map<String, ModuleLocationHandler> pathModules; 932 933 ModulePathLocationHandler(Location location, Option... options) { 934 super(location, options); 935 } 936 937 @Override 938 public boolean handleOption(Option option, String value) { 939 if (!options.contains(option)) { 940 return false; 941 } 942 setPaths(value == null ? null : getPathEntries(value)); 943 return true; 944 } 945 946 @Override 947 public Location getLocationForModule(String moduleName) { 948 initPathModules(); 949 return pathModules.get(moduleName); 950 } 951 952 @Override 953 Iterable<Set<Location>> listLocationsForModules() { 954 if (searchPath == null) 955 return Collections.emptyList(); 956 957 return ModulePathIterator::new; 958 } 959 960 @Override 961 void setPaths(Iterable<? extends Path> paths) { 962 if (paths != null) { 963 for (Path p: paths) { 964 checkValidModulePathEntry(p); 965 } 966 } 967 super.setPaths(paths); 968 } 969 970 private void initPathModules() { 971 if (pathModules != null) { 972 return; 973 } 974 975 pathModules = new LinkedHashMap<>(); 976 977 for (Set<Location> set : listLocationsForModules()) { 978 for (Location locn : set) { 979 if (locn instanceof ModuleLocationHandler) { 980 ModuleLocationHandler h = (ModuleLocationHandler) locn; 981 pathModules.put(h.moduleName, h); 982 } 983 } 984 } 985 } 986 987 private void checkValidModulePathEntry(Path p) { 988 if (Files.isDirectory(p)) { 989 // either an exploded module or a directory of modules 990 return; 991 } 992 993 String name = p.getFileName().toString(); 994 int lastDot = name.lastIndexOf("."); 995 if (lastDot > 0) { 996 switch (name.substring(lastDot)) { 997 case ".jar": 998 case ".jmod": 999 return; 1000 } 1001 } 1002 throw new IllegalArgumentException(p.toString()); 1003 } 1004 1005 class ModulePathIterator implements Iterator<Set<Location>> { 1006 Iterator<Path> pathIter = searchPath.iterator(); 1007 int pathIndex = 0; 1008 Set<Location> next = null; 1009 1010 @Override 1011 public boolean hasNext() { 1012 if (next != null) 1013 return true; 1014 1015 while (next == null) { 1016 if (pathIter.hasNext()) { 1017 Path path = pathIter.next(); 1018 if (Files.isDirectory(path)) { 1019 next = scanDirectory(path); 1020 } else { 1021 next = scanFile(path); 1022 } 1023 pathIndex++; 1024 } else 1025 return false; 1026 } 1027 return true; 1028 } 1029 1030 @Override 1031 public Set<Location> next() { 1032 hasNext(); 1033 if (next != null) { 1034 Set<Location> result = next; 1035 next = null; 1036 return result; 1037 } 1038 throw new NoSuchElementException(); 1039 } 1040 1041 private Set<Location> scanDirectory(Path path) { 1042 Set<Path> paths = new LinkedHashSet<>(); 1043 Path moduleInfoClass = null; 1044 try (DirectoryStream<Path> stream = Files.newDirectoryStream(path)) { 1045 for (Path entry: stream) { 1046 if (entry.endsWith("module-info.class")) { 1047 moduleInfoClass = entry; 1048 break; // no need to continue scanning 1049 } 1050 paths.add(entry); 1051 } 1052 } catch (DirectoryIteratorException | IOException ignore) { 1053 log.error(Errors.LocnCantReadDirectory(path)); 1054 return Collections.emptySet(); 1055 } 1056 1057 if (moduleInfoClass != null) { 1058 // It's an exploded module directly on the module path. 1059 // We can't infer module name from the directory name, so have to 1060 // read module-info.class. 1061 try { 1062 String moduleName = readModuleName(moduleInfoClass); 1063 String name = location.getName() 1064 + "[" + pathIndex + ":" + moduleName + "]"; 1065 ModuleLocationHandler l = new ModuleLocationHandler(name, moduleName, 1066 Collections.singleton(path), false, true); 1067 return Collections.singleton(l); 1068 } catch (ModuleNameReader.BadClassFile e) { 1069 log.error(Errors.LocnBadModuleInfo(path)); 1070 return Collections.emptySet(); 1071 } catch (IOException e) { 1072 log.error(Errors.LocnCantReadFile(path)); 1073 return Collections.emptySet(); 1074 } 1075 } 1076 1077 // A directory of modules 1078 Set<Location> result = new LinkedHashSet<>(); 1079 int index = 0; 1080 for (Path entry : paths) { 1081 Pair<String,Path> module = inferModuleName(entry); 1082 if (module == null) { 1083 // diagnostic reported if necessary; skip to next 1084 continue; 1085 } 1086 String moduleName = module.fst; 1087 Path modulePath = module.snd; 1088 String name = location.getName() 1089 + "[" + pathIndex + "." + (index++) + ":" + moduleName + "]"; 1090 ModuleLocationHandler l = new ModuleLocationHandler(name, moduleName, 1091 Collections.singleton(modulePath), false, true); 1092 result.add(l); 1093 } 1094 return result; 1095 } 1096 1097 private Set<Location> scanFile(Path path) { 1098 Pair<String,Path> module = inferModuleName(path); 1099 if (module == null) { 1100 // diagnostic reported if necessary 1101 return Collections.emptySet(); 1102 } 1103 String moduleName = module.fst; 1104 Path modulePath = module.snd; 1105 String name = location.getName() 1106 + "[" + pathIndex + ":" + moduleName + "]"; 1107 ModuleLocationHandler l = new ModuleLocationHandler(name, moduleName, 1108 Collections.singleton(modulePath), false, true); 1109 return Collections.singleton(l); 1110 } 1111 1112 private Pair<String,Path> inferModuleName(Path p) { 1113 if (Files.isDirectory(p)) { 1114 if (Files.exists(p.resolve("module-info.class"))) { 1115 String name = p.getFileName().toString(); 1116 if (SourceVersion.isName(name)) 1117 return new Pair<>(name, p); 1118 } 1119 return null; 1120 } 1121 1122 if (p.getFileName().toString().endsWith(".jar") && fsInfo.exists(p)) { 1123 FileSystemProvider jarFSProvider = fsInfo.getJarFSProvider(); 1124 if (jarFSProvider == null) { 1125 log.error(Errors.NoZipfsForArchive(p)); 1126 return null; 1127 } 1128 try (FileSystem fs = jarFSProvider.newFileSystem(p, fsEnv)) { 1129 Path moduleInfoClass = fs.getPath("module-info.class"); 1130 if (Files.exists(moduleInfoClass)) { 1131 String moduleName = readModuleName(moduleInfoClass); 1132 return new Pair<>(moduleName, p); 1133 } 1134 } catch (ModuleNameReader.BadClassFile e) { 1135 log.error(Errors.LocnBadModuleInfo(p)); 1136 return null; 1137 } catch (IOException e) { 1138 log.error(Errors.LocnCantReadFile(p)); 1139 return null; 1140 } 1141 1142 //automatic module: 1143 String fn = p.getFileName().toString(); 1144 //from ModulePath.deriveModuleDescriptor: 1145 1146 // drop .jar 1147 String mn = fn.substring(0, fn.length()-4); 1148 1149 // find first occurrence of -${NUMBER}. or -${NUMBER}$ 1150 Matcher matcher = Pattern.compile("-(\\d+(\\.|$))").matcher(mn); 1151 if (matcher.find()) { 1152 int start = matcher.start(); 1153 1154 mn = mn.substring(0, start); 1155 } 1156 1157 // finally clean up the module name 1158 mn = mn.replaceAll("(\\.|\\d)*$", "") // remove trailing version 1159 .replaceAll("[^A-Za-z0-9]", ".") // replace non-alphanumeric 1160 .replaceAll("(\\.)(\\1)+", ".") // collapse repeating dots 1161 .replaceAll("^\\.", "") // drop leading dots 1162 .replaceAll("\\.$", ""); // drop trailing dots 1163 1164 1165 if (!mn.isEmpty()) { 1166 return new Pair<>(mn, p); 1167 } 1168 1169 log.error(Errors.LocnCantGetModuleNameForJar(p)); 1170 return null; 1171 } 1172 1173 if (p.getFileName().toString().endsWith(".jmod")) { 1174 try { 1175 // check if the JMOD file is valid 1176 JDK9Wrappers.JmodFile.checkMagic(p); 1177 1178 // No JMOD file system. Use JarFileSystem to 1179 // workaround for now 1180 FileSystem fs = fileSystems.get(p); 1181 if (fs == null) { 1182 FileSystemProvider jarFSProvider = fsInfo.getJarFSProvider(); 1183 if (jarFSProvider == null) { 1184 log.error(Errors.LocnCantReadFile(p)); 1185 return null; 1186 } 1187 fs = jarFSProvider.newFileSystem(p, Collections.emptyMap()); 1188 try { 1189 Path moduleInfoClass = fs.getPath("classes/module-info.class"); 1190 String moduleName = readModuleName(moduleInfoClass); 1191 Path modulePath = fs.getPath("classes"); 1192 fileSystems.put(p, fs); 1193 closeables.add(fs); 1194 fs = null; // prevent fs being closed in the finally clause 1195 return new Pair<>(moduleName, modulePath); 1196 } finally { 1197 if (fs != null) 1198 fs.close(); 1199 } 1200 } 1201 } catch (ModuleNameReader.BadClassFile e) { 1202 log.error(Errors.LocnBadModuleInfo(p)); 1203 } catch (IOException e) { 1204 log.error(Errors.LocnCantReadFile(p)); 1205 return null; 1206 } 1207 } 1208 1209 if (warn && false) { // temp disable, when enabled, massage examples.not-yet.txt suitably. 1210 log.warning(Warnings.LocnUnknownFileOnModulePath(p)); 1211 } 1212 return null; 1213 } 1214 1215 private String readModuleName(Path path) throws IOException, ModuleNameReader.BadClassFile { 1216 if (moduleNameReader == null) 1217 moduleNameReader = new ModuleNameReader(); 1218 return moduleNameReader.readModuleName(path); 1219 } 1220 } 1221 1222 } 1223 1224 private class ModuleSourcePathLocationHandler extends BasicLocationHandler { 1225 1226 private Map<String, Location> moduleLocations; 1227 private Map<Path, Location> pathLocations; 1228 1229 ModuleSourcePathLocationHandler() { 1230 super(StandardLocation.MODULE_SOURCE_PATH, 1231 Option.MODULE_SOURCE_PATH); 1232 } 1233 1234 @Override 1235 boolean handleOption(Option option, String value) { 1236 init(value); 1237 return true; 1238 } 1239 1240 void init(String value) { 1241 Collection<String> segments = new ArrayList<>(); 1242 for (String s: value.split(File.pathSeparator)) { 1243 expandBraces(s, segments); 1244 } 1245 1246 Map<String, Collection<Path>> map = new LinkedHashMap<>(); 1247 final String MARKER = "*"; 1248 for (String seg: segments) { 1249 int markStart = seg.indexOf(MARKER); 1250 if (markStart == -1) { 1251 add(map, getPath(seg), null); 1252 } else { 1253 if (markStart == 0 || !isSeparator(seg.charAt(markStart - 1))) { 1254 throw new IllegalArgumentException("illegal use of " + MARKER + " in " + seg); 1255 } 1256 Path prefix = getPath(seg.substring(0, markStart - 1)); 1257 Path suffix; 1258 int markEnd = markStart + MARKER.length(); 1259 if (markEnd == seg.length()) { 1260 suffix = null; 1261 } else if (!isSeparator(seg.charAt(markEnd)) 1262 || seg.indexOf(MARKER, markEnd) != -1) { 1263 throw new IllegalArgumentException("illegal use of " + MARKER + " in " + seg); 1264 } else { 1265 suffix = getPath(seg.substring(markEnd + 1)); 1266 } 1267 add(map, prefix, suffix); 1268 } 1269 } 1270 1271 moduleLocations = new LinkedHashMap<>(); 1272 pathLocations = new LinkedHashMap<>(); 1273 map.forEach((k, v) -> { 1274 String name = location.getName() + "[" + k + "]"; 1275 ModuleLocationHandler h = new ModuleLocationHandler(name, k, v, false, false); 1276 moduleLocations.put(k, h); 1277 v.forEach(p -> pathLocations.put(normalize(p), h)); 1278 }); 1279 } 1280 1281 private boolean isSeparator(char ch) { 1282 // allow both separators on Windows 1283 return (ch == File.separatorChar) || (ch == '/'); 1284 } 1285 1286 void add(Map<String, Collection<Path>> map, Path prefix, Path suffix) { 1287 if (!Files.isDirectory(prefix)) { 1288 if (warn) { 1289 String key = Files.exists(prefix) 1290 ? "dir.path.element.not.directory" 1291 : "dir.path.element.not.found"; 1292 log.warning(Lint.LintCategory.PATH, key, prefix); 1293 } 1294 return; 1295 } 1296 try (DirectoryStream<Path> stream = Files.newDirectoryStream(prefix, path -> Files.isDirectory(path))) { 1297 for (Path entry: stream) { 1298 Path path = (suffix == null) ? entry : entry.resolve(suffix); 1299 if (Files.isDirectory(path)) { 1300 String name = entry.getFileName().toString(); 1301 Collection<Path> paths = map.get(name); 1302 if (paths == null) 1303 map.put(name, paths = new ArrayList<>()); 1304 paths.add(path); 1305 } 1306 } 1307 } catch (IOException e) { 1308 // TODO? What to do? 1309 System.err.println(e); 1310 } 1311 } 1312 1313 private void expandBraces(String value, Collection<String> results) { 1314 int depth = 0; 1315 int start = -1; 1316 String prefix = null; 1317 String suffix = null; 1318 for (int i = 0; i < value.length(); i++) { 1319 switch (value.charAt(i)) { 1320 case '{': 1321 depth++; 1322 if (depth == 1) { 1323 prefix = value.substring(0, i); 1324 suffix = value.substring(getMatchingBrace(value, i) + 1); 1325 start = i + 1; 1326 } 1327 break; 1328 1329 case ',': 1330 if (depth == 1) { 1331 String elem = value.substring(start, i); 1332 expandBraces(prefix + elem + suffix, results); 1333 start = i + 1; 1334 } 1335 break; 1336 1337 case '}': 1338 switch (depth) { 1339 case 0: 1340 throw new IllegalArgumentException("mismatched braces"); 1341 1342 case 1: 1343 String elem = value.substring(start, i); 1344 expandBraces(prefix + elem + suffix, results); 1345 return; 1346 1347 default: 1348 depth--; 1349 } 1350 break; 1351 } 1352 } 1353 if (depth > 0) 1354 throw new IllegalArgumentException("mismatched braces"); 1355 results.add(value); 1356 } 1357 1358 int getMatchingBrace(String value, int offset) { 1359 int depth = 1; 1360 for (int i = offset + 1; i < value.length(); i++) { 1361 switch (value.charAt(i)) { 1362 case '{': 1363 depth++; 1364 break; 1365 1366 case '}': 1367 if (--depth == 0) 1368 return i; 1369 break; 1370 } 1371 } 1372 throw new IllegalArgumentException("mismatched braces"); 1373 } 1374 1375 @Override 1376 boolean isSet() { 1377 return (moduleLocations != null); 1378 } 1379 1380 @Override 1381 Collection<Path> getPaths() { 1382 throw new UnsupportedOperationException(); 1383 } 1384 1385 @Override 1386 void setPaths(Iterable<? extends Path> files) throws IOException { 1387 throw new UnsupportedOperationException(); 1388 } 1389 1390 @Override 1391 Location getLocationForModule(String name) { 1392 return (moduleLocations == null) ? null : moduleLocations.get(name); 1393 } 1394 1395 @Override 1396 Location getLocationForModule(Path dir) { 1397 return (pathLocations == null) ? null : pathLocations.get(dir); 1398 } 1399 1400 @Override 1401 Iterable<Set<Location>> listLocationsForModules() { 1402 if (moduleLocations == null) 1403 return Collections.emptySet(); 1404 Set<Location> locns = new LinkedHashSet<>(); 1405 moduleLocations.forEach((k, v) -> locns.add(v)); 1406 return Collections.singleton(locns); 1407 } 1408 1409 } 1410 1411 private class SystemModulesLocationHandler extends BasicLocationHandler { 1412 private Path systemJavaHome; 1413 private Path modules; 1414 private Map<String, ModuleLocationHandler> systemModules; 1415 1416 SystemModulesLocationHandler() { 1417 super(StandardLocation.SYSTEM_MODULES, Option.SYSTEM); 1418 systemJavaHome = Locations.javaHome; 1419 } 1420 1421 @Override 1422 boolean handleOption(Option option, String value) { 1423 if (!options.contains(option)) { 1424 return false; 1425 } 1426 1427 if (value == null) { 1428 systemJavaHome = Locations.javaHome; 1429 } else if (value.equals("none")) { 1430 systemJavaHome = null; 1431 } else { 1432 update(getPath(value)); 1433 } 1434 1435 modules = null; 1436 return true; 1437 } 1438 1439 @Override 1440 Collection<Path> getPaths() { 1441 return (systemJavaHome == null) ? null : Collections.singleton(systemJavaHome); 1442 } 1443 1444 @Override 1445 void setPaths(Iterable<? extends Path> files) throws IOException { 1446 if (files == null) { 1447 systemJavaHome = null; 1448 } else { 1449 Iterator<? extends Path> pathIter = files.iterator(); 1450 if (!pathIter.hasNext()) { 1451 throw new IllegalArgumentException("empty path for directory"); // TODO: FIXME 1452 } 1453 Path dir = pathIter.next(); 1454 if (pathIter.hasNext()) { 1455 throw new IllegalArgumentException("path too long for directory"); // TODO: FIXME 1456 } 1457 if (!Files.exists(dir)) { 1458 throw new FileNotFoundException(dir + ": does not exist"); 1459 } else if (!Files.isDirectory(dir)) { 1460 throw new IOException(dir + ": not a directory"); 1461 } 1462 update(dir); 1463 } 1464 } 1465 1466 private void update(Path p) { 1467 if (!isCurrentPlatform(p) && !Files.exists(p.resolve("lib").resolve("jrt-fs.jar")) && 1468 !Files.exists(systemJavaHome.resolve("modules"))) 1469 throw new IllegalArgumentException(p.toString()); 1470 systemJavaHome = p; 1471 modules = null; 1472 } 1473 1474 private boolean isCurrentPlatform(Path p) { 1475 try { 1476 return Files.isSameFile(p, Locations.javaHome); 1477 } catch (IOException ex) { 1478 throw new IllegalArgumentException(p.toString(), ex); 1479 } 1480 } 1481 1482 @Override 1483 Location getLocationForModule(String name) throws IOException { 1484 initSystemModules(); 1485 return systemModules.get(name); 1486 } 1487 1488 @Override 1489 Iterable<Set<Location>> listLocationsForModules() throws IOException { 1490 initSystemModules(); 1491 Set<Location> locns = new LinkedHashSet<>(); 1492 for (Location l: systemModules.values()) 1493 locns.add(l); 1494 return Collections.singleton(locns); 1495 } 1496 1497 private void initSystemModules() throws IOException { 1498 if (systemModules != null) { 1499 return; 1500 } 1501 1502 if (systemJavaHome == null) { 1503 systemModules = Collections.emptyMap(); 1504 return; 1505 } 1506 1507 if (modules == null) { 1508 try { 1509 URI jrtURI = URI.create("jrt:/"); 1510 FileSystem jrtfs; 1511 1512 if (isCurrentPlatform(systemJavaHome)) { 1513 jrtfs = FileSystems.getFileSystem(jrtURI); 1514 } else { 1515 try { 1516 Map<String, String> attrMap = 1517 Collections.singletonMap("java.home", systemJavaHome.toString()); 1518 jrtfs = FileSystems.newFileSystem(jrtURI, attrMap); 1519 } catch (ProviderNotFoundException ex) { 1520 URL javaHomeURL = systemJavaHome.resolve("jrt-fs.jar").toUri().toURL(); 1521 ClassLoader currentLoader = Locations.class.getClassLoader(); 1522 URLClassLoader fsLoader = 1523 new URLClassLoader(new URL[] {javaHomeURL}, currentLoader); 1524 1525 jrtfs = FileSystems.newFileSystem(jrtURI, Collections.emptyMap(), fsLoader); 1526 1527 closeables.add(fsLoader); 1528 } 1529 1530 closeables.add(jrtfs); 1531 } 1532 1533 modules = jrtfs.getPath("/modules"); 1534 } catch (FileSystemNotFoundException | ProviderNotFoundException e) { 1535 modules = systemJavaHome.resolve("modules"); 1536 if (!Files.exists(modules)) 1537 throw new IOException("can't find system classes", e); 1538 } 1539 } 1540 1541 systemModules = new LinkedHashMap<>(); 1542 try (DirectoryStream<Path> stream = Files.newDirectoryStream(modules, Files::isDirectory)) { 1543 for (Path entry : stream) { 1544 String moduleName = entry.getFileName().toString(); 1545 String name = location.getName() + "[" + moduleName + "]"; 1546 ModuleLocationHandler h = new ModuleLocationHandler(name, moduleName, 1547 Collections.singleton(entry), false, true); 1548 systemModules.put(moduleName, h); 1549 } 1550 } 1551 } 1552 } 1553 1554 Map<Location, LocationHandler> handlersForLocation; 1555 Map<Option, LocationHandler> handlersForOption; 1556 1557 void initHandlers() { 1558 handlersForLocation = new HashMap<>(); 1559 handlersForOption = new EnumMap<>(Option.class); 1560 1561 BasicLocationHandler[] handlers = { 1562 new BootClassPathLocationHandler(), 1563 new ClassPathLocationHandler(), 1564 new SimpleLocationHandler(StandardLocation.SOURCE_PATH, Option.SOURCE_PATH), 1565 new SimpleLocationHandler(StandardLocation.ANNOTATION_PROCESSOR_PATH, Option.PROCESSOR_PATH), 1566 new SimpleLocationHandler(StandardLocation.ANNOTATION_PROCESSOR_MODULE_PATH, Option.PROCESSOR_MODULE_PATH), 1567 new OutputLocationHandler(StandardLocation.CLASS_OUTPUT, Option.D), 1568 new OutputLocationHandler(StandardLocation.SOURCE_OUTPUT, Option.S), 1569 new OutputLocationHandler(StandardLocation.NATIVE_HEADER_OUTPUT, Option.H), 1570 new ModuleSourcePathLocationHandler(), 1571 // TODO: should UPGRADE_MODULE_PATH be merged with SYSTEM_MODULES? 1572 new ModulePathLocationHandler(StandardLocation.UPGRADE_MODULE_PATH, Option.UPGRADE_MODULE_PATH), 1573 new ModulePathLocationHandler(StandardLocation.MODULE_PATH, Option.MODULE_PATH), 1574 new SystemModulesLocationHandler(), 1575 }; 1576 1577 for (BasicLocationHandler h : handlers) { 1578 handlersForLocation.put(h.location, h); 1579 for (Option o : h.options) { 1580 handlersForOption.put(o, h); 1581 } 1582 } 1583 } 1584 1585 private Map<String, SearchPath> patchMap; 1586 1587 boolean handleOption(Option option, String value) { 1588 switch (option) { 1589 case PATCH_MODULE: 1590 if (value == null) { 1591 patchMap = null; 1592 } else { 1593 // Allow an extended syntax for --patch-module consisting of a series 1594 // of values separated by NULL characters. This is to facilitate 1595 // supporting deferred file manager options on the command line. 1596 // See Option.PATCH_MODULE for the code that composes these multiple 1597 // values. 1598 for (String v : value.split("\0")) { 1599 int eq = v.indexOf('='); 1600 if (eq > 0) { 1601 String mName = v.substring(0, eq); 1602 SearchPath mPatchPath = new SearchPath() 1603 .addFiles(v.substring(eq + 1)); 1604 boolean ok = true; 1605 for (Path p : mPatchPath) { 1606 Path mi = p.resolve("module-info.class"); 1607 if (Files.exists(mi)) { 1608 log.error(Errors.LocnModuleInfoNotAllowedOnPatchPath(mi)); 1609 ok = false; 1610 } 1611 } 1612 if (ok) { 1613 if (patchMap == null) { 1614 patchMap = new LinkedHashMap<>(); 1615 } 1616 patchMap.put(mName, mPatchPath); 1617 } 1618 } else { 1619 // Should not be able to get here; 1620 // this should be caught and handled in Option.PATCH_MODULE 1621 log.error(Errors.LocnInvalidArgForXpatch(value)); 1622 } 1623 } 1624 } 1625 return true; 1626 default: 1627 LocationHandler h = handlersForOption.get(option); 1628 return (h == null ? false : h.handleOption(option, value)); 1629 } 1630 } 1631 1632 boolean hasLocation(Location location) { 1633 LocationHandler h = getHandler(location); 1634 return (h == null ? false : h.isSet()); 1635 } 1636 1637 Collection<Path> getLocation(Location location) { 1638 LocationHandler h = getHandler(location); 1639 return (h == null ? null : h.getPaths()); 1640 } 1641 1642 Path getOutputLocation(Location location) { 1643 if (!location.isOutputLocation()) { 1644 throw new IllegalArgumentException(); 1645 } 1646 LocationHandler h = getHandler(location); 1647 return ((OutputLocationHandler) h).outputDir; 1648 } 1649 1650 void setLocation(Location location, Iterable<? extends Path> files) throws IOException { 1651 LocationHandler h = getHandler(location); 1652 if (h == null) { 1653 if (location.isOutputLocation()) { 1654 h = new OutputLocationHandler(location); 1655 } else { 1656 h = new SimpleLocationHandler(location); 1657 } 1658 handlersForLocation.put(location, h); 1659 } 1660 h.setPaths(files); 1661 } 1662 1663 Location getLocationForModule(Location location, String name) throws IOException { 1664 LocationHandler h = getHandler(location); 1665 return (h == null ? null : h.getLocationForModule(name)); 1666 } 1667 1668 Location getLocationForModule(Location location, Path dir) { 1669 LocationHandler h = getHandler(location); 1670 return (h == null ? null : h.getLocationForModule(dir)); 1671 } 1672 1673 String inferModuleName(Location location) { 1674 LocationHandler h = getHandler(location); 1675 return (h == null ? null : h.inferModuleName()); 1676 } 1677 1678 Iterable<Set<Location>> listLocationsForModules(Location location) throws IOException { 1679 LocationHandler h = getHandler(location); 1680 return (h == null ? null : h.listLocationsForModules()); 1681 } 1682 1683 protected LocationHandler getHandler(Location location) { 1684 Objects.requireNonNull(location); 1685 return (location instanceof LocationHandler) 1686 ? (LocationHandler) location 1687 : handlersForLocation.get(location); 1688 } 1689 1690 /** 1691 * Is this the name of an archive file? 1692 */ 1693 private boolean isArchive(Path file) { 1694 String n = StringUtils.toLowerCase(file.getFileName().toString()); 1695 return fsInfo.isFile(file) 1696 && (n.endsWith(".jar") || n.endsWith(".zip")); 1697 } 1698 1699 static Path normalize(Path p) { 1700 try { 1701 return p.toRealPath(); 1702 } catch (IOException e) { 1703 return p.toAbsolutePath().normalize(); 1704 } 1705 } 1706 1707 }