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