1 /* 2 * Copyright (c) 2003, 2016, 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.UncheckedIOException; 33 import java.net.URI; 34 import java.net.URL; 35 import java.net.URLClassLoader; 36 import java.nio.file.DirectoryIteratorException; 37 import java.nio.file.DirectoryStream; 38 import java.nio.file.FileSystem; 39 import java.nio.file.FileSystemNotFoundException; 40 import java.nio.file.FileSystems; 41 import java.nio.file.Files; 42 import java.nio.file.Path; 43 import java.nio.file.Paths; 44 import java.nio.file.ProviderNotFoundException; 45 import java.nio.file.spi.FileSystemProvider; 46 import java.util.ArrayList; 47 import java.util.Arrays; 48 import java.util.Collection; 49 import java.util.Collections; 50 import java.util.EnumMap; 51 import java.util.EnumSet; 52 import java.util.HashMap; 53 import java.util.HashSet; 54 import java.util.Iterator; 55 import java.util.LinkedHashMap; 56 import java.util.LinkedHashSet; 57 import java.util.List; 58 import java.util.Map; 59 import java.util.Objects; 60 import java.util.NoSuchElementException; 61 import java.util.Set; 62 import java.util.regex.Matcher; 63 import java.util.regex.Pattern; 64 import java.util.stream.Collectors; 65 import java.util.stream.Stream; 66 import java.util.zip.ZipFile; 67 68 import javax.lang.model.SourceVersion; 69 import javax.tools.JavaFileManager; 70 import javax.tools.JavaFileManager.Location; 71 import javax.tools.StandardJavaFileManager; 72 import javax.tools.StandardJavaFileManager.PathFactory; 73 import javax.tools.StandardLocation; 74 75 import com.sun.tools.javac.code.Lint; 76 import com.sun.tools.javac.main.Option; 77 import com.sun.tools.javac.resources.CompilerProperties.Errors; 78 import com.sun.tools.javac.resources.CompilerProperties.Warnings; 79 import com.sun.tools.javac.util.DefinedBy; 80 import com.sun.tools.javac.util.DefinedBy.Api; 81 import com.sun.tools.javac.util.ListBuffer; 82 import com.sun.tools.javac.util.Log; 83 import com.sun.tools.javac.util.Pair; 84 import com.sun.tools.javac.util.StringUtils; 85 86 import static javax.tools.StandardLocation.PLATFORM_CLASS_PATH; 87 88 import static com.sun.tools.javac.main.Option.BOOT_CLASS_PATH; 89 import static com.sun.tools.javac.main.Option.DJAVA_ENDORSED_DIRS; 90 import static com.sun.tools.javac.main.Option.DJAVA_EXT_DIRS; 91 import static com.sun.tools.javac.main.Option.ENDORSEDDIRS; 92 import static com.sun.tools.javac.main.Option.EXTDIRS; 93 import static com.sun.tools.javac.main.Option.XBOOTCLASSPATH; 94 import static com.sun.tools.javac.main.Option.XBOOTCLASSPATH_APPEND; 95 import static com.sun.tools.javac.main.Option.XBOOTCLASSPATH_PREPEND; 96 97 /** 98 * This class converts command line arguments, environment variables and system properties (in 99 * File.pathSeparator-separated String form) into a boot class path, user class path, and source 100 * path (in {@code Collection<String>} form). 101 * 102 * <p> 103 * <b>This is NOT part of any supported API. If you write code that depends on this, you do so at 104 * your own risk. This code and its internal interfaces are subject to change or deletion without 105 * notice.</b> 106 */ 107 public class Locations { 108 109 /** 110 * The log to use for warning output 111 */ 112 private Log log; 113 114 /** 115 * Access to (possibly cached) file info 116 */ 117 private FSInfo fsInfo; 118 119 /** 120 * Whether to warn about non-existent path elements 121 */ 122 private boolean warn; 123 124 private ModuleNameReader moduleNameReader; 125 126 private PathFactory pathFactory = Paths::get; 127 128 static final Path javaHome = FileSystems.getDefault().getPath(System.getProperty("java.home")); 129 static final Path thisSystemModules = javaHome.resolve("lib").resolve("modules"); 130 131 Map<Path, FileSystem> fileSystems = new LinkedHashMap<>(); 132 List<Closeable> closeables = new ArrayList<>(); 133 134 Locations() { 135 initHandlers(); 136 } 137 138 Path getPath(String first, String... more) { 139 return pathFactory.getPath(first, more); 140 } 141 142 public void close() throws IOException { 143 ListBuffer<IOException> list = new ListBuffer<>(); 144 closeables.forEach(closeable -> { 145 try { 146 closeable.close(); 147 } catch (IOException ex) { 148 list.add(ex); 149 } 150 }); 151 if (list.nonEmpty()) { 152 IOException ex = new IOException(); 153 for (IOException e: list) 154 ex.addSuppressed(e); 155 throw ex; 156 } 157 } 158 159 // could replace Lint by "boolean warn" 160 void update(Log log, Lint lint, FSInfo fsInfo) { 161 this.log = log; 162 warn = lint.isEnabled(Lint.LintCategory.PATH); 163 this.fsInfo = fsInfo; 164 } 165 166 void setPathFactory(PathFactory f) { 167 pathFactory = f; 168 } 169 170 boolean isDefaultBootClassPath() { 171 BootClassPathLocationHandler h 172 = (BootClassPathLocationHandler) getHandler(PLATFORM_CLASS_PATH); 173 return h.isDefault(); 174 } 175 176 /** 177 * Split a search path into its elements. Empty path elements will be ignored. 178 * 179 * @param searchPath The search path to be split 180 * @return The elements of the path 181 */ 182 private Iterable<Path> getPathEntries(String searchPath) { 183 return getPathEntries(searchPath, null); 184 } 185 186 /** 187 * Split a search path into its elements. If emptyPathDefault is not null, all empty elements in the 188 * path, including empty elements at either end of the path, will be replaced with the value of 189 * emptyPathDefault. 190 * 191 * @param searchPath The search path to be split 192 * @param emptyPathDefault The value to substitute for empty path elements, or null, to ignore 193 * empty path elements 194 * @return The elements of the path 195 */ 196 private Iterable<Path> getPathEntries(String searchPath, Path emptyPathDefault) { 197 ListBuffer<Path> entries = new ListBuffer<>(); 198 for (String s: searchPath.split(Pattern.quote(File.pathSeparator), -1)) { 199 if (s.isEmpty()) { 200 if (emptyPathDefault != null) { 201 entries.add(emptyPathDefault); 202 } 203 } else { 204 entries.add(getPath(s)); 205 } 206 } 207 return entries; 208 } 209 210 /** 211 * Utility class to help evaluate a path option. Duplicate entries are ignored, jar class paths 212 * can be expanded. 213 */ 214 private class SearchPath extends LinkedHashSet<Path> { 215 216 private static final long serialVersionUID = 0; 217 218 private boolean expandJarClassPaths = false; 219 private final Set<Path> canonicalValues = new HashSet<>(); 220 221 public SearchPath expandJarClassPaths(boolean x) { 222 expandJarClassPaths = x; 223 return this; 224 } 225 226 /** 227 * What to use when path element is the empty string 228 */ 229 private Path emptyPathDefault = null; 230 231 public SearchPath emptyPathDefault(Path x) { 232 emptyPathDefault = x; 233 return this; 234 } 235 236 public SearchPath addDirectories(String dirs, boolean warn) { 237 boolean prev = expandJarClassPaths; 238 expandJarClassPaths = true; 239 try { 240 if (dirs != null) { 241 for (Path dir : getPathEntries(dirs)) { 242 addDirectory(dir, warn); 243 } 244 } 245 return this; 246 } finally { 247 expandJarClassPaths = prev; 248 } 249 } 250 251 public SearchPath addDirectories(String dirs) { 252 return addDirectories(dirs, warn); 253 } 254 255 private void addDirectory(Path dir, boolean warn) { 256 if (!Files.isDirectory(dir)) { 257 if (warn) { 258 log.warning(Lint.LintCategory.PATH, 259 "dir.path.element.not.found", dir); 260 } 261 return; 262 } 263 264 try (Stream<Path> s = Files.list(dir)) { 265 s.filter(dirEntry -> isArchive(dirEntry)) 266 .forEach(dirEntry -> addFile(dirEntry, warn)); 267 } catch (IOException ignore) { 268 } 269 } 270 271 public SearchPath addFiles(String files, boolean warn) { 272 if (files != null) { 273 addFiles(getPathEntries(files, emptyPathDefault), warn); 274 } 275 return this; 276 } 277 278 public SearchPath addFiles(String files) { 279 return addFiles(files, warn); 280 } 281 282 public SearchPath addFiles(Iterable<? extends Path> files, boolean warn) { 283 if (files != null) { 284 for (Path file : files) { 285 addFile(file, warn); 286 } 287 } 288 return this; 289 } 290 291 public SearchPath addFiles(Iterable<? extends Path> files) { 292 return addFiles(files, warn); 293 } 294 295 public void addFile(Path file, boolean warn) { 296 if (contains(file)) { 297 // discard duplicates 298 return; 299 } 300 301 if (!fsInfo.exists(file)) { 302 /* No such file or directory exists */ 303 if (warn) { 304 log.warning(Lint.LintCategory.PATH, 305 "path.element.not.found", file); 306 } 307 super.add(file); 308 return; 309 } 310 311 Path canonFile = fsInfo.getCanonicalFile(file); 312 if (canonicalValues.contains(canonFile)) { 313 /* Discard duplicates and avoid infinite recursion */ 314 return; 315 } 316 317 if (fsInfo.isFile(file)) { 318 /* File is an ordinary file. */ 319 if ( !file.getFileName().toString().endsWith(".jmod") 320 && !file.endsWith("modules")) { 321 if (!isArchive(file)) { 322 /* Not a recognized extension; open it to see if 323 it looks like a valid zip file. */ 324 try { 325 FileSystems.newFileSystem(file, null).close(); 326 if (warn) { 327 log.warning(Lint.LintCategory.PATH, 328 "unexpected.archive.file", file); 329 } 330 } catch (IOException | ProviderNotFoundException e) { 331 // FIXME: include e.getLocalizedMessage in warning 332 if (warn) { 333 log.warning(Lint.LintCategory.PATH, 334 "invalid.archive.file", file); 335 } 336 return; 337 } 338 } else { 339 if (fsInfo.getJarFSProvider() == null) { 340 log.error(Errors.NoZipfsForArchive(file)); 341 return ; 342 } 343 } 344 } 345 } 346 347 /* Now what we have left is either a directory or a file name 348 conforming to archive naming convention */ 349 super.add(file); 350 canonicalValues.add(canonFile); 351 352 if (expandJarClassPaths && fsInfo.isFile(file) && !file.endsWith("modules")) { 353 addJarClassPath(file, warn); 354 } 355 } 356 357 // Adds referenced classpath elements from a jar's Class-Path 358 // Manifest entry. In some future release, we may want to 359 // update this code to recognize URLs rather than simple 360 // filenames, but if we do, we should redo all path-related code. 361 private void addJarClassPath(Path jarFile, boolean warn) { 362 try { 363 for (Path f : fsInfo.getJarClassPath(jarFile)) { 364 addFile(f, warn); 365 } 366 } catch (IOException e) { 367 log.error("error.reading.file", jarFile, JavacFileManager.getMessage(e)); 368 } 369 } 370 } 371 372 /** 373 * Base class for handling support for the representation of Locations. 374 * 375 * Locations are (by design) opaque handles that can easily be implemented 376 * by enums like StandardLocation. Within JavacFileManager, each Location 377 * has an associated LocationHandler, which provides much of the appropriate 378 * functionality for the corresponding Location. 379 * 380 * @see #initHandlers 381 * @see #getHandler 382 */ 383 protected abstract class LocationHandler { 384 385 /** 386 * @see JavaFileManager#handleOption 387 */ 388 abstract boolean handleOption(Option option, String value); 389 390 /** 391 * @see StandardJavaFileManager#hasLocation 392 */ 393 boolean isSet() { 394 return (getPaths() != null); 395 } 396 397 /** 398 * @see StandardJavaFileManager#getLocation 399 */ 400 abstract Collection<Path> getPaths(); 401 402 /** 403 * @see StandardJavaFileManager#setLocation 404 */ 405 abstract void setPaths(Iterable<? extends Path> files) throws IOException; 406 407 /** 408 * @see JavaFileManager#getModuleLocation(Location, String) 409 */ 410 Location getModuleLocation(String moduleName) throws IOException { 411 return null; 412 } 413 414 /** 415 * @see JavaFileManager#getModuleLocation(Location, JavaFileObject, String) 416 */ 417 Location getModuleLocation(Path dir) { 418 return null; 419 } 420 421 /** 422 * @see JavaFileManager#inferModuleName 423 */ 424 String inferModuleName() { 425 return null; 426 } 427 428 /** 429 * @see JavaFileManager#listModuleLocations 430 */ 431 Iterable<Set<Location>> listModuleLocations() throws IOException { 432 return null; 433 } 434 } 435 436 /** 437 * A LocationHandler for a given Location, and associated set of options. 438 */ 439 private abstract class BasicLocationHandler extends LocationHandler { 440 441 final Location location; 442 final Set<Option> options; 443 444 /** 445 * Create a handler. The location and options provide a way to map from a location or an 446 * option to the corresponding handler. 447 * 448 * @param location the location for which this is the handler 449 * @param options the options affecting this location 450 * @see #initHandlers 451 */ 452 protected BasicLocationHandler(Location location, Option... options) { 453 this.location = location; 454 this.options = options.length == 0 455 ? EnumSet.noneOf(Option.class) 456 : EnumSet.copyOf(Arrays.asList(options)); 457 } 458 } 459 460 /** 461 * General purpose implementation for output locations, such as -d/CLASS_OUTPUT and 462 * -s/SOURCE_OUTPUT. All options are treated as equivalent (i.e. aliases.) 463 * The value is a single file, possibly null. 464 */ 465 private class OutputLocationHandler extends BasicLocationHandler { 466 467 private Path outputDir; 468 private Map<String, Location> moduleLocations; 469 470 OutputLocationHandler(Location location, Option... options) { 471 super(location, options); 472 } 473 474 @Override 475 boolean handleOption(Option option, String value) { 476 if (!options.contains(option)) { 477 return false; 478 } 479 480 // TODO: could/should validate outputDir exists and is a directory 481 // need to decide how best to report issue for benefit of 482 // direct API call on JavaFileManager.handleOption(specifies IAE) 483 // vs. command line decoding. 484 outputDir = (value == null) ? null : getPath(value); 485 return true; 486 } 487 488 @Override 489 Collection<Path> getPaths() { 490 return (outputDir == null) ? null : Collections.singleton(outputDir); 491 } 492 493 @Override 494 void setPaths(Iterable<? extends Path> files) throws IOException { 495 if (files == null) { 496 outputDir = null; 497 } else { 498 Iterator<? extends Path> pathIter = files.iterator(); 499 if (!pathIter.hasNext()) { 500 throw new IllegalArgumentException("empty path for directory"); 501 } 502 Path dir = pathIter.next(); 503 if (pathIter.hasNext()) { 504 throw new IllegalArgumentException("path too long for directory"); 505 } 506 if (!Files.exists(dir)) { 507 throw new FileNotFoundException(dir + ": does not exist"); 508 } else if (!Files.isDirectory(dir)) { 509 throw new IOException(dir + ": not a directory"); 510 } 511 outputDir = dir; 512 } 513 moduleLocations = null; 514 } 515 516 @Override 517 Location getModuleLocation(String name) { 518 if (moduleLocations == null) 519 moduleLocations = new HashMap<>(); 520 Location l = moduleLocations.get(name); 521 if (l == null) { 522 l = new ModuleLocationHandler(location.getName() + "[" + name + "]", 523 name, 524 Collections.singleton(outputDir.resolve(name)), 525 true, false); 526 moduleLocations.put(name, l); 527 } 528 return l; 529 } 530 } 531 532 /** 533 * General purpose implementation for search path locations, 534 * such as -sourcepath/SOURCE_PATH and -processorPath/ANNOTATION_PROCESSOR_PATH. 535 * All options are treated as equivalent (i.e. aliases.) 536 * The value is an ordered set of files and/or directories. 537 */ 538 private class SimpleLocationHandler extends BasicLocationHandler { 539 540 protected Collection<Path> searchPath; 541 542 SimpleLocationHandler(Location location, Option... options) { 543 super(location, options); 544 } 545 546 @Override 547 boolean handleOption(Option option, String value) { 548 if (!options.contains(option)) { 549 return false; 550 } 551 searchPath = value == null ? null 552 : Collections.unmodifiableCollection(createPath().addFiles(value)); 553 return true; 554 } 555 556 @Override 557 Collection<Path> getPaths() { 558 return searchPath; 559 } 560 561 @Override 562 void setPaths(Iterable<? extends Path> files) { 563 SearchPath p; 564 if (files == null) { 565 p = computePath(null); 566 } else { 567 p = createPath().addFiles(files); 568 } 569 searchPath = Collections.unmodifiableCollection(p); 570 } 571 572 protected SearchPath computePath(String value) { 573 return createPath().addFiles(value); 574 } 575 576 protected SearchPath createPath() { 577 return new SearchPath(); 578 } 579 } 580 581 /** 582 * Subtype of SimpleLocationHandler for -classpath/CLASS_PATH. 583 * If no value is given, a default is provided, based on system properties and other values. 584 */ 585 private class ClassPathLocationHandler extends SimpleLocationHandler { 586 587 ClassPathLocationHandler() { 588 super(StandardLocation.CLASS_PATH, Option.CLASS_PATH); 589 } 590 591 @Override 592 Collection<Path> getPaths() { 593 lazy(); 594 return searchPath; 595 } 596 597 @Override 598 protected SearchPath computePath(String value) { 599 String cp = value; 600 601 // CLASSPATH environment variable when run from `javac'. 602 if (cp == null) { 603 cp = System.getProperty("env.class.path"); 604 } 605 606 // If invoked via a java VM (not the javac launcher), use the 607 // platform class path 608 if (cp == null && System.getProperty("application.home") == null) { 609 cp = System.getProperty("java.class.path"); 610 } 611 612 // Default to current working directory. 613 if (cp == null) { 614 cp = "."; 615 } 616 617 return createPath().addFiles(cp); 618 } 619 620 @Override 621 protected SearchPath createPath() { 622 return new SearchPath() 623 .expandJarClassPaths(true) // Only search user jars for Class-Paths 624 .emptyPathDefault(getPath(".")); // Empty path elt ==> current directory 625 } 626 627 private void lazy() { 628 if (searchPath == null) { 629 setPaths(null); 630 } 631 } 632 } 633 634 /** 635 * Custom subtype of LocationHandler for PLATFORM_CLASS_PATH. 636 * Various options are supported for different components of the 637 * platform class path. 638 * Setting a value with setLocation overrides all existing option values. 639 * Setting any option overrides any value set with setLocation, and 640 * reverts to using default values for options that have not been set. 641 * Setting -bootclasspath or -Xbootclasspath overrides any existing 642 * value for -Xbootclasspath/p: and -Xbootclasspath/a:. 643 */ 644 private class BootClassPathLocationHandler extends BasicLocationHandler { 645 646 private Collection<Path> searchPath; 647 final Map<Option, String> optionValues = new EnumMap<>(Option.class); 648 649 /** 650 * Is the bootclasspath the default? 651 */ 652 private boolean isDefault; 653 654 BootClassPathLocationHandler() { 655 super(StandardLocation.PLATFORM_CLASS_PATH, 656 Option.BOOT_CLASS_PATH, Option.XBOOTCLASSPATH, 657 Option.XBOOTCLASSPATH_PREPEND, 658 Option.XBOOTCLASSPATH_APPEND, 659 Option.ENDORSEDDIRS, Option.DJAVA_ENDORSED_DIRS, 660 Option.EXTDIRS, Option.DJAVA_EXT_DIRS); 661 } 662 663 boolean isDefault() { 664 lazy(); 665 return isDefault; 666 } 667 668 @Override 669 boolean handleOption(Option option, String value) { 670 if (!options.contains(option)) { 671 return false; 672 } 673 674 option = canonicalize(option); 675 optionValues.put(option, value); 676 if (option == BOOT_CLASS_PATH) { 677 optionValues.remove(XBOOTCLASSPATH_PREPEND); 678 optionValues.remove(XBOOTCLASSPATH_APPEND); 679 } 680 searchPath = null; // reset to "uninitialized" 681 return true; 682 } 683 // where 684 // TODO: would be better if option aliasing was handled at a higher 685 // level 686 private Option canonicalize(Option option) { 687 switch (option) { 688 case XBOOTCLASSPATH: 689 return Option.BOOT_CLASS_PATH; 690 case DJAVA_ENDORSED_DIRS: 691 return Option.ENDORSEDDIRS; 692 case DJAVA_EXT_DIRS: 693 return Option.EXTDIRS; 694 default: 695 return option; 696 } 697 } 698 699 @Override 700 Collection<Path> getPaths() { 701 lazy(); 702 return searchPath; 703 } 704 705 @Override 706 void setPaths(Iterable<? extends Path> files) { 707 if (files == null) { 708 searchPath = null; // reset to "uninitialized" 709 } else { 710 isDefault = false; 711 SearchPath p = new SearchPath().addFiles(files, false); 712 searchPath = Collections.unmodifiableCollection(p); 713 optionValues.clear(); 714 } 715 } 716 717 SearchPath computePath() throws IOException { 718 SearchPath path = new SearchPath(); 719 720 String bootclasspathOpt = optionValues.get(BOOT_CLASS_PATH); 721 String endorseddirsOpt = optionValues.get(ENDORSEDDIRS); 722 String extdirsOpt = optionValues.get(EXTDIRS); 723 String xbootclasspathPrependOpt = optionValues.get(XBOOTCLASSPATH_PREPEND); 724 String xbootclasspathAppendOpt = optionValues.get(XBOOTCLASSPATH_APPEND); 725 path.addFiles(xbootclasspathPrependOpt); 726 727 if (endorseddirsOpt != null) { 728 path.addDirectories(endorseddirsOpt); 729 } else { 730 path.addDirectories(System.getProperty("java.endorsed.dirs"), false); 731 } 732 733 if (bootclasspathOpt != null) { 734 path.addFiles(bootclasspathOpt); 735 } else { 736 // Standard system classes for this compiler's release. 737 Collection<Path> systemClasses = systemClasses(); 738 if (systemClasses != null) { 739 path.addFiles(systemClasses, false); 740 } else { 741 // fallback to the value of sun.boot.class.path 742 String files = System.getProperty("sun.boot.class.path"); 743 path.addFiles(files, false); 744 } 745 } 746 747 path.addFiles(xbootclasspathAppendOpt); 748 749 // Strictly speaking, standard extensions are not bootstrap 750 // classes, but we treat them identically, so we'll pretend 751 // that they are. 752 if (extdirsOpt != null) { 753 path.addDirectories(extdirsOpt); 754 } else { 755 // Add lib/jfxrt.jar to the search path 756 Path jfxrt = javaHome.resolve("lib/jfxrt.jar"); 757 if (Files.exists(jfxrt)) { 758 path.addFile(jfxrt, false); 759 } 760 path.addDirectories(System.getProperty("java.ext.dirs"), false); 761 } 762 763 isDefault = 764 (xbootclasspathPrependOpt == null) 765 && (bootclasspathOpt == null) 766 && (xbootclasspathAppendOpt == null); 767 768 return path; 769 } 770 771 /** 772 * Return a collection of files containing system classes. 773 * Returns {@code null} if not running on a modular image. 774 * 775 * @throws UncheckedIOException if an I/O errors occurs 776 */ 777 private Collection<Path> systemClasses() throws IOException { 778 // Return "modules" jimage file if available 779 if (Files.isRegularFile(thisSystemModules)) { 780 return Collections.singleton(thisSystemModules); 781 } 782 783 // Exploded module image 784 Path modules = javaHome.resolve("modules"); 785 if (Files.isDirectory(modules.resolve("java.base"))) { 786 try (Stream<Path> listedModules = Files.list(modules)) { 787 return listedModules.collect(Collectors.toList()); 788 } 789 } 790 791 // not a modular image that we know about 792 return null; 793 } 794 795 private void lazy() { 796 if (searchPath == null) { 797 try { 798 searchPath = Collections.unmodifiableCollection(computePath()); 799 } catch (IOException e) { 800 // TODO: need better handling here, e.g. javac Abort? 801 throw new UncheckedIOException(e); 802 } 803 } 804 } 805 } 806 807 /** 808 * A LocationHander to represent modules found from a module-oriented 809 * location such as MODULE_SOURCE_PATH, UPGRADE_MODULE_PATH, 810 * SYSTEM_MODULES and MODULE_PATH. 811 * 812 * The Location can be specified to accept overriding classes from the 813 * {@code --patch-module <module>=<path> } parameter. 814 */ 815 private class ModuleLocationHandler extends LocationHandler implements Location { 816 protected final String name; 817 protected final String moduleName; 818 protected final Collection<Path> searchPath; 819 protected final Collection<Path> searchPathWithOverrides; 820 protected final boolean output; 821 822 ModuleLocationHandler(String name, String moduleName, Collection<Path> searchPath, 823 boolean output, boolean allowOverrides) { 824 this.name = name; 825 this.moduleName = moduleName; 826 this.searchPath = searchPath; 827 this.output = output; 828 829 if (allowOverrides && patchMap != null) { 830 SearchPath mPatch = patchMap.get(moduleName); 831 if (mPatch != null) { 832 SearchPath sp = new SearchPath(); 833 sp.addAll(mPatch); 834 sp.addAll(searchPath); 835 searchPathWithOverrides = sp; 836 } else { 837 searchPathWithOverrides = searchPath; 838 } 839 } else { 840 searchPathWithOverrides = searchPath; 841 } 842 } 843 844 @Override @DefinedBy(Api.COMPILER) 845 public String getName() { 846 return name; 847 } 848 849 @Override @DefinedBy(Api.COMPILER) 850 public boolean isOutputLocation() { 851 return output; 852 } 853 854 @Override // defined by LocationHandler 855 boolean handleOption(Option option, String value) { 856 throw new UnsupportedOperationException(); 857 } 858 859 @Override // defined by LocationHandler 860 Collection<Path> getPaths() { 861 // For now, we always return searchPathWithOverrides. This may differ from the 862 // JVM behavior if there is a module-info.class to be found in the overriding 863 // classes. 864 return searchPathWithOverrides; 865 } 866 867 @Override // defined by LocationHandler 868 void setPaths(Iterable<? extends Path> files) throws IOException { 869 throw new UnsupportedOperationException(); 870 } 871 872 @Override // defined by LocationHandler 873 String inferModuleName() { 874 return moduleName; 875 } 876 } 877 878 /** 879 * A LocationHandler for simple module-oriented search paths, 880 * like UPGRADE_MODULE_PATH and MODULE_PATH. 881 */ 882 private class ModulePathLocationHandler extends SimpleLocationHandler { 883 ModulePathLocationHandler(Location location, Option... options) { 884 super(location, options); 885 } 886 887 @Override 888 public boolean handleOption(Option option, String value) { 889 if (!options.contains(option)) { 890 return false; 891 } 892 setPaths(value == null ? null : getPathEntries(value)); 893 return true; 894 } 895 896 @Override 897 Iterable<Set<Location>> listModuleLocations() { 898 if (searchPath == null) 899 return Collections.emptyList(); 900 901 return () -> new ModulePathIterator(); 902 } 903 904 @Override 905 void setPaths(Iterable<? extends Path> paths) { 906 if (paths != null) { 907 for (Path p: paths) { 908 checkValidModulePathEntry(p); 909 } 910 } 911 super.setPaths(paths); 912 } 913 914 private void checkValidModulePathEntry(Path p) { 915 if (Files.isDirectory(p)) { 916 // either an exploded module or a directory of modules 917 return; 918 } 919 920 String name = p.getFileName().toString(); 921 int lastDot = name.lastIndexOf("."); 922 if (lastDot > 0) { 923 switch (name.substring(lastDot)) { 924 case ".jar": 925 case ".jmod": 926 return; 927 } 928 } 929 throw new IllegalArgumentException(p.toString()); 930 } 931 932 class ModulePathIterator implements Iterator<Set<Location>> { 933 Iterator<Path> pathIter = searchPath.iterator(); 934 int pathIndex = 0; 935 Set<Location> next = null; 936 937 @Override 938 public boolean hasNext() { 939 if (next != null) 940 return true; 941 942 while (next == null) { 943 if (pathIter.hasNext()) { 944 Path path = pathIter.next(); 945 if (Files.isDirectory(path)) { 946 next = scanDirectory(path); 947 } else { 948 next = scanFile(path); 949 } 950 pathIndex++; 951 } else 952 return false; 953 } 954 return true; 955 } 956 957 @Override 958 public Set<Location> next() { 959 hasNext(); 960 if (next != null) { 961 Set<Location> result = next; 962 next = null; 963 return result; 964 } 965 throw new NoSuchElementException(); 966 } 967 968 private Set<Location> scanDirectory(Path path) { 969 Set<Path> paths = new LinkedHashSet<>(); 970 Path moduleInfoClass = null; 971 try (DirectoryStream<Path> stream = Files.newDirectoryStream(path)) { 972 for (Path entry: stream) { 973 if (entry.endsWith("module-info.class")) { 974 moduleInfoClass = entry; 975 break; // no need to continue scanning 976 } 977 paths.add(entry); 978 } 979 } catch (DirectoryIteratorException | IOException ignore) { 980 log.error(Errors.LocnCantReadDirectory(path)); 981 return Collections.emptySet(); 982 } 983 984 if (moduleInfoClass != null) { 985 // It's an exploded module directly on the module path. 986 // We can't infer module name from the directory name, so have to 987 // read module-info.class. 988 try { 989 String moduleName = readModuleName(moduleInfoClass); 990 String name = location.getName() 991 + "[" + pathIndex + ":" + moduleName + "]"; 992 ModuleLocationHandler l = new ModuleLocationHandler(name, moduleName, 993 Collections.singleton(path), false, true); 994 return Collections.singleton(l); 995 } catch (ModuleNameReader.BadClassFile e) { 996 log.error(Errors.LocnBadModuleInfo(path)); 997 return Collections.emptySet(); 998 } catch (IOException e) { 999 log.error(Errors.LocnCantReadFile(path)); 1000 return Collections.emptySet(); 1001 } 1002 } 1003 1004 // A directory of modules 1005 Set<Location> result = new LinkedHashSet<>(); 1006 int index = 0; 1007 for (Path entry : paths) { 1008 Pair<String,Path> module = inferModuleName(entry); 1009 if (module == null) { 1010 // diagnostic reported if necessary; skip to next 1011 continue; 1012 } 1013 String moduleName = module.fst; 1014 Path modulePath = module.snd; 1015 String name = location.getName() 1016 + "[" + pathIndex + "." + (index++) + ":" + moduleName + "]"; 1017 ModuleLocationHandler l = new ModuleLocationHandler(name, moduleName, 1018 Collections.singleton(modulePath), false, true); 1019 result.add(l); 1020 } 1021 return result; 1022 } 1023 1024 private Set<Location> scanFile(Path path) { 1025 Pair<String,Path> module = inferModuleName(path); 1026 if (module == null) { 1027 // diagnostic reported if necessary 1028 return Collections.emptySet(); 1029 } 1030 String moduleName = module.fst; 1031 Path modulePath = module.snd; 1032 String name = location.getName() 1033 + "[" + pathIndex + ":" + moduleName + "]"; 1034 ModuleLocationHandler l = new ModuleLocationHandler(name, moduleName, 1035 Collections.singleton(modulePath), false, true); 1036 return Collections.singleton(l); 1037 } 1038 1039 private Pair<String,Path> inferModuleName(Path p) { 1040 if (Files.isDirectory(p)) { 1041 if (Files.exists(p.resolve("module-info.class"))) { 1042 String name = p.getFileName().toString(); 1043 if (SourceVersion.isName(name)) 1044 return new Pair<>(name, p); 1045 } 1046 return null; 1047 } 1048 1049 if (p.getFileName().toString().endsWith(".jar") && fsInfo.exists(p)) { 1050 try (FileSystem fs = FileSystems.newFileSystem(p, null)) { 1051 Path moduleInfoClass = fs.getPath("module-info.class"); 1052 if (Files.exists(moduleInfoClass)) { 1053 String moduleName = readModuleName(moduleInfoClass); 1054 return new Pair<>(moduleName, p); 1055 } 1056 } catch (ModuleNameReader.BadClassFile e) { 1057 log.error(Errors.LocnBadModuleInfo(p)); 1058 return null; 1059 } catch (IOException e) { 1060 log.error(Errors.LocnCantReadFile(p)); 1061 return null; 1062 } catch (ProviderNotFoundException e) { 1063 log.error(Errors.NoZipfsForArchive(p)); 1064 return null; 1065 } 1066 1067 //automatic module: 1068 String fn = p.getFileName().toString(); 1069 //from ModulePath.deriveModuleDescriptor: 1070 1071 // drop .jar 1072 String mn = fn.substring(0, fn.length()-4); 1073 1074 // find first occurrence of -${NUMBER}. or -${NUMBER}$ 1075 Matcher matcher = Pattern.compile("-(\\d+(\\.|$))").matcher(mn); 1076 if (matcher.find()) { 1077 int start = matcher.start(); 1078 1079 mn = mn.substring(0, start); 1080 } 1081 1082 // finally clean up the module name 1083 mn = mn.replaceAll("[^A-Za-z0-9]", ".") // replace non-alphanumeric 1084 .replaceAll("(\\.)(\\1)+", ".") // collapse repeating dots 1085 .replaceAll("^\\.", "") // drop leading dots 1086 .replaceAll("\\.$", ""); // drop trailing dots 1087 1088 1089 if (!mn.isEmpty()) { 1090 return new Pair<>(mn, p); 1091 } 1092 1093 log.error(Errors.LocnCantGetModuleNameForJar(p)); 1094 return null; 1095 } 1096 1097 if (p.getFileName().toString().endsWith(".jmod")) { 1098 try { 1099 FileSystem fs = fileSystems.get(p); 1100 if (fs == null) { 1101 URI uri = URI.create("jar:" + p.toUri()); 1102 fs = FileSystems.newFileSystem(uri, Collections.emptyMap(), null); 1103 try { 1104 Path moduleInfoClass = fs.getPath("classes/module-info.class"); 1105 String moduleName = readModuleName(moduleInfoClass); 1106 Path modulePath = fs.getPath("classes"); 1107 fileSystems.put(p, fs); 1108 closeables.add(fs); 1109 fs = null; // prevent fs being closed in the finally clause 1110 return new Pair<>(moduleName, modulePath); 1111 } finally { 1112 if (fs != null) 1113 fs.close(); 1114 } 1115 } 1116 } catch (ModuleNameReader.BadClassFile e) { 1117 log.error(Errors.LocnBadModuleInfo(p)); 1118 } catch (IOException | ProviderNotFoundException e) { 1119 log.error(Errors.LocnCantReadFile(p)); 1120 return null; 1121 } 1122 } 1123 1124 if (warn && false) { // temp disable, when enabled, massage examples.not-yet.txt suitably. 1125 log.warning(Warnings.LocnUnknownFileOnModulePath(p)); 1126 } 1127 return null; 1128 } 1129 1130 private String readModuleName(Path path) throws IOException, ModuleNameReader.BadClassFile { 1131 if (moduleNameReader == null) 1132 moduleNameReader = new ModuleNameReader(); 1133 return moduleNameReader.readModuleName(path); 1134 } 1135 } 1136 1137 } 1138 1139 private class ModuleSourcePathLocationHandler extends BasicLocationHandler { 1140 1141 private Map<String, Location> moduleLocations; 1142 private Map<Path, Location> pathLocations; 1143 1144 1145 ModuleSourcePathLocationHandler() { 1146 super(StandardLocation.MODULE_SOURCE_PATH, 1147 Option.MODULE_SOURCE_PATH); 1148 } 1149 1150 @Override 1151 boolean handleOption(Option option, String value) { 1152 init(value); 1153 return true; 1154 } 1155 1156 void init(String value) { 1157 Collection<String> segments = new ArrayList<>(); 1158 for (String s: value.split(File.pathSeparator)) { 1159 expandBraces(s, segments); 1160 } 1161 1162 Map<String, Collection<Path>> map = new LinkedHashMap<>(); 1163 final String MARKER = "*"; 1164 for (String seg: segments) { 1165 int markStart = seg.indexOf(MARKER); 1166 if (markStart == -1) { 1167 add(map, getPath(seg), null); 1168 } else { 1169 if (markStart == 0 || !isSeparator(seg.charAt(markStart - 1))) { 1170 throw new IllegalArgumentException("illegal use of " + MARKER + " in " + seg); 1171 } 1172 Path prefix = getPath(seg.substring(0, markStart - 1)); 1173 Path suffix; 1174 int markEnd = markStart + MARKER.length(); 1175 if (markEnd == seg.length()) { 1176 suffix = null; 1177 } else if (!isSeparator(seg.charAt(markEnd)) 1178 || seg.indexOf(MARKER, markEnd) != -1) { 1179 throw new IllegalArgumentException("illegal use of " + MARKER + " in " + seg); 1180 } else { 1181 suffix = getPath(seg.substring(markEnd + 1)); 1182 } 1183 add(map, prefix, suffix); 1184 } 1185 } 1186 1187 moduleLocations = new LinkedHashMap<>(); 1188 pathLocations = new LinkedHashMap<>(); 1189 map.forEach((k, v) -> { 1190 String name = location.getName() + "[" + k + "]"; 1191 ModuleLocationHandler h = new ModuleLocationHandler(name, k, v, false, false); 1192 moduleLocations.put(k, h); 1193 v.forEach(p -> pathLocations.put(normalize(p), h)); 1194 }); 1195 } 1196 1197 private boolean isSeparator(char ch) { 1198 // allow both separators on Windows 1199 return (ch == File.separatorChar) || (ch == '/'); 1200 } 1201 1202 void add(Map<String, Collection<Path>> map, Path prefix, Path suffix) { 1203 if (!Files.isDirectory(prefix)) { 1204 if (warn) { 1205 String key = Files.exists(prefix) 1206 ? "dir.path.element.not.directory" 1207 : "dir.path.element.not.found"; 1208 log.warning(Lint.LintCategory.PATH, key, prefix); 1209 } 1210 return; 1211 } 1212 try (DirectoryStream<Path> stream = Files.newDirectoryStream(prefix, path -> Files.isDirectory(path))) { 1213 for (Path entry: stream) { 1214 Path path = (suffix == null) ? entry : entry.resolve(suffix); 1215 if (Files.isDirectory(path)) { 1216 String name = entry.getFileName().toString(); 1217 Collection<Path> paths = map.get(name); 1218 if (paths == null) 1219 map.put(name, paths = new ArrayList<>()); 1220 paths.add(path); 1221 } 1222 } 1223 } catch (IOException e) { 1224 // TODO? What to do? 1225 System.err.println(e); 1226 } 1227 } 1228 1229 private void expandBraces(String value, Collection<String> results) { 1230 int depth = 0; 1231 int start = -1; 1232 String prefix = null; 1233 String suffix = null; 1234 for (int i = 0; i < value.length(); i++) { 1235 switch (value.charAt(i)) { 1236 case '{': 1237 depth++; 1238 if (depth == 1) { 1239 prefix = value.substring(0, i); 1240 suffix = value.substring(getMatchingBrace(value, i) + 1); 1241 start = i + 1; 1242 } 1243 break; 1244 1245 case ',': 1246 if (depth == 1) { 1247 String elem = value.substring(start, i); 1248 expandBraces(prefix + elem + suffix, results); 1249 start = i + 1; 1250 } 1251 break; 1252 1253 case '}': 1254 switch (depth) { 1255 case 0: 1256 throw new IllegalArgumentException("mismatched braces"); 1257 1258 case 1: 1259 String elem = value.substring(start, i); 1260 expandBraces(prefix + elem + suffix, results); 1261 return; 1262 1263 default: 1264 depth--; 1265 } 1266 break; 1267 } 1268 } 1269 if (depth > 0) 1270 throw new IllegalArgumentException("mismatched braces"); 1271 results.add(value); 1272 } 1273 1274 int getMatchingBrace(String value, int offset) { 1275 int depth = 1; 1276 for (int i = offset + 1; i < value.length(); i++) { 1277 switch (value.charAt(i)) { 1278 case '{': 1279 depth++; 1280 break; 1281 1282 case '}': 1283 if (--depth == 0) 1284 return i; 1285 break; 1286 } 1287 } 1288 throw new IllegalArgumentException("mismatched braces"); 1289 } 1290 1291 @Override 1292 boolean isSet() { 1293 return (moduleLocations != null); 1294 } 1295 1296 @Override 1297 Collection<Path> getPaths() { 1298 throw new UnsupportedOperationException(); 1299 } 1300 1301 @Override 1302 void setPaths(Iterable<? extends Path> files) throws IOException { 1303 throw new UnsupportedOperationException(); 1304 } 1305 1306 @Override 1307 Location getModuleLocation(String name) { 1308 return (moduleLocations == null) ? null : moduleLocations.get(name); 1309 } 1310 1311 @Override 1312 Location getModuleLocation(Path dir) { 1313 return (pathLocations == null) ? null : pathLocations.get(dir); 1314 } 1315 1316 @Override 1317 Iterable<Set<Location>> listModuleLocations() { 1318 if (moduleLocations == null) 1319 return Collections.emptySet(); 1320 Set<Location> locns = new LinkedHashSet<>(); 1321 moduleLocations.forEach((k, v) -> locns.add(v)); 1322 return Collections.singleton(locns); 1323 } 1324 1325 } 1326 1327 private class SystemModulesLocationHandler extends BasicLocationHandler { 1328 private Path systemJavaHome; 1329 private Path modules; 1330 private Map<String, ModuleLocationHandler> systemModules; 1331 1332 SystemModulesLocationHandler() { 1333 super(StandardLocation.SYSTEM_MODULES, Option.SYSTEM); 1334 systemJavaHome = Locations.javaHome; 1335 } 1336 1337 @Override 1338 boolean handleOption(Option option, String value) { 1339 if (!options.contains(option)) { 1340 return false; 1341 } 1342 1343 if (value == null) { 1344 systemJavaHome = Locations.javaHome; 1345 } else if (value.equals("none")) { 1346 systemJavaHome = null; 1347 } else { 1348 update(getPath(value)); 1349 } 1350 1351 modules = null; 1352 return true; 1353 } 1354 1355 @Override 1356 Collection<Path> getPaths() { 1357 return (systemJavaHome == null) ? null : Collections.singleton(systemJavaHome); 1358 } 1359 1360 @Override 1361 void setPaths(Iterable<? extends Path> files) throws IOException { 1362 if (files == null) { 1363 systemJavaHome = null; 1364 } else { 1365 Iterator<? extends Path> pathIter = files.iterator(); 1366 if (!pathIter.hasNext()) { 1367 throw new IllegalArgumentException("empty path for directory"); // TODO: FIXME 1368 } 1369 Path dir = pathIter.next(); 1370 if (pathIter.hasNext()) { 1371 throw new IllegalArgumentException("path too long for directory"); // TODO: FIXME 1372 } 1373 if (!Files.exists(dir)) { 1374 throw new FileNotFoundException(dir + ": does not exist"); 1375 } else if (!Files.isDirectory(dir)) { 1376 throw new IOException(dir + ": not a directory"); 1377 } 1378 update(dir); 1379 } 1380 } 1381 1382 private void update(Path p) { 1383 if (!isCurrentPlatform(p) && !Files.exists(p.resolve("jrt-fs.jar")) && !Files.exists(systemJavaHome.resolve("modules"))) 1384 throw new IllegalArgumentException(p.toString()); 1385 systemJavaHome = p; 1386 modules = null; 1387 } 1388 1389 private boolean isCurrentPlatform(Path p) { 1390 try { 1391 return Files.isSameFile(p, Locations.javaHome); 1392 } catch (IOException ex) { 1393 throw new IllegalArgumentException(p.toString(), ex); 1394 } 1395 } 1396 1397 @Override 1398 Location getModuleLocation(String name) throws IOException { 1399 initSystemModules(); 1400 return systemModules.get(name); 1401 } 1402 1403 @Override 1404 Iterable<Set<Location>> listModuleLocations() throws IOException { 1405 initSystemModules(); 1406 Set<Location> locns = new LinkedHashSet<>(); 1407 for (Location l: systemModules.values()) 1408 locns.add(l); 1409 return Collections.singleton(locns); 1410 } 1411 1412 private void initSystemModules() throws IOException { 1413 if (systemModules != null) { 1414 return; 1415 } 1416 1417 if (systemJavaHome == null) { 1418 systemModules = Collections.emptyMap(); 1419 return; 1420 } 1421 1422 if (modules == null) { 1423 try { 1424 URI jrtURI = URI.create("jrt:/"); 1425 FileSystem jrtfs; 1426 1427 if (isCurrentPlatform(systemJavaHome)) { 1428 jrtfs = FileSystems.getFileSystem(jrtURI); 1429 } else { 1430 try { 1431 Map<String, String> attrMap = 1432 Collections.singletonMap("java.home", systemJavaHome.toString()); 1433 jrtfs = FileSystems.newFileSystem(jrtURI, attrMap); 1434 } catch (ProviderNotFoundException ex) { 1435 URL javaHomeURL = systemJavaHome.resolve("jrt-fs.jar").toUri().toURL(); 1436 ClassLoader currentLoader = Locations.class.getClassLoader(); 1437 URLClassLoader fsLoader = 1438 new URLClassLoader(new URL[] {javaHomeURL}, currentLoader); 1439 1440 jrtfs = FileSystems.newFileSystem(jrtURI, Collections.emptyMap(), fsLoader); 1441 1442 closeables.add(fsLoader); 1443 } 1444 1445 closeables.add(jrtfs); 1446 } 1447 1448 modules = jrtfs.getPath("/modules"); 1449 } catch (FileSystemNotFoundException | ProviderNotFoundException e) { 1450 modules = systemJavaHome.resolve("modules"); 1451 if (!Files.exists(modules)) 1452 throw new IOException("can't find system classes", e); 1453 } 1454 } 1455 1456 systemModules = new LinkedHashMap<>(); 1457 try (DirectoryStream<Path> stream = Files.newDirectoryStream(modules, Files::isDirectory)) { 1458 for (Path entry : stream) { 1459 String moduleName = entry.getFileName().toString(); 1460 String name = location.getName() + "[" + moduleName + "]"; 1461 ModuleLocationHandler h = new ModuleLocationHandler(name, moduleName, 1462 Collections.singleton(entry), false, true); 1463 systemModules.put(moduleName, h); 1464 } 1465 } 1466 } 1467 } 1468 1469 Map<Location, LocationHandler> handlersForLocation; 1470 Map<Option, LocationHandler> handlersForOption; 1471 1472 void initHandlers() { 1473 handlersForLocation = new HashMap<>(); 1474 handlersForOption = new EnumMap<>(Option.class); 1475 1476 BasicLocationHandler[] handlers = { 1477 new BootClassPathLocationHandler(), 1478 new ClassPathLocationHandler(), 1479 new SimpleLocationHandler(StandardLocation.SOURCE_PATH, Option.SOURCE_PATH), 1480 new SimpleLocationHandler(StandardLocation.ANNOTATION_PROCESSOR_PATH, Option.PROCESSOR_PATH), 1481 new SimpleLocationHandler(StandardLocation.ANNOTATION_PROCESSOR_MODULE_PATH, Option.PROCESSOR_MODULE_PATH), 1482 new OutputLocationHandler(StandardLocation.CLASS_OUTPUT, Option.D), 1483 new OutputLocationHandler(StandardLocation.SOURCE_OUTPUT, Option.S), 1484 new OutputLocationHandler(StandardLocation.NATIVE_HEADER_OUTPUT, Option.H), 1485 new ModuleSourcePathLocationHandler(), 1486 // TODO: should UPGRADE_MODULE_PATH be merged with SYSTEM_MODULES? 1487 new ModulePathLocationHandler(StandardLocation.UPGRADE_MODULE_PATH, Option.UPGRADE_MODULE_PATH), 1488 new ModulePathLocationHandler(StandardLocation.MODULE_PATH, Option.MODULE_PATH), 1489 new SystemModulesLocationHandler(), 1490 }; 1491 1492 for (BasicLocationHandler h : handlers) { 1493 handlersForLocation.put(h.location, h); 1494 for (Option o : h.options) { 1495 handlersForOption.put(o, h); 1496 } 1497 } 1498 } 1499 1500 private Map<String, SearchPath> patchMap; 1501 1502 boolean handleOption(Option option, String value) { 1503 switch (option) { 1504 case PATCH_MODULE: 1505 if (value == null) { 1506 patchMap = null; 1507 } else { 1508 // Allow an extended syntax for --patch-module consisting of a series 1509 // of values separated by NULL characters. This is to facilitate 1510 // supporting deferred file manager options on the command line. 1511 // See Option.PATCH_MODULE for the code that composes these multiple 1512 // values. 1513 for (String v : value.split("\0")) { 1514 int eq = v.indexOf('='); 1515 if (eq > 0) { 1516 String mName = v.substring(0, eq); 1517 SearchPath mPatchPath = new SearchPath() 1518 .addFiles(v.substring(eq + 1)); 1519 boolean ok = true; 1520 for (Path p : mPatchPath) { 1521 Path mi = p.resolve("module-info.class"); 1522 if (Files.exists(mi)) { 1523 log.error(Errors.LocnModuleInfoNotAllowedOnPatchPath(mi)); 1524 ok = false; 1525 } 1526 } 1527 if (ok) { 1528 if (patchMap == null) { 1529 patchMap = new LinkedHashMap<>(); 1530 } 1531 patchMap.put(mName, mPatchPath); 1532 } 1533 } else { 1534 // Should not be able to get here; 1535 // this should be caught and handled in Option.PATCH_MODULE 1536 log.error(Errors.LocnInvalidArgForXpatch(value)); 1537 } 1538 } 1539 } 1540 return true; 1541 default: 1542 LocationHandler h = handlersForOption.get(option); 1543 return (h == null ? false : h.handleOption(option, value)); 1544 } 1545 } 1546 1547 boolean hasLocation(Location location) { 1548 LocationHandler h = getHandler(location); 1549 return (h == null ? false : h.isSet()); 1550 } 1551 1552 Collection<Path> getLocation(Location location) { 1553 LocationHandler h = getHandler(location); 1554 return (h == null ? null : h.getPaths()); 1555 } 1556 1557 Path getOutputLocation(Location location) { 1558 if (!location.isOutputLocation()) { 1559 throw new IllegalArgumentException(); 1560 } 1561 LocationHandler h = getHandler(location); 1562 return ((OutputLocationHandler) h).outputDir; 1563 } 1564 1565 void setLocation(Location location, Iterable<? extends Path> files) throws IOException { 1566 LocationHandler h = getHandler(location); 1567 if (h == null) { 1568 if (location.isOutputLocation()) { 1569 h = new OutputLocationHandler(location); 1570 } else { 1571 h = new SimpleLocationHandler(location); 1572 } 1573 handlersForLocation.put(location, h); 1574 } 1575 h.setPaths(files); 1576 } 1577 1578 Location getModuleLocation(Location location, String name) throws IOException { 1579 LocationHandler h = getHandler(location); 1580 return (h == null ? null : h.getModuleLocation(name)); 1581 } 1582 1583 Location getModuleLocation(Location location, Path dir) { 1584 LocationHandler h = getHandler(location); 1585 return (h == null ? null : h.getModuleLocation(dir)); 1586 } 1587 1588 String inferModuleName(Location location) { 1589 LocationHandler h = getHandler(location); 1590 return (h == null ? null : h.inferModuleName()); 1591 } 1592 1593 Iterable<Set<Location>> listModuleLocations(Location location) throws IOException { 1594 LocationHandler h = getHandler(location); 1595 return (h == null ? null : h.listModuleLocations()); 1596 } 1597 1598 protected LocationHandler getHandler(Location location) { 1599 Objects.requireNonNull(location); 1600 return (location instanceof LocationHandler) 1601 ? (LocationHandler) location 1602 : handlersForLocation.get(location); 1603 } 1604 1605 /** 1606 * Is this the name of an archive file? 1607 */ 1608 private boolean isArchive(Path file) { 1609 String n = StringUtils.toLowerCase(file.getFileName().toString()); 1610 return fsInfo.isFile(file) 1611 && (n.endsWith(".jar") || n.endsWith(".zip")); 1612 } 1613 1614 static Path normalize(Path p) { 1615 try { 1616 return p.toRealPath(); 1617 } catch (IOException e) { 1618 return p.toAbsolutePath().normalize(); 1619 } 1620 } 1621 1622 }