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