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