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