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