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