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