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