Locations.java revision 3902:63141c3a65a6
1170159Sariff/* 2170159Sariff * Copyright (c) 2003, 2017, Oracle and/or its affiliates. All rights reserved. 3170159Sariff * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 4170159Sariff * 5170159Sariff * This code is free software; you can redistribute it and/or modify it 6170159Sariff * under the terms of the GNU General Public License version 2 only, as 7170159Sariff * published by the Free Software Foundation. Oracle designates this 8170159Sariff * particular file as subject to the "Classpath" exception as provided 9170159Sariff * by Oracle in the LICENSE file that accompanied this code. 10170159Sariff * 11170159Sariff * This code is distributed in the hope that it will be useful, but WITHOUT 12170159Sariff * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 13170159Sariff * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 14170159Sariff * version 2 for more details (a copy is included in the LICENSE file that 15170159Sariff * accompanied this code). 16170159Sariff * 17170159Sariff * You should have received a copy of the GNU General Public License version 18170159Sariff * 2 along with this work; if not, write to the Free Software Foundation, 19170159Sariff * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 20170159Sariff * 21170159Sariff * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 22170159Sariff * or visit www.oracle.com if you need additional information or have any 23170159Sariff * questions. 24170159Sariff */ 25170159Sariff 26170159Sariffpackage com.sun.tools.javac.file; 27170159Sariff 28170159Sariffimport java.io.Closeable; 29170159Sariffimport java.io.File; 30170159Sariffimport java.io.FileNotFoundException; 31170159Sariffimport java.io.IOException; 32170159Sariffimport java.io.UncheckedIOException; 33170159Sariffimport java.net.URI; 34170159Sariffimport java.net.URL; 35170159Sariffimport java.net.URLClassLoader; 36193640Sariffimport java.nio.file.DirectoryIteratorException; 37193640Sariffimport java.nio.file.DirectoryStream; 38193640Sariffimport java.nio.file.FileSystem; 39193640Sariffimport java.nio.file.FileSystemNotFoundException; 40170159Sariffimport java.nio.file.FileSystems; 41170159Sariffimport java.nio.file.Files; 42170159Sariffimport java.nio.file.InvalidPathException; 43170159Sariffimport java.nio.file.Path; 44170159Sariffimport java.nio.file.Paths; 45170159Sariffimport java.nio.file.ProviderNotFoundException; 46170159Sariffimport java.util.ArrayList; 47170159Sariffimport java.util.Arrays; 48170159Sariffimport java.util.Collection; 49170159Sariffimport java.util.Collections; 50170159Sariffimport java.util.EnumMap; 51170159Sariffimport java.util.EnumSet; 52170159Sariffimport java.util.HashMap; 53170159Sariffimport java.util.HashSet; 54170159Sariffimport java.util.Iterator; 55170159Sariffimport java.util.LinkedHashMap; 56170159Sariffimport java.util.LinkedHashSet; 57170159Sariffimport java.util.List; 58170159Sariffimport java.util.Map; 59170159Sariffimport java.util.Objects; 60170159Sariffimport java.util.NoSuchElementException; 61170159Sariffimport java.util.Set; 62170159Sariffimport java.util.regex.Matcher; 63170159Sariffimport java.util.regex.Pattern; 64170159Sariffimport java.util.stream.Collectors; 65170159Sariffimport java.util.stream.Stream; 66170159Sariff 67170159Sariffimport javax.lang.model.SourceVersion; 68170159Sariffimport javax.tools.JavaFileManager; 69170159Sariffimport javax.tools.JavaFileManager.Location; 70170159Sariffimport javax.tools.JavaFileObject; 71170159Sariffimport javax.tools.StandardJavaFileManager; 72170159Sariffimport javax.tools.StandardJavaFileManager.PathFactory; 73170159Sariffimport javax.tools.StandardLocation; 74170159Sariff 75170159Sariffimport com.sun.tools.javac.code.Lint; 76170159Sariffimport com.sun.tools.javac.code.Lint.LintCategory; 77170159Sariffimport com.sun.tools.javac.main.Option; 78170159Sariffimport com.sun.tools.javac.resources.CompilerProperties.Errors; 79170159Sariffimport com.sun.tools.javac.resources.CompilerProperties.Warnings; 80170159Sariffimport com.sun.tools.javac.util.DefinedBy; 81170159Sariffimport com.sun.tools.javac.util.DefinedBy.Api; 82170159Sariffimport com.sun.tools.javac.util.JDK9Wrappers; 83170159Sariffimport com.sun.tools.javac.util.ListBuffer; 84170159Sariffimport com.sun.tools.javac.util.Log; 85170159Sariffimport com.sun.tools.javac.jvm.ModuleNameReader; 86170159Sariffimport com.sun.tools.javac.util.Pair; 87170159Sariffimport com.sun.tools.javac.util.StringUtils; 88170159Sariff 89170159Sariffimport static javax.tools.StandardLocation.PLATFORM_CLASS_PATH; 90170159Sariff 91170159Sariffimport static com.sun.tools.javac.main.Option.BOOT_CLASS_PATH; 92170159Sariffimport static com.sun.tools.javac.main.Option.DJAVA_ENDORSED_DIRS; 93170159Sariffimport static com.sun.tools.javac.main.Option.DJAVA_EXT_DIRS; 94170159Sariffimport static com.sun.tools.javac.main.Option.ENDORSEDDIRS; 95193640Sariffimport static com.sun.tools.javac.main.Option.EXTDIRS; 96170159Sariffimport static com.sun.tools.javac.main.Option.XBOOTCLASSPATH; 97193640Sariffimport static com.sun.tools.javac.main.Option.XBOOTCLASSPATH_APPEND; 98170159Sariffimport static com.sun.tools.javac.main.Option.XBOOTCLASSPATH_PREPEND; 99170159Sariff 100170159Sariff/** 101170159Sariff * This class converts command line arguments, environment variables and system properties (in 102170159Sariff * File.pathSeparator-separated String form) into a boot class path, user class path, and source 103170159Sariff * path (in {@code Collection<String>} form). 104170159Sariff * 105170159Sariff * <p> 106170159Sariff * <b>This is NOT part of any supported API. If you write code that depends on this, you do so at 107170159Sariff * your own risk. This code and its internal interfaces are subject to change or deletion without 108170159Sariff * notice.</b> 109170159Sariff */ 110170159Sariffpublic class Locations { 111170159Sariff 112170159Sariff /** 113170159Sariff * The log to use for warning output 114170159Sariff */ 115170159Sariff private Log log; 116170159Sariff 117170159Sariff /** 118170159Sariff * Access to (possibly cached) file info 119170159Sariff */ 120170159Sariff private FSInfo fsInfo; 121170159Sariff 122170159Sariff /** 123170159Sariff * Whether to warn about non-existent path elements 124170159Sariff */ 125170159Sariff private boolean warn; 126170159Sariff 127170159Sariff private ModuleNameReader moduleNameReader; 128170159Sariff 129170159Sariff private PathFactory pathFactory = Paths::get; 130170159Sariff 131170159Sariff static final Path javaHome = FileSystems.getDefault().getPath(System.getProperty("java.home")); 132170159Sariff static final Path thisSystemModules = javaHome.resolve("lib").resolve("modules"); 133170159Sariff 134170159Sariff Map<Path, FileSystem> fileSystems = new LinkedHashMap<>(); 135170159Sariff List<Closeable> closeables = new ArrayList<>(); 136170159Sariff private Map<String,String> fsEnv = Collections.emptyMap(); 137170159Sariff 138170159Sariff Locations() { 139170159Sariff initHandlers(); 140170159Sariff } 141170159Sariff 142170159Sariff Path getPath(String first, String... more) { 143170289Sdwmalone try { 144170159Sariff return pathFactory.getPath(first, more); 145170159Sariff } catch (InvalidPathException ipe) { 146170159Sariff throw new IllegalArgumentException(ipe); 147170159Sariff } 148170159Sariff } 149170159Sariff 150170159Sariff public void close() throws IOException { 151170159Sariff ListBuffer<IOException> list = new ListBuffer<>(); 152170159Sariff closeables.forEach(closeable -> { 153170159Sariff try { 154170159Sariff closeable.close(); 155170159Sariff } catch (IOException ex) { 156170159Sariff list.add(ex); 157170159Sariff } 158170159Sariff }); 159170159Sariff if (list.nonEmpty()) { 160170159Sariff IOException ex = new IOException(); 161170159Sariff for (IOException e: list) 162170159Sariff ex.addSuppressed(e); 163170159Sariff throw ex; 164170159Sariff } 165170719Sariff } 166170159Sariff 167170159Sariff void update(Log log, boolean warn, FSInfo fsInfo) { 168170719Sariff this.log = log; 169170159Sariff this.warn = warn; 170170159Sariff this.fsInfo = fsInfo; 171170159Sariff } 172170159Sariff 173170159Sariff void setPathFactory(PathFactory f) { 174170159Sariff pathFactory = f; 175170159Sariff } 176170159Sariff 177170159Sariff boolean isDefaultBootClassPath() { 178170159Sariff BootClassPathLocationHandler h 179170159Sariff = (BootClassPathLocationHandler) getHandler(PLATFORM_CLASS_PATH); 180170159Sariff return h.isDefault(); 181170159Sariff } 182170159Sariff 183170159Sariff /** 184170159Sariff * Split a search path into its elements. Empty path elements will be ignored. 185170159Sariff * 186170159Sariff * @param searchPath The search path to be split 187170159Sariff * @return The elements of the path 188170159Sariff */ 189170159Sariff private Iterable<Path> getPathEntries(String searchPath) { 190170159Sariff return getPathEntries(searchPath, null); 191170159Sariff } 192170159Sariff 193170159Sariff /** 194170159Sariff * Split a search path into its elements. If emptyPathDefault is not null, all empty elements in the 195170159Sariff * path, including empty elements at either end of the path, will be replaced with the value of 196170159Sariff * emptyPathDefault. 197170159Sariff * 198170159Sariff * @param searchPath The search path to be split 199170159Sariff * @param emptyPathDefault The value to substitute for empty path elements, or null, to ignore 200170159Sariff * empty path elements 201170159Sariff * @return The elements of the path 202170159Sariff */ 203170159Sariff private Iterable<Path> getPathEntries(String searchPath, Path emptyPathDefault) { 204170159Sariff ListBuffer<Path> entries = new ListBuffer<>(); 205170159Sariff for (String s: searchPath.split(Pattern.quote(File.pathSeparator), -1)) { 206170159Sariff if (s.isEmpty()) { 207170159Sariff if (emptyPathDefault != null) { 208170159Sariff entries.add(emptyPathDefault); 209170159Sariff } 210170159Sariff } else { 211170159Sariff try { 212170159Sariff entries.add(getPath(s)); 213170159Sariff } catch (IllegalArgumentException e) { 214170159Sariff if (warn) { 215170159Sariff log.warning(LintCategory.PATH, "invalid.path", s); 216170159Sariff } 217170159Sariff } 218170159Sariff } 219170159Sariff } 220170159Sariff return entries; 221170159Sariff } 222170159Sariff 223170159Sariff public void setMultiReleaseValue(String multiReleaseValue) { 224170159Sariff fsEnv = Collections.singletonMap("multi-release", multiReleaseValue); 225170159Sariff } 226170159Sariff 227170159Sariff /** 228170159Sariff * Utility class to help evaluate a path option. Duplicate entries are ignored, jar class paths 229170159Sariff * can be expanded. 230170159Sariff */ 231170159Sariff private class SearchPath extends LinkedHashSet<Path> { 232170159Sariff 233170159Sariff private static final long serialVersionUID = 0; 234170159Sariff 235170159Sariff private boolean expandJarClassPaths = false; 236170159Sariff private final Set<Path> canonicalValues = new HashSet<>(); 237170159Sariff 238170159Sariff public SearchPath expandJarClassPaths(boolean x) { 239170159Sariff expandJarClassPaths = x; 240170159Sariff return this; 241170159Sariff } 242170159Sariff 243170159Sariff /** 244170159Sariff * What to use when path element is the empty string 245170159Sariff */ 246170159Sariff private Path emptyPathDefault = null; 247170159Sariff 248170159Sariff public SearchPath emptyPathDefault(Path x) { 249170159Sariff emptyPathDefault = x; 250170159Sariff return this; 251170159Sariff } 252170159Sariff 253170159Sariff public SearchPath addDirectories(String dirs, boolean warn) { 254170159Sariff boolean prev = expandJarClassPaths; 255170159Sariff expandJarClassPaths = true; 256170159Sariff try { 257170159Sariff if (dirs != null) { 258170159Sariff for (Path dir : getPathEntries(dirs)) { 259170159Sariff addDirectory(dir, warn); 260170159Sariff } 261170159Sariff } 262170159Sariff return this; 263170159Sariff } finally { 264170159Sariff expandJarClassPaths = prev; 265170159Sariff } 266170159Sariff } 267170159Sariff 268170159Sariff public SearchPath addDirectories(String dirs) { 269170159Sariff return addDirectories(dirs, warn); 270170159Sariff } 271170159Sariff 272170159Sariff private void addDirectory(Path dir, boolean warn) { 273170159Sariff if (!Files.isDirectory(dir)) { 274170159Sariff if (warn) { 275170159Sariff log.warning(Lint.LintCategory.PATH, 276170159Sariff "dir.path.element.not.found", dir); 277170159Sariff } 278170159Sariff return; 279170159Sariff } 280170159Sariff 281170159Sariff try (Stream<Path> s = Files.list(dir)) { 282170159Sariff s.filter(Locations.this::isArchive) 283170159Sariff .forEach(dirEntry -> addFile(dirEntry, warn)); 284170159Sariff } catch (IOException ignore) { 285170159Sariff } 286170159Sariff } 287170159Sariff 288170159Sariff public SearchPath addFiles(String files, boolean warn) { 289170159Sariff if (files != null) { 290170159Sariff addFiles(getPathEntries(files, emptyPathDefault), warn); 291170159Sariff } 292170159Sariff return this; 293170159Sariff } 294170159Sariff 295170159Sariff public SearchPath addFiles(String files) { 296170159Sariff return addFiles(files, warn); 297170159Sariff } 298170159Sariff 299170159Sariff public SearchPath addFiles(Iterable<? extends Path> files, boolean warn) { 300170159Sariff if (files != null) { 301170159Sariff for (Path file : files) { 302170159Sariff addFile(file, warn); 303170159Sariff } 304170159Sariff } 305170159Sariff return this; 306170159Sariff } 307170159Sariff 308170159Sariff public SearchPath addFiles(Iterable<? extends Path> files) { 309170159Sariff return addFiles(files, warn); 310170159Sariff } 311170159Sariff 312170159Sariff public void addFile(Path file, boolean warn) { 313170159Sariff if (contains(file)) { 314170159Sariff // discard duplicates 315170159Sariff return; 316170159Sariff } 317170159Sariff 318170159Sariff if (!fsInfo.exists(file)) { 319170159Sariff /* No such file or directory exists */ 320170159Sariff if (warn) { 321170159Sariff log.warning(Lint.LintCategory.PATH, 322170159Sariff "path.element.not.found", file); 323170159Sariff } 324170159Sariff super.add(file); 325170159Sariff return; 326170159Sariff } 327170159Sariff 328170159Sariff Path canonFile = fsInfo.getCanonicalFile(file); 329170159Sariff if (canonicalValues.contains(canonFile)) { 330170159Sariff /* Discard duplicates and avoid infinite recursion */ 331170159Sariff return; 332170159Sariff } 333170159Sariff 334170159Sariff if (fsInfo.isFile(file)) { 335170159Sariff /* File is an ordinary file. */ 336170159Sariff if ( !file.getFileName().toString().endsWith(".jmod") 337170159Sariff && !file.endsWith("modules")) { 338170159Sariff if (!isArchive(file)) { 339170159Sariff /* Not a recognized extension; open it to see if 340170159Sariff it looks like a valid zip file. */ 341170159Sariff try { 342170159Sariff FileSystems.newFileSystem(file, null).close(); 343170159Sariff if (warn) { 344170159Sariff log.warning(Lint.LintCategory.PATH, 345170159Sariff "unexpected.archive.file", file); 346170159Sariff } 347170159Sariff } catch (IOException | ProviderNotFoundException e) { 348170159Sariff // FIXME: include e.getLocalizedMessage in warning 349170159Sariff if (warn) { 350170159Sariff log.warning(Lint.LintCategory.PATH, 351170159Sariff "invalid.archive.file", file); 352170159Sariff } 353170159Sariff return; 354170159Sariff } 355170159Sariff } else { 356170159Sariff if (fsInfo.getJarFSProvider() == null) { 357170159Sariff log.error(Errors.NoZipfsForArchive(file)); 358170159Sariff return ; 359170159Sariff } 360170159Sariff } 361170159Sariff } 362170159Sariff } 363170159Sariff 364170159Sariff /* Now what we have left is either a directory or a file name 365170159Sariff conforming to archive naming convention */ 366170159Sariff super.add(file); 367170159Sariff canonicalValues.add(canonFile); 368170159Sariff 369170159Sariff if (expandJarClassPaths && fsInfo.isFile(file) && !file.endsWith("modules")) { 370170159Sariff addJarClassPath(file, warn); 371170159Sariff } 372170159Sariff } 373170159Sariff 374170159Sariff // Adds referenced classpath elements from a jar's Class-Path 375170159Sariff // Manifest entry. In some future release, we may want to 376170159Sariff // update this code to recognize URLs rather than simple 377170159Sariff // filenames, but if we do, we should redo all path-related code. 378170159Sariff private void addJarClassPath(Path jarFile, boolean warn) { 379170159Sariff try { 380170159Sariff for (Path f : fsInfo.getJarClassPath(jarFile)) { 381170159Sariff addFile(f, warn); 382170159Sariff } 383170159Sariff } catch (IOException e) { 384170159Sariff log.error("error.reading.file", jarFile, JavacFileManager.getMessage(e)); 385170159Sariff } 386170159Sariff } 387170159Sariff } 388170159Sariff 389170159Sariff /** 390170159Sariff * Base class for handling support for the representation of Locations. 391170159Sariff * 392170159Sariff * Locations are (by design) opaque handles that can easily be implemented 393170159Sariff * by enums like StandardLocation. Within JavacFileManager, each Location 394170159Sariff * has an associated LocationHandler, which provides much of the appropriate 395170159Sariff * functionality for the corresponding Location. 396170159Sariff * 397170159Sariff * @see #initHandlers 398170159Sariff * @see #getHandler 399170159Sariff */ 400170159Sariff protected abstract class LocationHandler { 401170159Sariff 402170159Sariff /** 403170159Sariff * @see JavaFileManager#handleOption 404170159Sariff */ 405170159Sariff abstract boolean handleOption(Option option, String value); 406170159Sariff 407170159Sariff /** 408170159Sariff * @see StandardJavaFileManager#hasLocation 409170159Sariff */ 410170159Sariff boolean isSet() { 411170159Sariff return (getPaths() != null); 412170159Sariff } 413170159Sariff 414170159Sariff /** 415170159Sariff * @see StandardJavaFileManager#getLocation 416170159Sariff */ 417170159Sariff abstract Collection<Path> getPaths(); 418170159Sariff 419170159Sariff /** 420170159Sariff * @see StandardJavaFileManager#setLocation 421170159Sariff */ 422170159Sariff abstract void setPaths(Iterable<? extends Path> files) throws IOException; 423170159Sariff 424170159Sariff /** 425170159Sariff * @see JavaFileManager#getLocationForModule(Location, String) 426170159Sariff */ 427170159Sariff Location getLocationForModule(String moduleName) throws IOException { 428170159Sariff return null; 429170159Sariff } 430170159Sariff 431170159Sariff /** 432170159Sariff * @see JavaFileManager#getLocationForModule(Location, JavaFileObject, String) 433170159Sariff */ 434170159Sariff Location getLocationForModule(Path dir) { 435170159Sariff return null; 436170159Sariff } 437170159Sariff 438170159Sariff /** 439170159Sariff * @see JavaFileManager#inferModuleName 440170159Sariff */ 441170159Sariff String inferModuleName() { 442170159Sariff return null; 443170159Sariff } 444170159Sariff 445170159Sariff /** 446170159Sariff * @see JavaFileManager#listLocationsForModules 447170159Sariff */ 448170159Sariff Iterable<Set<Location>> listLocationsForModules() throws IOException { 449170159Sariff return null; 450170159Sariff } 451170159Sariff } 452170159Sariff 453170159Sariff /** 454170719Sariff * A LocationHandler for a given Location, and associated set of options. 455170159Sariff */ 456170159Sariff private abstract class BasicLocationHandler extends LocationHandler { 457170159Sariff 458170719Sariff final Location location; 459170719Sariff final Set<Option> options; 460170719Sariff 461170159Sariff /** 462170159Sariff * Create a handler. The location and options provide a way to map from a location or an 463170159Sariff * option to the corresponding handler. 464170719Sariff * 465170159Sariff * @param location the location for which this is the handler 466170159Sariff * @param options the options affecting this location 467170159Sariff * @see #initHandlers 468170159Sariff */ 469170159Sariff protected BasicLocationHandler(Location location, Option... options) { 470170159Sariff this.location = location; 471170159Sariff this.options = options.length == 0 472170159Sariff ? EnumSet.noneOf(Option.class) 473170159Sariff : EnumSet.copyOf(Arrays.asList(options)); 474170159Sariff } 475170159Sariff } 476170159Sariff 477170159Sariff /** 478170159Sariff * General purpose implementation for output locations, such as -d/CLASS_OUTPUT and 479170159Sariff * -s/SOURCE_OUTPUT. All options are treated as equivalent (i.e. aliases.) 480170159Sariff * The value is a single file, possibly null. 481170159Sariff */ 482170159Sariff private class OutputLocationHandler extends BasicLocationHandler { 483170159Sariff 484170159Sariff private Path outputDir; 485170159Sariff private Map<String, Location> moduleLocations; 486170159Sariff private Map<Path, Location> pathLocations; 487170159Sariff 488170159Sariff OutputLocationHandler(Location location, Option... options) { 489170159Sariff super(location, options); 490170159Sariff } 491170159Sariff 492170159Sariff @Override 493170159Sariff boolean handleOption(Option option, String value) { 494170159Sariff if (!options.contains(option)) { 495170159Sariff return false; 496170159Sariff } 497170159Sariff 498170159Sariff // TODO: could/should validate outputDir exists and is a directory 499170159Sariff // need to decide how best to report issue for benefit of 500170159Sariff // direct API call on JavaFileManager.handleOption(specifies IAE) 501170159Sariff // vs. command line decoding. 502170159Sariff outputDir = (value == null) ? null : getPath(value); 503170159Sariff return true; 504170159Sariff } 505170159Sariff 506170159Sariff @Override 507170159Sariff Collection<Path> getPaths() { 508170159Sariff return (outputDir == null) ? null : Collections.singleton(outputDir); 509170159Sariff } 510170159Sariff 511170159Sariff @Override 512170159Sariff void setPaths(Iterable<? extends Path> files) throws IOException { 513170159Sariff if (files == null) { 514170159Sariff outputDir = null; 515170159Sariff } else { 516170159Sariff Iterator<? extends Path> pathIter = files.iterator(); 517170159Sariff if (!pathIter.hasNext()) { 518170159Sariff throw new IllegalArgumentException("empty path for directory"); 519170159Sariff } 520170159Sariff Path dir = pathIter.next(); 521170159Sariff if (pathIter.hasNext()) { 522170159Sariff throw new IllegalArgumentException("path too long for directory"); 523170159Sariff } 524170159Sariff if (!Files.exists(dir)) { 525170159Sariff throw new FileNotFoundException(dir + ": does not exist"); 526170159Sariff } else if (!Files.isDirectory(dir)) { 527170159Sariff throw new IOException(dir + ": not a directory"); 528170159Sariff } 529170159Sariff outputDir = dir; 530170159Sariff } 531170159Sariff moduleLocations = null; 532170159Sariff pathLocations = null; 533170159Sariff } 534170159Sariff 535170159Sariff @Override 536170159Sariff Location getLocationForModule(String name) { 537170159Sariff if (moduleLocations == null) { 538170159Sariff moduleLocations = new HashMap<>(); 539170159Sariff pathLocations = new HashMap<>(); 540170159Sariff } 541170159Sariff Location l = moduleLocations.get(name); 542170159Sariff if (l == null) { 543170159Sariff Path out = outputDir.resolve(name); 544170159Sariff l = new ModuleLocationHandler(location.getName() + "[" + name + "]", 545170159Sariff name, 546170159Sariff Collections.singleton(out), 547170159Sariff true, false); 548170159Sariff moduleLocations.put(name, l); 549170159Sariff pathLocations.put(out.toAbsolutePath(), l); 550170159Sariff } 551170159Sariff return l; 552170159Sariff } 553170159Sariff 554170159Sariff @Override 555170159Sariff Location getLocationForModule(Path dir) { 556170159Sariff return (pathLocations == null) ? null : pathLocations.get(dir); 557170159Sariff } 558170159Sariff 559170159Sariff private boolean listed; 560170159Sariff 561170159Sariff @Override 562170159Sariff Iterable<Set<Location>> listLocationsForModules() throws IOException { 563170159Sariff if (!listed && outputDir != null) { 564170159Sariff try (DirectoryStream<Path> stream = Files.newDirectoryStream(outputDir)) { 565170159Sariff for (Path p : stream) { 566170159Sariff getLocationForModule(p.getFileName().toString()); 567170159Sariff } 568170159Sariff } 569170159Sariff listed = true; 570170159Sariff } 571170159Sariff if (moduleLocations == null) 572170159Sariff return Collections.emptySet(); 573170159Sariff Set<Location> locns = new LinkedHashSet<>(); 574170159Sariff moduleLocations.forEach((k, v) -> locns.add(v)); 575170159Sariff return Collections.singleton(locns); 576170159Sariff } 577170159Sariff } 578170159Sariff 579170159Sariff /** 580170159Sariff * General purpose implementation for search path locations, 581170159Sariff * such as -sourcepath/SOURCE_PATH and -processorPath/ANNOTATION_PROCESSOR_PATH. 582170159Sariff * All options are treated as equivalent (i.e. aliases.) 583170159Sariff * The value is an ordered set of files and/or directories. 584170159Sariff */ 585170159Sariff private class SimpleLocationHandler extends BasicLocationHandler { 586170159Sariff 587170159Sariff protected Collection<Path> searchPath; 588170159Sariff 589170159Sariff SimpleLocationHandler(Location location, Option... options) { 590170159Sariff super(location, options); 591170159Sariff } 592170159Sariff 593170159Sariff @Override 594170159Sariff boolean handleOption(Option option, String value) { 595170159Sariff if (!options.contains(option)) { 596170159Sariff return false; 597170159Sariff } 598170159Sariff searchPath = value == null ? null 599170159Sariff : Collections.unmodifiableCollection(createPath().addFiles(value)); 600170159Sariff return true; 601170159Sariff } 602170159Sariff 603170159Sariff @Override 604170159Sariff Collection<Path> getPaths() { 605170159Sariff return searchPath; 606170159Sariff } 607170159Sariff 608170159Sariff @Override 609170159Sariff void setPaths(Iterable<? extends Path> files) { 610170159Sariff SearchPath p; 611170159Sariff if (files == null) { 612170159Sariff p = computePath(null); 613170159Sariff } else { 614170159Sariff p = createPath().addFiles(files); 615170159Sariff } 616170159Sariff searchPath = Collections.unmodifiableCollection(p); 617170159Sariff } 618170159Sariff 619170159Sariff protected SearchPath computePath(String value) { 620170159Sariff return createPath().addFiles(value); 621170159Sariff } 622170159Sariff 623170159Sariff protected SearchPath createPath() { 624170159Sariff return new SearchPath(); 625170159Sariff } 626170159Sariff } 627170159Sariff 628170159Sariff /** 629170159Sariff * Subtype of SimpleLocationHandler for -classpath/CLASS_PATH. 630170159Sariff * If no value is given, a default is provided, based on system properties and other values. 631170159Sariff */ 632170159Sariff private class ClassPathLocationHandler extends SimpleLocationHandler { 633170159Sariff 634170159Sariff ClassPathLocationHandler() { 635170159Sariff super(StandardLocation.CLASS_PATH, Option.CLASS_PATH); 636170159Sariff } 637170159Sariff 638170159Sariff @Override 639170159Sariff Collection<Path> getPaths() { 640170159Sariff lazy(); 641170159Sariff return searchPath; 642170159Sariff } 643170159Sariff 644170159Sariff @Override 645170159Sariff protected SearchPath computePath(String value) { 646170159Sariff String cp = value; 647170159Sariff 648170159Sariff // CLASSPATH environment variable when run from `javac'. 649170159Sariff if (cp == null) { 650170159Sariff cp = System.getProperty("env.class.path"); 651170159Sariff } 652170159Sariff 653170159Sariff // If invoked via a java VM (not the javac launcher), use the 654170159Sariff // platform class path 655170159Sariff if (cp == null && System.getProperty("application.home") == null) { 656170159Sariff cp = System.getProperty("java.class.path"); 657170159Sariff } 658170159Sariff 659170159Sariff // Default to current working directory. 660170159Sariff if (cp == null) { 661170159Sariff cp = "."; 662170159Sariff } 663170159Sariff 664170159Sariff return createPath().addFiles(cp); 665170159Sariff } 666170159Sariff 667170159Sariff @Override 668170159Sariff protected SearchPath createPath() { 669170159Sariff return new SearchPath() 670170159Sariff .expandJarClassPaths(true) // Only search user jars for Class-Paths 671170159Sariff .emptyPathDefault(getPath(".")); // Empty path elt ==> current directory 672170159Sariff } 673170159Sariff 674170159Sariff private void lazy() { 675170159Sariff if (searchPath == null) { 676170159Sariff setPaths(null); 677170159Sariff } 678170159Sariff } 679170159Sariff } 680170159Sariff 681170159Sariff /** 682170159Sariff * Custom subtype of LocationHandler for PLATFORM_CLASS_PATH. 683170159Sariff * Various options are supported for different components of the 684170159Sariff * platform class path. 685170159Sariff * Setting a value with setLocation overrides all existing option values. 686170159Sariff * Setting any option overrides any value set with setLocation, and 687170159Sariff * reverts to using default values for options that have not been set. 688170159Sariff * Setting -bootclasspath or -Xbootclasspath overrides any existing 689170159Sariff * value for -Xbootclasspath/p: and -Xbootclasspath/a:. 690170159Sariff */ 691170159Sariff private class BootClassPathLocationHandler extends BasicLocationHandler { 692170159Sariff 693170159Sariff private Collection<Path> searchPath; 694170159Sariff final Map<Option, String> optionValues = new EnumMap<>(Option.class); 695170159Sariff 696170159Sariff /** 697170159Sariff * Is the bootclasspath the default? 698170159Sariff */ 699170159Sariff private boolean isDefault; 700170159Sariff 701170159Sariff BootClassPathLocationHandler() { 702170159Sariff super(StandardLocation.PLATFORM_CLASS_PATH, 703170159Sariff Option.BOOT_CLASS_PATH, Option.XBOOTCLASSPATH, 704170159Sariff Option.XBOOTCLASSPATH_PREPEND, 705170159Sariff Option.XBOOTCLASSPATH_APPEND, 706170159Sariff Option.ENDORSEDDIRS, Option.DJAVA_ENDORSED_DIRS, 707170159Sariff Option.EXTDIRS, Option.DJAVA_EXT_DIRS); 708170159Sariff } 709170159Sariff 710170159Sariff boolean isDefault() { 711170159Sariff lazy(); 712170159Sariff return isDefault; 713170159Sariff } 714170159Sariff 715170159Sariff @Override 716170159Sariff boolean handleOption(Option option, String value) { 717170159Sariff if (!options.contains(option)) { 718170159Sariff return false; 719170159Sariff } 720170159Sariff 721170159Sariff option = canonicalize(option); 722170159Sariff optionValues.put(option, value); 723170159Sariff if (option == BOOT_CLASS_PATH) { 724170159Sariff optionValues.remove(XBOOTCLASSPATH_PREPEND); 725170159Sariff optionValues.remove(XBOOTCLASSPATH_APPEND); 726170159Sariff } 727170159Sariff searchPath = null; // reset to "uninitialized" 728170159Sariff return true; 729170159Sariff } 730170159Sariff // where 731170159Sariff // TODO: would be better if option aliasing was handled at a higher 732170159Sariff // level 733170159Sariff private Option canonicalize(Option option) { 734170159Sariff switch (option) { 735170719Sariff case XBOOTCLASSPATH: 736170719Sariff return Option.BOOT_CLASS_PATH; 737170719Sariff case DJAVA_ENDORSED_DIRS: 738170719Sariff return Option.ENDORSEDDIRS; 739170719Sariff case DJAVA_EXT_DIRS: 740170719Sariff return Option.EXTDIRS; 741170719Sariff default: 742170159Sariff return option; 743170719Sariff } 744170719Sariff } 745170159Sariff 746170159Sariff @Override 747170159Sariff Collection<Path> getPaths() { 748170159Sariff lazy(); 749170159Sariff return searchPath; 750170159Sariff } 751170159Sariff 752170159Sariff @Override 753170159Sariff void setPaths(Iterable<? extends Path> files) { 754170159Sariff if (files == null) { 755170159Sariff searchPath = null; // reset to "uninitialized" 756170159Sariff } else { 757170159Sariff isDefault = false; 758170159Sariff SearchPath p = new SearchPath().addFiles(files, false); 759170159Sariff searchPath = Collections.unmodifiableCollection(p); 760170159Sariff optionValues.clear(); 761170159Sariff } 762170159Sariff } 763170159Sariff 764170159Sariff SearchPath computePath() throws IOException { 765170159Sariff SearchPath path = new SearchPath(); 766170159Sariff 767170159Sariff String bootclasspathOpt = optionValues.get(BOOT_CLASS_PATH); 768170159Sariff String endorseddirsOpt = optionValues.get(ENDORSEDDIRS); 769170159Sariff String extdirsOpt = optionValues.get(EXTDIRS); 770170159Sariff String xbootclasspathPrependOpt = optionValues.get(XBOOTCLASSPATH_PREPEND); 771170159Sariff String xbootclasspathAppendOpt = optionValues.get(XBOOTCLASSPATH_APPEND); 772170159Sariff path.addFiles(xbootclasspathPrependOpt); 773170159Sariff 774170159Sariff if (endorseddirsOpt != null) { 775170159Sariff path.addDirectories(endorseddirsOpt); 776170159Sariff } else { 777170159Sariff path.addDirectories(System.getProperty("java.endorsed.dirs"), false); 778170159Sariff } 779170159Sariff 780170159Sariff if (bootclasspathOpt != null) { 781170159Sariff path.addFiles(bootclasspathOpt); 782170159Sariff } else { 783170159Sariff // Standard system classes for this compiler's release. 784170159Sariff Collection<Path> systemClasses = systemClasses(); 785170159Sariff if (systemClasses != null) { 786170159Sariff path.addFiles(systemClasses, false); 787170159Sariff } else { 788170159Sariff // fallback to the value of sun.boot.class.path 789170159Sariff String files = System.getProperty("sun.boot.class.path"); 790170159Sariff path.addFiles(files, false); 791170159Sariff } 792170159Sariff } 793170159Sariff 794170159Sariff path.addFiles(xbootclasspathAppendOpt); 795170159Sariff 796170159Sariff // Strictly speaking, standard extensions are not bootstrap 797170159Sariff // classes, but we treat them identically, so we'll pretend 798170159Sariff // that they are. 799170159Sariff if (extdirsOpt != null) { 800 path.addDirectories(extdirsOpt); 801 } else { 802 // Add lib/jfxrt.jar to the search path 803 Path jfxrt = javaHome.resolve("lib/jfxrt.jar"); 804 if (Files.exists(jfxrt)) { 805 path.addFile(jfxrt, false); 806 } 807 path.addDirectories(System.getProperty("java.ext.dirs"), false); 808 } 809 810 isDefault = 811 (xbootclasspathPrependOpt == null) 812 && (bootclasspathOpt == null) 813 && (xbootclasspathAppendOpt == null); 814 815 return path; 816 } 817 818 /** 819 * Return a collection of files containing system classes. 820 * Returns {@code null} if not running on a modular image. 821 * 822 * @throws UncheckedIOException if an I/O errors occurs 823 */ 824 private Collection<Path> systemClasses() throws IOException { 825 // Return "modules" jimage file if available 826 if (Files.isRegularFile(thisSystemModules)) { 827 return Collections.singleton(thisSystemModules); 828 } 829 830 // Exploded module image 831 Path modules = javaHome.resolve("modules"); 832 if (Files.isDirectory(modules.resolve("java.base"))) { 833 try (Stream<Path> listedModules = Files.list(modules)) { 834 return listedModules.collect(Collectors.toList()); 835 } 836 } 837 838 // not a modular image that we know about 839 return null; 840 } 841 842 private void lazy() { 843 if (searchPath == null) { 844 try { 845 searchPath = Collections.unmodifiableCollection(computePath()); 846 } catch (IOException e) { 847 // TODO: need better handling here, e.g. javac Abort? 848 throw new UncheckedIOException(e); 849 } 850 } 851 } 852 } 853 854 /** 855 * A LocationHander to represent modules found from a module-oriented 856 * location such as MODULE_SOURCE_PATH, UPGRADE_MODULE_PATH, 857 * SYSTEM_MODULES and MODULE_PATH. 858 * 859 * The Location can be specified to accept overriding classes from the 860 * {@code --patch-module <module>=<path> } parameter. 861 */ 862 private class ModuleLocationHandler extends LocationHandler implements Location { 863 protected final String name; 864 protected final String moduleName; 865 protected final Collection<Path> searchPath; 866 protected final Collection<Path> searchPathWithOverrides; 867 protected final boolean output; 868 869 ModuleLocationHandler(String name, String moduleName, Collection<Path> searchPath, 870 boolean output, boolean allowOverrides) { 871 this.name = name; 872 this.moduleName = moduleName; 873 this.searchPath = searchPath; 874 this.output = output; 875 876 if (allowOverrides && patchMap != null) { 877 SearchPath mPatch = patchMap.get(moduleName); 878 if (mPatch != null) { 879 SearchPath sp = new SearchPath(); 880 sp.addAll(mPatch); 881 sp.addAll(searchPath); 882 searchPathWithOverrides = sp; 883 } else { 884 searchPathWithOverrides = searchPath; 885 } 886 } else { 887 searchPathWithOverrides = searchPath; 888 } 889 } 890 891 @Override @DefinedBy(Api.COMPILER) 892 public String getName() { 893 return name; 894 } 895 896 @Override @DefinedBy(Api.COMPILER) 897 public boolean isOutputLocation() { 898 return output; 899 } 900 901 @Override // defined by LocationHandler 902 boolean handleOption(Option option, String value) { 903 throw new UnsupportedOperationException(); 904 } 905 906 @Override // defined by LocationHandler 907 Collection<Path> getPaths() { 908 // For now, we always return searchPathWithOverrides. This may differ from the 909 // JVM behavior if there is a module-info.class to be found in the overriding 910 // classes. 911 return searchPathWithOverrides; 912 } 913 914 @Override // defined by LocationHandler 915 void setPaths(Iterable<? extends Path> files) throws IOException { 916 throw new UnsupportedOperationException(); 917 } 918 919 @Override // defined by LocationHandler 920 String inferModuleName() { 921 return moduleName; 922 } 923 } 924 925 /** 926 * A LocationHandler for simple module-oriented search paths, 927 * like UPGRADE_MODULE_PATH and MODULE_PATH. 928 */ 929 private class ModulePathLocationHandler extends SimpleLocationHandler { 930 private Map<String, ModuleLocationHandler> pathModules; 931 932 ModulePathLocationHandler(Location location, Option... options) { 933 super(location, options); 934 } 935 936 @Override 937 public boolean handleOption(Option option, String value) { 938 if (!options.contains(option)) { 939 return false; 940 } 941 setPaths(value == null ? null : getPathEntries(value)); 942 return true; 943 } 944 945 @Override 946 public Location getLocationForModule(String moduleName) { 947 initPathModules(); 948 return pathModules.get(moduleName); 949 } 950 951 @Override 952 Iterable<Set<Location>> listLocationsForModules() { 953 if (searchPath == null) 954 return Collections.emptyList(); 955 956 return ModulePathIterator::new; 957 } 958 959 @Override 960 void setPaths(Iterable<? extends Path> paths) { 961 if (paths != null) { 962 for (Path p: paths) { 963 checkValidModulePathEntry(p); 964 } 965 } 966 super.setPaths(paths); 967 } 968 969 private void initPathModules() { 970 if (pathModules != null) { 971 return; 972 } 973 974 pathModules = new LinkedHashMap<>(); 975 976 for (Set<Location> set : listLocationsForModules()) { 977 for (Location locn : set) { 978 if (locn instanceof ModuleLocationHandler) { 979 ModuleLocationHandler h = (ModuleLocationHandler) locn; 980 pathModules.put(h.moduleName, h); 981 } 982 } 983 } 984 } 985 986 private void checkValidModulePathEntry(Path p) { 987 if (Files.isDirectory(p)) { 988 // either an exploded module or a directory of modules 989 return; 990 } 991 992 String name = p.getFileName().toString(); 993 int lastDot = name.lastIndexOf("."); 994 if (lastDot > 0) { 995 switch (name.substring(lastDot)) { 996 case ".jar": 997 case ".jmod": 998 return; 999 } 1000 } 1001 throw new IllegalArgumentException(p.toString()); 1002 } 1003 1004 class ModulePathIterator implements Iterator<Set<Location>> { 1005 Iterator<Path> pathIter = searchPath.iterator(); 1006 int pathIndex = 0; 1007 Set<Location> next = null; 1008 1009 @Override 1010 public boolean hasNext() { 1011 if (next != null) 1012 return true; 1013 1014 while (next == null) { 1015 if (pathIter.hasNext()) { 1016 Path path = pathIter.next(); 1017 if (Files.isDirectory(path)) { 1018 next = scanDirectory(path); 1019 } else { 1020 next = scanFile(path); 1021 } 1022 pathIndex++; 1023 } else 1024 return false; 1025 } 1026 return true; 1027 } 1028 1029 @Override 1030 public Set<Location> next() { 1031 hasNext(); 1032 if (next != null) { 1033 Set<Location> result = next; 1034 next = null; 1035 return result; 1036 } 1037 throw new NoSuchElementException(); 1038 } 1039 1040 private Set<Location> scanDirectory(Path path) { 1041 Set<Path> paths = new LinkedHashSet<>(); 1042 Path moduleInfoClass = null; 1043 try (DirectoryStream<Path> stream = Files.newDirectoryStream(path)) { 1044 for (Path entry: stream) { 1045 if (entry.endsWith("module-info.class")) { 1046 moduleInfoClass = entry; 1047 break; // no need to continue scanning 1048 } 1049 paths.add(entry); 1050 } 1051 } catch (DirectoryIteratorException | IOException ignore) { 1052 log.error(Errors.LocnCantReadDirectory(path)); 1053 return Collections.emptySet(); 1054 } 1055 1056 if (moduleInfoClass != null) { 1057 // It's an exploded module directly on the module path. 1058 // We can't infer module name from the directory name, so have to 1059 // read module-info.class. 1060 try { 1061 String moduleName = readModuleName(moduleInfoClass); 1062 String name = location.getName() 1063 + "[" + pathIndex + ":" + moduleName + "]"; 1064 ModuleLocationHandler l = new ModuleLocationHandler(name, moduleName, 1065 Collections.singleton(path), false, true); 1066 return Collections.singleton(l); 1067 } catch (ModuleNameReader.BadClassFile e) { 1068 log.error(Errors.LocnBadModuleInfo(path)); 1069 return Collections.emptySet(); 1070 } catch (IOException e) { 1071 log.error(Errors.LocnCantReadFile(path)); 1072 return Collections.emptySet(); 1073 } 1074 } 1075 1076 // A directory of modules 1077 Set<Location> result = new LinkedHashSet<>(); 1078 int index = 0; 1079 for (Path entry : paths) { 1080 Pair<String,Path> module = inferModuleName(entry); 1081 if (module == null) { 1082 // diagnostic reported if necessary; skip to next 1083 continue; 1084 } 1085 String moduleName = module.fst; 1086 Path modulePath = module.snd; 1087 String name = location.getName() 1088 + "[" + pathIndex + "." + (index++) + ":" + moduleName + "]"; 1089 ModuleLocationHandler l = new ModuleLocationHandler(name, moduleName, 1090 Collections.singleton(modulePath), false, true); 1091 result.add(l); 1092 } 1093 return result; 1094 } 1095 1096 private Set<Location> scanFile(Path path) { 1097 Pair<String,Path> module = inferModuleName(path); 1098 if (module == null) { 1099 // diagnostic reported if necessary 1100 return Collections.emptySet(); 1101 } 1102 String moduleName = module.fst; 1103 Path modulePath = module.snd; 1104 String name = location.getName() 1105 + "[" + pathIndex + ":" + moduleName + "]"; 1106 ModuleLocationHandler l = new ModuleLocationHandler(name, moduleName, 1107 Collections.singleton(modulePath), false, true); 1108 return Collections.singleton(l); 1109 } 1110 1111 private Pair<String,Path> inferModuleName(Path p) { 1112 if (Files.isDirectory(p)) { 1113 if (Files.exists(p.resolve("module-info.class"))) { 1114 String name = p.getFileName().toString(); 1115 if (SourceVersion.isName(name)) 1116 return new Pair<>(name, p); 1117 } 1118 return null; 1119 } 1120 1121 if (p.getFileName().toString().endsWith(".jar") && fsInfo.exists(p)) { 1122 URI uri = URI.create("jar:" + p.toUri()); 1123 try (FileSystem fs = FileSystems.newFileSystem(uri, fsEnv, null)) { 1124 Path moduleInfoClass = fs.getPath("module-info.class"); 1125 if (Files.exists(moduleInfoClass)) { 1126 String moduleName = readModuleName(moduleInfoClass); 1127 return new Pair<>(moduleName, p); 1128 } 1129 } catch (ModuleNameReader.BadClassFile e) { 1130 log.error(Errors.LocnBadModuleInfo(p)); 1131 return null; 1132 } catch (IOException e) { 1133 log.error(Errors.LocnCantReadFile(p)); 1134 return null; 1135 } catch (ProviderNotFoundException e) { 1136 log.error(Errors.NoZipfsForArchive(p)); 1137 return null; 1138 } 1139 1140 //automatic module: 1141 String fn = p.getFileName().toString(); 1142 //from ModulePath.deriveModuleDescriptor: 1143 1144 // drop .jar 1145 String mn = fn.substring(0, fn.length()-4); 1146 1147 // find first occurrence of -${NUMBER}. or -${NUMBER}$ 1148 Matcher matcher = Pattern.compile("-(\\d+(\\.|$))").matcher(mn); 1149 if (matcher.find()) { 1150 int start = matcher.start(); 1151 1152 mn = mn.substring(0, start); 1153 } 1154 1155 // finally clean up the module name 1156 mn = mn.replaceAll("(\\.|\\d)*$", "") // remove trailing version 1157 .replaceAll("[^A-Za-z0-9]", ".") // replace non-alphanumeric 1158 .replaceAll("(\\.)(\\1)+", ".") // collapse repeating dots 1159 .replaceAll("^\\.", "") // drop leading dots 1160 .replaceAll("\\.$", ""); // drop trailing dots 1161 1162 1163 if (!mn.isEmpty()) { 1164 return new Pair<>(mn, p); 1165 } 1166 1167 log.error(Errors.LocnCantGetModuleNameForJar(p)); 1168 return null; 1169 } 1170 1171 if (p.getFileName().toString().endsWith(".jmod")) { 1172 try { 1173 // check if the JMOD file is valid 1174 JDK9Wrappers.JmodFile.checkMagic(p); 1175 1176 // No JMOD file system. Use JarFileSystem to 1177 // workaround for now 1178 FileSystem fs = fileSystems.get(p); 1179 if (fs == null) { 1180 URI uri = URI.create("jar:" + p.toUri()); 1181 fs = FileSystems.newFileSystem(uri, Collections.emptyMap(), null); 1182 try { 1183 Path moduleInfoClass = fs.getPath("classes/module-info.class"); 1184 String moduleName = readModuleName(moduleInfoClass); 1185 Path modulePath = fs.getPath("classes"); 1186 fileSystems.put(p, fs); 1187 closeables.add(fs); 1188 fs = null; // prevent fs being closed in the finally clause 1189 return new Pair<>(moduleName, modulePath); 1190 } finally { 1191 if (fs != null) 1192 fs.close(); 1193 } 1194 } 1195 } catch (ModuleNameReader.BadClassFile e) { 1196 log.error(Errors.LocnBadModuleInfo(p)); 1197 } catch (IOException | ProviderNotFoundException e) { 1198 log.error(Errors.LocnCantReadFile(p)); 1199 return null; 1200 } 1201 } 1202 1203 if (warn && false) { // temp disable, when enabled, massage examples.not-yet.txt suitably. 1204 log.warning(Warnings.LocnUnknownFileOnModulePath(p)); 1205 } 1206 return null; 1207 } 1208 1209 private String readModuleName(Path path) throws IOException, ModuleNameReader.BadClassFile { 1210 if (moduleNameReader == null) 1211 moduleNameReader = new ModuleNameReader(); 1212 return moduleNameReader.readModuleName(path); 1213 } 1214 } 1215 1216 } 1217 1218 private class ModuleSourcePathLocationHandler extends BasicLocationHandler { 1219 1220 private Map<String, Location> moduleLocations; 1221 private Map<Path, Location> pathLocations; 1222 1223 ModuleSourcePathLocationHandler() { 1224 super(StandardLocation.MODULE_SOURCE_PATH, 1225 Option.MODULE_SOURCE_PATH); 1226 } 1227 1228 @Override 1229 boolean handleOption(Option option, String value) { 1230 init(value); 1231 return true; 1232 } 1233 1234 void init(String value) { 1235 Collection<String> segments = new ArrayList<>(); 1236 for (String s: value.split(File.pathSeparator)) { 1237 expandBraces(s, segments); 1238 } 1239 1240 Map<String, Collection<Path>> map = new LinkedHashMap<>(); 1241 final String MARKER = "*"; 1242 for (String seg: segments) { 1243 int markStart = seg.indexOf(MARKER); 1244 if (markStart == -1) { 1245 add(map, getPath(seg), null); 1246 } else { 1247 if (markStart == 0 || !isSeparator(seg.charAt(markStart - 1))) { 1248 throw new IllegalArgumentException("illegal use of " + MARKER + " in " + seg); 1249 } 1250 Path prefix = getPath(seg.substring(0, markStart - 1)); 1251 Path suffix; 1252 int markEnd = markStart + MARKER.length(); 1253 if (markEnd == seg.length()) { 1254 suffix = null; 1255 } else if (!isSeparator(seg.charAt(markEnd)) 1256 || seg.indexOf(MARKER, markEnd) != -1) { 1257 throw new IllegalArgumentException("illegal use of " + MARKER + " in " + seg); 1258 } else { 1259 suffix = getPath(seg.substring(markEnd + 1)); 1260 } 1261 add(map, prefix, suffix); 1262 } 1263 } 1264 1265 moduleLocations = new LinkedHashMap<>(); 1266 pathLocations = new LinkedHashMap<>(); 1267 map.forEach((k, v) -> { 1268 String name = location.getName() + "[" + k + "]"; 1269 ModuleLocationHandler h = new ModuleLocationHandler(name, k, v, false, false); 1270 moduleLocations.put(k, h); 1271 v.forEach(p -> pathLocations.put(normalize(p), h)); 1272 }); 1273 } 1274 1275 private boolean isSeparator(char ch) { 1276 // allow both separators on Windows 1277 return (ch == File.separatorChar) || (ch == '/'); 1278 } 1279 1280 void add(Map<String, Collection<Path>> map, Path prefix, Path suffix) { 1281 if (!Files.isDirectory(prefix)) { 1282 if (warn) { 1283 String key = Files.exists(prefix) 1284 ? "dir.path.element.not.directory" 1285 : "dir.path.element.not.found"; 1286 log.warning(Lint.LintCategory.PATH, key, prefix); 1287 } 1288 return; 1289 } 1290 try (DirectoryStream<Path> stream = Files.newDirectoryStream(prefix, path -> Files.isDirectory(path))) { 1291 for (Path entry: stream) { 1292 Path path = (suffix == null) ? entry : entry.resolve(suffix); 1293 if (Files.isDirectory(path)) { 1294 String name = entry.getFileName().toString(); 1295 Collection<Path> paths = map.get(name); 1296 if (paths == null) 1297 map.put(name, paths = new ArrayList<>()); 1298 paths.add(path); 1299 } 1300 } 1301 } catch (IOException e) { 1302 // TODO? What to do? 1303 System.err.println(e); 1304 } 1305 } 1306 1307 private void expandBraces(String value, Collection<String> results) { 1308 int depth = 0; 1309 int start = -1; 1310 String prefix = null; 1311 String suffix = null; 1312 for (int i = 0; i < value.length(); i++) { 1313 switch (value.charAt(i)) { 1314 case '{': 1315 depth++; 1316 if (depth == 1) { 1317 prefix = value.substring(0, i); 1318 suffix = value.substring(getMatchingBrace(value, i) + 1); 1319 start = i + 1; 1320 } 1321 break; 1322 1323 case ',': 1324 if (depth == 1) { 1325 String elem = value.substring(start, i); 1326 expandBraces(prefix + elem + suffix, results); 1327 start = i + 1; 1328 } 1329 break; 1330 1331 case '}': 1332 switch (depth) { 1333 case 0: 1334 throw new IllegalArgumentException("mismatched braces"); 1335 1336 case 1: 1337 String elem = value.substring(start, i); 1338 expandBraces(prefix + elem + suffix, results); 1339 return; 1340 1341 default: 1342 depth--; 1343 } 1344 break; 1345 } 1346 } 1347 if (depth > 0) 1348 throw new IllegalArgumentException("mismatched braces"); 1349 results.add(value); 1350 } 1351 1352 int getMatchingBrace(String value, int offset) { 1353 int depth = 1; 1354 for (int i = offset + 1; i < value.length(); i++) { 1355 switch (value.charAt(i)) { 1356 case '{': 1357 depth++; 1358 break; 1359 1360 case '}': 1361 if (--depth == 0) 1362 return i; 1363 break; 1364 } 1365 } 1366 throw new IllegalArgumentException("mismatched braces"); 1367 } 1368 1369 @Override 1370 boolean isSet() { 1371 return (moduleLocations != null); 1372 } 1373 1374 @Override 1375 Collection<Path> getPaths() { 1376 throw new UnsupportedOperationException(); 1377 } 1378 1379 @Override 1380 void setPaths(Iterable<? extends Path> files) throws IOException { 1381 throw new UnsupportedOperationException(); 1382 } 1383 1384 @Override 1385 Location getLocationForModule(String name) { 1386 return (moduleLocations == null) ? null : moduleLocations.get(name); 1387 } 1388 1389 @Override 1390 Location getLocationForModule(Path dir) { 1391 return (pathLocations == null) ? null : pathLocations.get(dir); 1392 } 1393 1394 @Override 1395 Iterable<Set<Location>> listLocationsForModules() { 1396 if (moduleLocations == null) 1397 return Collections.emptySet(); 1398 Set<Location> locns = new LinkedHashSet<>(); 1399 moduleLocations.forEach((k, v) -> locns.add(v)); 1400 return Collections.singleton(locns); 1401 } 1402 1403 } 1404 1405 private class SystemModulesLocationHandler extends BasicLocationHandler { 1406 private Path systemJavaHome; 1407 private Path modules; 1408 private Map<String, ModuleLocationHandler> systemModules; 1409 1410 SystemModulesLocationHandler() { 1411 super(StandardLocation.SYSTEM_MODULES, Option.SYSTEM); 1412 systemJavaHome = Locations.javaHome; 1413 } 1414 1415 @Override 1416 boolean handleOption(Option option, String value) { 1417 if (!options.contains(option)) { 1418 return false; 1419 } 1420 1421 if (value == null) { 1422 systemJavaHome = Locations.javaHome; 1423 } else if (value.equals("none")) { 1424 systemJavaHome = null; 1425 } else { 1426 update(getPath(value)); 1427 } 1428 1429 modules = null; 1430 return true; 1431 } 1432 1433 @Override 1434 Collection<Path> getPaths() { 1435 return (systemJavaHome == null) ? null : Collections.singleton(systemJavaHome); 1436 } 1437 1438 @Override 1439 void setPaths(Iterable<? extends Path> files) throws IOException { 1440 if (files == null) { 1441 systemJavaHome = null; 1442 } else { 1443 Iterator<? extends Path> pathIter = files.iterator(); 1444 if (!pathIter.hasNext()) { 1445 throw new IllegalArgumentException("empty path for directory"); // TODO: FIXME 1446 } 1447 Path dir = pathIter.next(); 1448 if (pathIter.hasNext()) { 1449 throw new IllegalArgumentException("path too long for directory"); // TODO: FIXME 1450 } 1451 if (!Files.exists(dir)) { 1452 throw new FileNotFoundException(dir + ": does not exist"); 1453 } else if (!Files.isDirectory(dir)) { 1454 throw new IOException(dir + ": not a directory"); 1455 } 1456 update(dir); 1457 } 1458 } 1459 1460 private void update(Path p) { 1461 if (!isCurrentPlatform(p) && !Files.exists(p.resolve("lib").resolve("jrt-fs.jar")) && 1462 !Files.exists(systemJavaHome.resolve("modules"))) 1463 throw new IllegalArgumentException(p.toString()); 1464 systemJavaHome = p; 1465 modules = null; 1466 } 1467 1468 private boolean isCurrentPlatform(Path p) { 1469 try { 1470 return Files.isSameFile(p, Locations.javaHome); 1471 } catch (IOException ex) { 1472 throw new IllegalArgumentException(p.toString(), ex); 1473 } 1474 } 1475 1476 @Override 1477 Location getLocationForModule(String name) throws IOException { 1478 initSystemModules(); 1479 return systemModules.get(name); 1480 } 1481 1482 @Override 1483 Iterable<Set<Location>> listLocationsForModules() throws IOException { 1484 initSystemModules(); 1485 Set<Location> locns = new LinkedHashSet<>(); 1486 for (Location l: systemModules.values()) 1487 locns.add(l); 1488 return Collections.singleton(locns); 1489 } 1490 1491 private void initSystemModules() throws IOException { 1492 if (systemModules != null) { 1493 return; 1494 } 1495 1496 if (systemJavaHome == null) { 1497 systemModules = Collections.emptyMap(); 1498 return; 1499 } 1500 1501 if (modules == null) { 1502 try { 1503 URI jrtURI = URI.create("jrt:/"); 1504 FileSystem jrtfs; 1505 1506 if (isCurrentPlatform(systemJavaHome)) { 1507 jrtfs = FileSystems.getFileSystem(jrtURI); 1508 } else { 1509 try { 1510 Map<String, String> attrMap = 1511 Collections.singletonMap("java.home", systemJavaHome.toString()); 1512 jrtfs = FileSystems.newFileSystem(jrtURI, attrMap); 1513 } catch (ProviderNotFoundException ex) { 1514 URL javaHomeURL = systemJavaHome.resolve("jrt-fs.jar").toUri().toURL(); 1515 ClassLoader currentLoader = Locations.class.getClassLoader(); 1516 URLClassLoader fsLoader = 1517 new URLClassLoader(new URL[] {javaHomeURL}, currentLoader); 1518 1519 jrtfs = FileSystems.newFileSystem(jrtURI, Collections.emptyMap(), fsLoader); 1520 1521 closeables.add(fsLoader); 1522 } 1523 1524 closeables.add(jrtfs); 1525 } 1526 1527 modules = jrtfs.getPath("/modules"); 1528 } catch (FileSystemNotFoundException | ProviderNotFoundException e) { 1529 modules = systemJavaHome.resolve("modules"); 1530 if (!Files.exists(modules)) 1531 throw new IOException("can't find system classes", e); 1532 } 1533 } 1534 1535 systemModules = new LinkedHashMap<>(); 1536 try (DirectoryStream<Path> stream = Files.newDirectoryStream(modules, Files::isDirectory)) { 1537 for (Path entry : stream) { 1538 String moduleName = entry.getFileName().toString(); 1539 String name = location.getName() + "[" + moduleName + "]"; 1540 ModuleLocationHandler h = new ModuleLocationHandler(name, moduleName, 1541 Collections.singleton(entry), false, true); 1542 systemModules.put(moduleName, h); 1543 } 1544 } 1545 } 1546 } 1547 1548 Map<Location, LocationHandler> handlersForLocation; 1549 Map<Option, LocationHandler> handlersForOption; 1550 1551 void initHandlers() { 1552 handlersForLocation = new HashMap<>(); 1553 handlersForOption = new EnumMap<>(Option.class); 1554 1555 BasicLocationHandler[] handlers = { 1556 new BootClassPathLocationHandler(), 1557 new ClassPathLocationHandler(), 1558 new SimpleLocationHandler(StandardLocation.SOURCE_PATH, Option.SOURCE_PATH), 1559 new SimpleLocationHandler(StandardLocation.ANNOTATION_PROCESSOR_PATH, Option.PROCESSOR_PATH), 1560 new SimpleLocationHandler(StandardLocation.ANNOTATION_PROCESSOR_MODULE_PATH, Option.PROCESSOR_MODULE_PATH), 1561 new OutputLocationHandler(StandardLocation.CLASS_OUTPUT, Option.D), 1562 new OutputLocationHandler(StandardLocation.SOURCE_OUTPUT, Option.S), 1563 new OutputLocationHandler(StandardLocation.NATIVE_HEADER_OUTPUT, Option.H), 1564 new ModuleSourcePathLocationHandler(), 1565 // TODO: should UPGRADE_MODULE_PATH be merged with SYSTEM_MODULES? 1566 new ModulePathLocationHandler(StandardLocation.UPGRADE_MODULE_PATH, Option.UPGRADE_MODULE_PATH), 1567 new ModulePathLocationHandler(StandardLocation.MODULE_PATH, Option.MODULE_PATH), 1568 new SystemModulesLocationHandler(), 1569 }; 1570 1571 for (BasicLocationHandler h : handlers) { 1572 handlersForLocation.put(h.location, h); 1573 for (Option o : h.options) { 1574 handlersForOption.put(o, h); 1575 } 1576 } 1577 } 1578 1579 private Map<String, SearchPath> patchMap; 1580 1581 boolean handleOption(Option option, String value) { 1582 switch (option) { 1583 case PATCH_MODULE: 1584 if (value == null) { 1585 patchMap = null; 1586 } else { 1587 // Allow an extended syntax for --patch-module consisting of a series 1588 // of values separated by NULL characters. This is to facilitate 1589 // supporting deferred file manager options on the command line. 1590 // See Option.PATCH_MODULE for the code that composes these multiple 1591 // values. 1592 for (String v : value.split("\0")) { 1593 int eq = v.indexOf('='); 1594 if (eq > 0) { 1595 String mName = v.substring(0, eq); 1596 SearchPath mPatchPath = new SearchPath() 1597 .addFiles(v.substring(eq + 1)); 1598 boolean ok = true; 1599 for (Path p : mPatchPath) { 1600 Path mi = p.resolve("module-info.class"); 1601 if (Files.exists(mi)) { 1602 log.error(Errors.LocnModuleInfoNotAllowedOnPatchPath(mi)); 1603 ok = false; 1604 } 1605 } 1606 if (ok) { 1607 if (patchMap == null) { 1608 patchMap = new LinkedHashMap<>(); 1609 } 1610 patchMap.put(mName, mPatchPath); 1611 } 1612 } else { 1613 // Should not be able to get here; 1614 // this should be caught and handled in Option.PATCH_MODULE 1615 log.error(Errors.LocnInvalidArgForXpatch(value)); 1616 } 1617 } 1618 } 1619 return true; 1620 default: 1621 LocationHandler h = handlersForOption.get(option); 1622 return (h == null ? false : h.handleOption(option, value)); 1623 } 1624 } 1625 1626 boolean hasLocation(Location location) { 1627 LocationHandler h = getHandler(location); 1628 return (h == null ? false : h.isSet()); 1629 } 1630 1631 Collection<Path> getLocation(Location location) { 1632 LocationHandler h = getHandler(location); 1633 return (h == null ? null : h.getPaths()); 1634 } 1635 1636 Path getOutputLocation(Location location) { 1637 if (!location.isOutputLocation()) { 1638 throw new IllegalArgumentException(); 1639 } 1640 LocationHandler h = getHandler(location); 1641 return ((OutputLocationHandler) h).outputDir; 1642 } 1643 1644 void setLocation(Location location, Iterable<? extends Path> files) throws IOException { 1645 LocationHandler h = getHandler(location); 1646 if (h == null) { 1647 if (location.isOutputLocation()) { 1648 h = new OutputLocationHandler(location); 1649 } else { 1650 h = new SimpleLocationHandler(location); 1651 } 1652 handlersForLocation.put(location, h); 1653 } 1654 h.setPaths(files); 1655 } 1656 1657 Location getLocationForModule(Location location, String name) throws IOException { 1658 LocationHandler h = getHandler(location); 1659 return (h == null ? null : h.getLocationForModule(name)); 1660 } 1661 1662 Location getLocationForModule(Location location, Path dir) { 1663 LocationHandler h = getHandler(location); 1664 return (h == null ? null : h.getLocationForModule(dir)); 1665 } 1666 1667 String inferModuleName(Location location) { 1668 LocationHandler h = getHandler(location); 1669 return (h == null ? null : h.inferModuleName()); 1670 } 1671 1672 Iterable<Set<Location>> listLocationsForModules(Location location) throws IOException { 1673 LocationHandler h = getHandler(location); 1674 return (h == null ? null : h.listLocationsForModules()); 1675 } 1676 1677 protected LocationHandler getHandler(Location location) { 1678 Objects.requireNonNull(location); 1679 return (location instanceof LocationHandler) 1680 ? (LocationHandler) location 1681 : handlersForLocation.get(location); 1682 } 1683 1684 /** 1685 * Is this the name of an archive file? 1686 */ 1687 private boolean isArchive(Path file) { 1688 String n = StringUtils.toLowerCase(file.getFileName().toString()); 1689 return fsInfo.isFile(file) 1690 && (n.endsWith(".jar") || n.endsWith(".zip")); 1691 } 1692 1693 static Path normalize(Path p) { 1694 try { 1695 return p.toRealPath(); 1696 } catch (IOException e) { 1697 return p.toAbsolutePath().normalize(); 1698 } 1699 } 1700 1701} 1702