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