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