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