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