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