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.BOOTCLASSPATH; 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, 584 Option.CLASSPATH, Option.CP); 585 } 586 587 @Override 588 Collection<Path> getPaths() { 589 lazy(); 590 return searchPath; 591 } 592 593 @Override 594 protected SearchPath computePath(String value) { 595 String cp = value; 596 597 // CLASSPATH environment variable when run from `javac'. 598 if (cp == null) { 599 cp = System.getProperty("env.class.path"); 600 } 601 602 // If invoked via a java VM (not the javac launcher), use the 603 // platform class path 604 if (cp == null && System.getProperty("application.home") == null) { 605 cp = System.getProperty("java.class.path"); 606 } 607 608 // Default to current working directory. 609 if (cp == null) { 610 cp = "."; 611 } 612 613 return createPath().addFiles(cp); 614 } 615 616 @Override 617 protected SearchPath createPath() { 618 return new SearchPath() 619 .expandJarClassPaths(true) // Only search user jars for Class-Paths 620 .emptyPathDefault(getPath(".")); // Empty path elt ==> current directory 621 } 622 623 private void lazy() { 624 if (searchPath == null) { 625 setPaths(null); 626 } 627 } 628 } 629 630 /** 631 * Custom subtype of LocationHandler for PLATFORM_CLASS_PATH. 632 * Various options are supported for different components of the 633 * platform class path. 634 * Setting a value with setLocation overrides all existing option values. 635 * Setting any option overrides any value set with setLocation, and 636 * reverts to using default values for options that have not been set. 637 * Setting -bootclasspath or -Xbootclasspath overrides any existing 638 * value for -Xbootclasspath/p: and -Xbootclasspath/a:. 639 */ 640 private class BootClassPathLocationHandler extends BasicLocationHandler { 641 642 private Collection<Path> searchPath; 643 final Map<Option, String> optionValues = new EnumMap<>(Option.class); 644 645 /** 646 * Is the bootclasspath the default? 647 */ 648 private boolean isDefault; 649 650 BootClassPathLocationHandler() { 651 super(StandardLocation.PLATFORM_CLASS_PATH, 652 Option.BOOTCLASSPATH, Option.XBOOTCLASSPATH, 653 Option.XBOOTCLASSPATH_PREPEND, 654 Option.XBOOTCLASSPATH_APPEND, 655 Option.ENDORSEDDIRS, Option.DJAVA_ENDORSED_DIRS, 656 Option.EXTDIRS, Option.DJAVA_EXT_DIRS); 657 } 658 659 boolean isDefault() { 660 lazy(); 661 return isDefault; 662 } 663 664 @Override 665 boolean handleOption(Option option, String value) { 666 if (!options.contains(option)) { 667 return false; 668 } 669 670 option = canonicalize(option); 671 optionValues.put(option, value); 672 if (option == BOOTCLASSPATH) { 673 optionValues.remove(XBOOTCLASSPATH_PREPEND); 674 optionValues.remove(XBOOTCLASSPATH_APPEND); 675 } 676 searchPath = null; // reset to "uninitialized" 677 return true; 678 } 679 // where 680 // TODO: would be better if option aliasing was handled at a higher 681 // level 682 private Option canonicalize(Option option) { 683 switch (option) { 684 case XBOOTCLASSPATH: 685 return Option.BOOTCLASSPATH; 686 case DJAVA_ENDORSED_DIRS: 687 return Option.ENDORSEDDIRS; 688 case DJAVA_EXT_DIRS: 689 return Option.EXTDIRS; 690 default: 691 return option; 692 } 693 } 694 695 @Override 696 Collection<Path> getPaths() { 697 lazy(); 698 return searchPath; 699 } 700 701 @Override 702 void setPaths(Iterable<? extends Path> files) { 703 if (files == null) { 704 searchPath = null; // reset to "uninitialized" 705 } else { 706 isDefault = false; 707 SearchPath p = new SearchPath().addFiles(files, false); 708 searchPath = Collections.unmodifiableCollection(p); 709 optionValues.clear(); 710 } 711 } 712 713 SearchPath computePath() throws IOException { 714 SearchPath path = new SearchPath(); 715 716 String bootclasspathOpt = optionValues.get(BOOTCLASSPATH); 717 String endorseddirsOpt = optionValues.get(ENDORSEDDIRS); 718 String extdirsOpt = optionValues.get(EXTDIRS); 719 String xbootclasspathPrependOpt = optionValues.get(XBOOTCLASSPATH_PREPEND); 720 String xbootclasspathAppendOpt = optionValues.get(XBOOTCLASSPATH_APPEND); 721 path.addFiles(xbootclasspathPrependOpt); 722 723 if (endorseddirsOpt != null) { 724 path.addDirectories(endorseddirsOpt); 725 } else { 726 path.addDirectories(System.getProperty("java.endorsed.dirs"), false); 727 } 728 729 if (bootclasspathOpt != null) { 730 path.addFiles(bootclasspathOpt); 731 } else { 732 // Standard system classes for this compiler's release. 733 Collection<Path> systemClasses = systemClasses(); 734 if (systemClasses != null) { 735 path.addFiles(systemClasses, false); 736 } else { 737 // fallback to the value of sun.boot.class.path 738 String files = System.getProperty("sun.boot.class.path"); 739 path.addFiles(files, false); 740 } 741 } 742 743 path.addFiles(xbootclasspathAppendOpt); 744 745 // Strictly speaking, standard extensions are not bootstrap 746 // classes, but we treat them identically, so we'll pretend 747 // that they are. 748 if (extdirsOpt != null) { 749 path.addDirectories(extdirsOpt); 750 } else { 751 // Add lib/jfxrt.jar to the search path 752 Path jfxrt = javaHome.resolve("lib/jfxrt.jar"); 753 if (Files.exists(jfxrt)) { 754 path.addFile(jfxrt, false); 755 } 756 path.addDirectories(System.getProperty("java.ext.dirs"), false); 757 } 758 759 isDefault = 760 (xbootclasspathPrependOpt == null) 761 && (bootclasspathOpt == null) 762 && (xbootclasspathAppendOpt == null); 763 764 return path; 765 } 766 767 /** 768 * Return a collection of files containing system classes. 769 * Returns {@code null} if not running on a modular image. 770 * 771 * @throws UncheckedIOException if an I/O errors occurs 772 */ 773 private Collection<Path> systemClasses() throws IOException { 774 // Return "modules" jimage file if available 775 if (Files.isRegularFile(thisSystemModules)) { 776 return addAdditionalBootEntries(Collections.singleton(thisSystemModules)); 777 } 778 779 // Exploded module image 780 Path modules = javaHome.resolve("modules"); 781 if (Files.isDirectory(modules.resolve("java.base"))) { 782 try (Stream<Path> listedModules = Files.list(modules)) { 783 return addAdditionalBootEntries(listedModules.collect(Collectors.toList())); 784 } 785 } 786 787 // not a modular image that we know about 788 return null; 789 } 790 791 //ensure bootclasspath prepends/appends are reflected in the systemClasses 792 private Collection<Path> addAdditionalBootEntries(Collection<Path> modules) throws IOException { 793 String files = System.getProperty("sun.boot.class.path"); 794 if (files == null) 795 return modules; 796 797 Set<Path> paths = new LinkedHashSet<>(); 798 799 // The JVM no longer supports -Xbootclasspath/p:, so any interesting 800 // entries should be appended to the set of modules. 801 802 paths.addAll(modules); 803 804 for (String s : files.split(Pattern.quote(File.pathSeparator))) { 805 paths.add(getPath(s)); 806 } 807 808 return paths; 809 } 810 811 private void lazy() { 812 if (searchPath == null) { 813 try { 814 searchPath = Collections.unmodifiableCollection(computePath()); 815 } catch (IOException e) { 816 // TODO: need better handling here, e.g. javac Abort? 817 throw new UncheckedIOException(e); 818 } 819 } 820 } 821 } 822 823 /** 824 * A LocationHander to represent modules found from a module-oriented 825 * location such as MODULE_SOURCE_PATH, UPGRADE_MODULE_PATH, 826 * SYSTEM_MODULES and MODULE_PATH. 827 * 828 * The Location can be specified to accept overriding classes from the 829 * {@code -Xpatch:<module>=<path> } parameter. 830 */ 831 private class ModuleLocationHandler extends LocationHandler implements Location { 832 protected final String name; 833 protected final String moduleName; 834 protected final Collection<Path> searchPath; 835 protected final Collection<Path> searchPathWithOverrides; 836 protected final boolean output; 837 838 ModuleLocationHandler(String name, String moduleName, Collection<Path> searchPath, 839 boolean output, boolean allowOverrides) { 840 this.name = name; 841 this.moduleName = moduleName; 842 this.searchPath = searchPath; 843 this.output = output; 844 845 if (allowOverrides && patchMap != null) { 846 SearchPath mPatch = patchMap.get(moduleName); 847 if (mPatch != null) { 848 SearchPath sp = new SearchPath(); 849 sp.addAll(mPatch); 850 sp.addAll(searchPath); 851 searchPathWithOverrides = sp; 852 } else { 853 searchPathWithOverrides = searchPath; 854 } 855 } else { 856 searchPathWithOverrides = searchPath; 857 } 858 } 859 860 @Override @DefinedBy(Api.COMPILER) 861 public String getName() { 862 return name; 863 } 864 865 @Override @DefinedBy(Api.COMPILER) 866 public boolean isOutputLocation() { 867 return output; 868 } 869 870 @Override // defined by LocationHandler 871 boolean handleOption(Option option, String value) { 872 throw new UnsupportedOperationException(); 873 } 874 875 @Override // defined by LocationHandler 876 Collection<Path> getPaths() { 877 // For now, we always return searchPathWithOverrides. This may differ from the 878 // JVM behavior if there is a module-info.class to be found in the overriding 879 // classes. 880 return searchPathWithOverrides; 881 } 882 883 @Override // defined by LocationHandler 884 void setPaths(Iterable<? extends Path> files) throws IOException { 885 throw new UnsupportedOperationException(); 886 } 887 888 @Override // defined by LocationHandler 889 String inferModuleName() { 890 return moduleName; 891 } 892 } 893 894 /** 895 * A LocationHandler for simple module-oriented search paths, 896 * like UPGRADE_MODULE_PATH and MODULE_PATH. 897 */ 898 private class ModulePathLocationHandler extends SimpleLocationHandler { 899 ModulePathLocationHandler(Location location, Option... options) { 900 super(location, options); 901 } 902 903 @Override 904 public boolean handleOption(Option option, String value) { 905 if (!options.contains(option)) { 906 return false; 907 } 908 setPaths(value == null ? null : getPathEntries(value)); 909 return true; 910 } 911 912 @Override 913 Iterable<Set<Location>> listModuleLocations() { 914 if (searchPath == null) 915 return Collections.emptyList(); 916 917 return () -> new ModulePathIterator(); 918 } 919 920 @Override 921 void setPaths(Iterable<? extends Path> paths) { 922 if (paths != null) { 923 for (Path p: paths) { 924 checkValidModulePathEntry(p); 925 } 926 } 927 super.setPaths(paths); 928 } 929 930 private void checkValidModulePathEntry(Path p) { 931 if (Files.isDirectory(p)) { 932 // either an exploded module or a directory of modules 933 return; 934 } 935 936 String name = p.getFileName().toString(); 937 int lastDot = name.lastIndexOf("."); 938 if (lastDot > 0) { 939 switch (name.substring(lastDot)) { 940 case ".jar": 941 case ".jmod": 942 return; 943 } 944 } 945 throw new IllegalArgumentException(p.toString()); 946 } 947 948 class ModulePathIterator implements Iterator<Set<Location>> { 949 Iterator<Path> pathIter = searchPath.iterator(); 950 int pathIndex = 0; 951 Set<Location> next = null; 952 953 @Override 954 public boolean hasNext() { 955 if (next != null) 956 return true; 957 958 while (next == null) { 959 if (pathIter.hasNext()) { 960 Path path = pathIter.next(); 961 if (Files.isDirectory(path)) { 962 next = scanDirectory(path); 963 } else { 964 next = scanFile(path); 965 } 966 pathIndex++; 967 } else 968 return false; 969 } 970 return true; 971 } 972 973 @Override 974 public Set<Location> next() { 975 hasNext(); 976 if (next != null) { 977 Set<Location> result = next; 978 next = null; 979 return result; 980 } 981 throw new NoSuchElementException(); 982 } 983 984 private Set<Location> scanDirectory(Path path) { 985 Set<Path> paths = new LinkedHashSet<>(); 986 Path moduleInfoClass = null; 987 try (DirectoryStream<Path> stream = Files.newDirectoryStream(path)) { 988 for (Path entry: stream) { 989 if (entry.endsWith("module-info.class")) { 990 moduleInfoClass = entry; 991 break; // no need to continue scanning 992 } 993 paths.add(entry); 994 } 995 } catch (DirectoryIteratorException | IOException ignore) { 996 log.error(Errors.LocnCantReadDirectory(path)); 997 return Collections.emptySet(); 998 } 999 1000 if (moduleInfoClass != null) { 1001 // It's an exploded module directly on the module path. 1002 // We can't infer module name from the directory name, so have to 1003 // read module-info.class. 1004 try { 1005 String moduleName = readModuleName(moduleInfoClass); 1006 String name = location.getName() 1007 + "[" + pathIndex + ":" + moduleName + "]"; 1008 ModuleLocationHandler l = new ModuleLocationHandler(name, moduleName, 1009 Collections.singleton(path), false, true); 1010 return Collections.singleton(l); 1011 } catch (ModuleNameReader.BadClassFile e) { 1012 log.error(Errors.LocnBadModuleInfo(path)); 1013 return Collections.emptySet(); 1014 } catch (IOException e) { 1015 log.error(Errors.LocnCantReadFile(path)); 1016 return Collections.emptySet(); 1017 } 1018 } 1019 1020 // A directory of modules 1021 Set<Location> result = new LinkedHashSet<>(); 1022 int index = 0; 1023 for (Path entry : paths) { 1024 Pair<String,Path> module = inferModuleName(entry); 1025 if (module == null) { 1026 // diagnostic reported if necessary; skip to next 1027 continue; 1028 } 1029 String moduleName = module.fst; 1030 Path modulePath = module.snd; 1031 String name = location.getName() 1032 + "[" + pathIndex + "." + (index++) + ":" + moduleName + "]"; 1033 ModuleLocationHandler l = new ModuleLocationHandler(name, moduleName, 1034 Collections.singleton(modulePath), false, true); 1035 result.add(l); 1036 } 1037 return result; 1038 } 1039 1040 private Set<Location> scanFile(Path path) { 1041 Pair<String,Path> module = inferModuleName(path); 1042 if (module == null) { 1043 // diagnostic reported if necessary 1044 return Collections.emptySet(); 1045 } 1046 String moduleName = module.fst; 1047 Path modulePath = module.snd; 1048 String name = location.getName() 1049 + "[" + pathIndex + ":" + moduleName + "]"; 1050 ModuleLocationHandler l = new ModuleLocationHandler(name, moduleName, 1051 Collections.singleton(modulePath), false, true); 1052 return Collections.singleton(l); 1053 } 1054 1055 private Pair<String,Path> inferModuleName(Path p) { 1056 if (Files.isDirectory(p)) { 1057 if (Files.exists(p.resolve("module-info.class"))) { 1058 String name = p.getFileName().toString(); 1059 if (SourceVersion.isName(name)) 1060 return new Pair<>(name, p); 1061 } 1062 return null; 1063 } 1064 1065 if (p.getFileName().toString().endsWith(".jar") && fsInfo.exists(p)) { 1066 try (FileSystem fs = FileSystems.newFileSystem(p, null)) { 1067 Path moduleInfoClass = fs.getPath("module-info.class"); 1068 if (Files.exists(moduleInfoClass)) { 1069 String moduleName = readModuleName(moduleInfoClass); 1070 return new Pair<>(moduleName, p); 1071 } 1072 } catch (ModuleNameReader.BadClassFile e) { 1073 log.error(Errors.LocnBadModuleInfo(p)); 1074 return null; 1075 } catch (IOException e) { 1076 log.error(Errors.LocnCantReadFile(p)); 1077 return null; 1078 } 1079 1080 //automatic module: 1081 String fn = p.getFileName().toString(); 1082 //from ModulePath.deriveModuleDescriptor: 1083 1084 // drop .jar 1085 String mn = fn.substring(0, fn.length()-4); 1086 1087 // find first occurrence of -${NUMBER}. or -${NUMBER}$ 1088 Matcher matcher = Pattern.compile("-(\\d+(\\.|$))").matcher(mn); 1089 if (matcher.find()) { 1090 int start = matcher.start(); 1091 1092 mn = mn.substring(0, start); 1093 } 1094 1095 // finally clean up the module name 1096 mn = mn.replaceAll("[^A-Za-z0-9]", ".") // replace non-alphanumeric 1097 .replaceAll("(\\.)(\\1)+", ".") // collapse repeating dots 1098 .replaceAll("^\\.", "") // drop leading dots 1099 .replaceAll("\\.$", ""); // drop trailing dots 1100 1101 1102 if (!mn.isEmpty()) { 1103 return new Pair<>(mn, p); 1104 } 1105 1106 log.error(Errors.LocnCantGetModuleNameForJar(p)); 1107 return null; 1108 } 1109 1110 if (p.getFileName().toString().endsWith(".jmod")) { 1111 try { 1112 FileSystem fs = fileSystems.get(p); 1113 if (fs == null) { 1114 URI uri = URI.create("jar:" + p.toUri()); 1115 fs = FileSystems.newFileSystem(uri, Collections.emptyMap(), null); 1116 try { 1117 Path moduleInfoClass = fs.getPath("classes/module-info.class"); 1118 String moduleName = readModuleName(moduleInfoClass); 1119 Path modulePath = fs.getPath("classes"); 1120 fileSystems.put(p, fs); 1121 closeables.add(fs); 1122 fs = null; // prevent fs being closed in the finally clause 1123 return new Pair<>(moduleName, modulePath); 1124 } finally { 1125 if (fs != null) 1126 fs.close(); 1127 } 1128 } 1129 } catch (ProviderNotFoundException e) { 1130 // will be thrown if the file is not a valid zip file 1131 log.error(Errors.LocnCantReadFile(p)); 1132 return null; 1133 } catch (ModuleNameReader.BadClassFile e) { 1134 log.error(Errors.LocnBadModuleInfo(p)); 1135 } catch (IOException e) { 1136 log.error(Errors.LocnCantReadFile(p)); 1137 return null; 1138 } 1139 } 1140 1141 if (warn && false) { // temp disable, when enabled, massage examples.not-yet.txt suitably. 1142 log.warning(Warnings.LocnUnknownFileOnModulePath(p)); 1143 } 1144 return null; 1145 } 1146 1147 private String readModuleName(Path path) throws IOException, ModuleNameReader.BadClassFile { 1148 if (moduleNameReader == null) 1149 moduleNameReader = new ModuleNameReader(); 1150 return moduleNameReader.readModuleName(path); 1151 } 1152 } 1153 1154 } 1155 1156 private class ModuleSourcePathLocationHandler extends BasicLocationHandler { 1157 1158 private Map<String, Location> moduleLocations; 1159 private Map<Path, Location> pathLocations; 1160 1161 1162 ModuleSourcePathLocationHandler() { 1163 super(StandardLocation.MODULE_SOURCE_PATH, 1164 Option.MODULESOURCEPATH); 1165 } 1166 1167 @Override 1168 boolean handleOption(Option option, String value) { 1169 init(value); 1170 return true; 1171 } 1172 1173 void init(String value) { 1174 Collection<String> segments = new ArrayList<>(); 1175 for (String s: value.split(File.pathSeparator)) { 1176 expandBraces(s, segments); 1177 } 1178 1179 Map<String, Collection<Path>> map = new LinkedHashMap<>(); 1180 final String MARKER = "*"; 1181 for (String seg: segments) { 1182 int markStart = seg.indexOf(MARKER); 1183 if (markStart == -1) { 1184 add(map, getPath(seg), null); 1185 } else { 1186 if (markStart == 0 || !isSeparator(seg.charAt(markStart - 1))) { 1187 throw new IllegalArgumentException("illegal use of " + MARKER + " in " + seg); 1188 } 1189 Path prefix = getPath(seg.substring(0, markStart - 1)); 1190 Path suffix; 1191 int markEnd = markStart + MARKER.length(); 1192 if (markEnd == seg.length()) { 1193 suffix = null; 1194 } else if (!isSeparator(seg.charAt(markEnd)) 1195 || seg.indexOf(MARKER, markEnd) != -1) { 1196 throw new IllegalArgumentException("illegal use of " + MARKER + " in " + seg); 1197 } else { 1198 suffix = getPath(seg.substring(markEnd + 1)); 1199 } 1200 add(map, prefix, suffix); 1201 } 1202 } 1203 1204 moduleLocations = new LinkedHashMap<>(); 1205 pathLocations = new LinkedHashMap<>(); 1206 map.forEach((k, v) -> { 1207 String name = location.getName() + "[" + k + "]"; 1208 ModuleLocationHandler h = new ModuleLocationHandler(name, k, v, false, false); 1209 moduleLocations.put(k, h); 1210 v.forEach(p -> pathLocations.put(normalize(p), h)); 1211 }); 1212 } 1213 1214 private boolean isSeparator(char ch) { 1215 // allow both separators on Windows 1216 return (ch == File.separatorChar) || (ch == '/'); 1217 } 1218 1219 void add(Map<String, Collection<Path>> map, Path prefix, Path suffix) { 1220 if (!Files.isDirectory(prefix)) { 1221 if (warn) { 1222 String key = Files.exists(prefix) 1223 ? "dir.path.element.not.directory" 1224 : "dir.path.element.not.found"; 1225 log.warning(Lint.LintCategory.PATH, key, prefix); 1226 } 1227 return; 1228 } 1229 try (DirectoryStream<Path> stream = Files.newDirectoryStream(prefix, path -> Files.isDirectory(path))) { 1230 for (Path entry: stream) { 1231 Path path = (suffix == null) ? entry : entry.resolve(suffix); 1232 if (Files.isDirectory(path)) { 1233 String name = entry.getFileName().toString(); 1234 Collection<Path> paths = map.get(name); 1235 if (paths == null) 1236 map.put(name, paths = new ArrayList<>()); 1237 paths.add(path); 1238 } 1239 } 1240 } catch (IOException e) { 1241 // TODO? What to do? 1242 System.err.println(e); 1243 } 1244 } 1245 1246 private void expandBraces(String value, Collection<String> results) { 1247 int depth = 0; 1248 int start = -1; 1249 String prefix = null; 1250 String suffix = null; 1251 for (int i = 0; i < value.length(); i++) { 1252 switch (value.charAt(i)) { 1253 case '{': 1254 depth++; 1255 if (depth == 1) { 1256 prefix = value.substring(0, i); 1257 suffix = value.substring(getMatchingBrace(value, i) + 1); 1258 start = i + 1; 1259 } 1260 break; 1261 1262 case ',': 1263 if (depth == 1) { 1264 String elem = value.substring(start, i); 1265 expandBraces(prefix + elem + suffix, results); 1266 start = i + 1; 1267 } 1268 break; 1269 1270 case '}': 1271 switch (depth) { 1272 case 0: 1273 throw new IllegalArgumentException("mismatched braces"); 1274 1275 case 1: 1276 String elem = value.substring(start, i); 1277 expandBraces(prefix + elem + suffix, results); 1278 return; 1279 1280 default: 1281 depth--; 1282 } 1283 break; 1284 } 1285 } 1286 if (depth > 0) 1287 throw new IllegalArgumentException("mismatched braces"); 1288 results.add(value); 1289 } 1290 1291 int getMatchingBrace(String value, int offset) { 1292 int depth = 1; 1293 for (int i = offset + 1; i < value.length(); i++) { 1294 switch (value.charAt(i)) { 1295 case '{': 1296 depth++; 1297 break; 1298 1299 case '}': 1300 if (--depth == 0) 1301 return i; 1302 break; 1303 } 1304 } 1305 throw new IllegalArgumentException("mismatched braces"); 1306 } 1307 1308 @Override 1309 boolean isSet() { 1310 return (moduleLocations != null); 1311 } 1312 1313 @Override 1314 Collection<Path> getPaths() { 1315 throw new UnsupportedOperationException(); 1316 } 1317 1318 @Override 1319 void setPaths(Iterable<? extends Path> files) throws IOException { 1320 throw new UnsupportedOperationException(); 1321 } 1322 1323 @Override 1324 Location getModuleLocation(String name) { 1325 return (moduleLocations == null) ? null : moduleLocations.get(name); 1326 } 1327 1328 @Override 1329 Location getModuleLocation(Path dir) { 1330 return (pathLocations == null) ? null : pathLocations.get(dir); 1331 } 1332 1333 @Override 1334 Iterable<Set<Location>> listModuleLocations() { 1335 if (moduleLocations == null) 1336 return Collections.emptySet(); 1337 Set<Location> locns = new LinkedHashSet<>(); 1338 moduleLocations.forEach((k, v) -> locns.add(v)); 1339 return Collections.singleton(locns); 1340 } 1341 1342 } 1343 1344 private class SystemModulesLocationHandler extends BasicLocationHandler { 1345 private Path systemJavaHome; 1346 private Path modules; 1347 private Map<String, ModuleLocationHandler> systemModules; 1348 1349 SystemModulesLocationHandler() { 1350 super(StandardLocation.SYSTEM_MODULES, Option.SYSTEM); 1351 systemJavaHome = Locations.javaHome; 1352 } 1353 1354 @Override 1355 boolean handleOption(Option option, String value) { 1356 if (!options.contains(option)) { 1357 return false; 1358 } 1359 1360 if (value == null) { 1361 systemJavaHome = Locations.javaHome; 1362 } else if (value.equals("none")) { 1363 systemJavaHome = null; 1364 } else { 1365 update(getPath(value)); 1366 } 1367 1368 modules = null; 1369 return true; 1370 } 1371 1372 @Override 1373 Collection<Path> getPaths() { 1374 return (systemJavaHome == null) ? null : Collections.singleton(systemJavaHome); 1375 } 1376 1377 @Override 1378 void setPaths(Iterable<? extends Path> files) throws IOException { 1379 if (files == null) { 1380 systemJavaHome = null; 1381 } else { 1382 Iterator<? extends Path> pathIter = files.iterator(); 1383 if (!pathIter.hasNext()) { 1384 throw new IllegalArgumentException("empty path for directory"); // TODO: FIXME 1385 } 1386 Path dir = pathIter.next(); 1387 if (pathIter.hasNext()) { 1388 throw new IllegalArgumentException("path too long for directory"); // TODO: FIXME 1389 } 1390 if (!Files.exists(dir)) { 1391 throw new FileNotFoundException(dir + ": does not exist"); 1392 } else if (!Files.isDirectory(dir)) { 1393 throw new IOException(dir + ": not a directory"); 1394 } 1395 update(dir); 1396 } 1397 } 1398 1399 private void update(Path p) { 1400 if (!isCurrentPlatform(p) && !Files.exists(p.resolve("jrt-fs.jar")) && !Files.exists(systemJavaHome.resolve("modules"))) 1401 throw new IllegalArgumentException(p.toString()); 1402 systemJavaHome = p; 1403 modules = null; 1404 } 1405 1406 private boolean isCurrentPlatform(Path p) { 1407 try { 1408 return Files.isSameFile(p, Locations.javaHome); 1409 } catch (IOException ex) { 1410 throw new IllegalArgumentException(p.toString(), ex); 1411 } 1412 } 1413 1414 @Override 1415 Location getModuleLocation(String name) throws IOException { 1416 initSystemModules(); 1417 return systemModules.get(name); 1418 } 1419 1420 @Override 1421 Iterable<Set<Location>> listModuleLocations() throws IOException { 1422 initSystemModules(); 1423 Set<Location> locns = new LinkedHashSet<>(); 1424 for (Location l: systemModules.values()) 1425 locns.add(l); 1426 return Collections.singleton(locns); 1427 } 1428 1429 private void initSystemModules() throws IOException { 1430 if (systemModules != null) { 1431 return; 1432 } 1433 1434 if (systemJavaHome == null) { 1435 systemModules = Collections.emptyMap(); 1436 return; 1437 } 1438 1439 if (modules == null) { 1440 try { 1441 URI jrtURI = URI.create("jrt:/"); 1442 FileSystem jrtfs; 1443 1444 if (isCurrentPlatform(systemJavaHome)) { 1445 jrtfs = FileSystems.getFileSystem(jrtURI); 1446 } else { 1447 try { 1448 Map<String, String> attrMap = 1449 Collections.singletonMap("java.home", systemJavaHome.toString()); 1450 jrtfs = FileSystems.newFileSystem(jrtURI, attrMap); 1451 } catch (ProviderNotFoundException ex) { 1452 URL javaHomeURL = systemJavaHome.resolve("jrt-fs.jar").toUri().toURL(); 1453 ClassLoader currentLoader = Locations.class.getClassLoader(); 1454 URLClassLoader fsLoader = 1455 new URLClassLoader(new URL[] {javaHomeURL}, currentLoader); 1456 1457 jrtfs = FileSystems.newFileSystem(jrtURI, Collections.emptyMap(), fsLoader); 1458 1459 closeables.add(fsLoader); 1460 } 1461 1462 closeables.add(jrtfs); 1463 } 1464 1465 modules = jrtfs.getPath("/modules"); 1466 } catch (FileSystemNotFoundException | ProviderNotFoundException e) { 1467 modules = systemJavaHome.resolve("modules"); 1468 if (!Files.exists(modules)) 1469 throw new IOException("can't find system classes", e); 1470 } 1471 } 1472 1473 systemModules = new LinkedHashMap<>(); 1474 try (DirectoryStream<Path> stream = Files.newDirectoryStream(modules, Files::isDirectory)) { 1475 for (Path entry : stream) { 1476 String moduleName = entry.getFileName().toString(); 1477 String name = location.getName() + "[" + moduleName + "]"; 1478 ModuleLocationHandler h = new ModuleLocationHandler(name, moduleName, 1479 Collections.singleton(entry), false, true); 1480 systemModules.put(moduleName, h); 1481 } 1482 } 1483 } 1484 } 1485 1486 Map<Location, LocationHandler> handlersForLocation; 1487 Map<Option, LocationHandler> handlersForOption; 1488 1489 void initHandlers() { 1490 handlersForLocation = new HashMap<>(); 1491 handlersForOption = new EnumMap<>(Option.class); 1492 1493 BasicLocationHandler[] handlers = { 1494 new BootClassPathLocationHandler(), 1495 new ClassPathLocationHandler(), 1496 new SimpleLocationHandler(StandardLocation.SOURCE_PATH, Option.SOURCEPATH), 1497 new SimpleLocationHandler(StandardLocation.ANNOTATION_PROCESSOR_PATH, Option.PROCESSORPATH), 1498 new SimpleLocationHandler(StandardLocation.ANNOTATION_PROCESSOR_MODULE_PATH, Option.PROCESSORMODULEPATH), 1499 new OutputLocationHandler(StandardLocation.CLASS_OUTPUT, Option.D), 1500 new OutputLocationHandler(StandardLocation.SOURCE_OUTPUT, Option.S), 1501 new OutputLocationHandler(StandardLocation.NATIVE_HEADER_OUTPUT, Option.H), 1502 new ModuleSourcePathLocationHandler(), 1503 // TODO: should UPGRADE_MODULE_PATH be merged with SYSTEM_MODULES? 1504 new ModulePathLocationHandler(StandardLocation.UPGRADE_MODULE_PATH, Option.UPGRADEMODULEPATH), 1505 new ModulePathLocationHandler(StandardLocation.MODULE_PATH, Option.MODULEPATH, Option.MP), 1506 new SystemModulesLocationHandler(), 1507 }; 1508 1509 for (BasicLocationHandler h : handlers) { 1510 handlersForLocation.put(h.location, h); 1511 for (Option o : h.options) { 1512 handlersForOption.put(o, h); 1513 } 1514 } 1515 } 1516 1517 private Map<String, SearchPath> patchMap; 1518 1519 boolean handleOption(Option option, String value) { 1520 switch (option) { 1521 case XPATCH: 1522 Map<String, SearchPath> map = new LinkedHashMap<>(); 1523 int eq = value.indexOf('='); 1524 if (eq > 0) { 1525 String mName = value.substring(0, eq); 1526 SearchPath mPatchPath = new SearchPath() 1527 .addFiles(value.substring(eq + 1)); 1528 boolean ok = true; 1529 for (Path p: mPatchPath) { 1530 Path mi = p.resolve("module-info.class"); 1531 if (Files.exists(mi)) { 1532 log.error(Errors.LocnModuleInfoNotAllowedOnPatchPath(mi)); 1533 ok = false; 1534 } 1535 } 1536 if (ok && !mPatchPath.isEmpty()) { 1537 map.computeIfAbsent(mName, (_x) -> new SearchPath()) 1538 .addAll(mPatchPath); 1539 } 1540 } else { 1541 log.error(Errors.LocnInvalidArgForXpatch(value)); 1542 } 1543 patchMap = map; 1544 return true; 1545 default: 1546 LocationHandler h = handlersForOption.get(option); 1547 return (h == null ? false : h.handleOption(option, value)); 1548 } 1549 } 1550 1551 boolean hasLocation(Location location) { 1552 LocationHandler h = getHandler(location); 1553 return (h == null ? false : h.isSet()); 1554 } 1555 1556 Collection<Path> getLocation(Location location) { 1557 LocationHandler h = getHandler(location); 1558 return (h == null ? null : h.getPaths()); 1559 } 1560 1561 Path getOutputLocation(Location location) { 1562 if (!location.isOutputLocation()) { 1563 throw new IllegalArgumentException(); 1564 } 1565 LocationHandler h = getHandler(location); 1566 return ((OutputLocationHandler) h).outputDir; 1567 } 1568 1569 void setLocation(Location location, Iterable<? extends Path> files) throws IOException { 1570 LocationHandler h = getHandler(location); 1571 if (h == null) { 1572 if (location.isOutputLocation()) { 1573 h = new OutputLocationHandler(location); 1574 } else { 1575 h = new SimpleLocationHandler(location); 1576 } 1577 handlersForLocation.put(location, h); 1578 } 1579 h.setPaths(files); 1580 } 1581 1582 Location getModuleLocation(Location location, String name) throws IOException { 1583 LocationHandler h = getHandler(location); 1584 return (h == null ? null : h.getModuleLocation(name)); 1585 } 1586 1587 Location getModuleLocation(Location location, Path dir) { 1588 LocationHandler h = getHandler(location); 1589 return (h == null ? null : h.getModuleLocation(dir)); 1590 } 1591 1592 String inferModuleName(Location location) { 1593 LocationHandler h = getHandler(location); 1594 return (h == null ? null : h.inferModuleName()); 1595 } 1596 1597 Iterable<Set<Location>> listModuleLocations(Location location) throws IOException { 1598 LocationHandler h = getHandler(location); 1599 return (h == null ? null : h.listModuleLocations()); 1600 } 1601 1602 protected LocationHandler getHandler(Location location) { 1603 Objects.requireNonNull(location); 1604 return (location instanceof LocationHandler) 1605 ? (LocationHandler) location 1606 : handlersForLocation.get(location); 1607 } 1608 1609 /** 1610 * Is this the name of an archive file? 1611 */ 1612 private boolean isArchive(Path file) { 1613 String n = StringUtils.toLowerCase(file.getFileName().toString()); 1614 return fsInfo.isFile(file) 1615 && (n.endsWith(".jar") || n.endsWith(".zip")); 1616 } 1617 1618 static Path normalize(Path p) { 1619 try { 1620 return p.toRealPath(); 1621 } catch (IOException e) { 1622 return p.toAbsolutePath().normalize(); 1623 } 1624 } 1625 1626 }