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