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