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