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