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