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