1 /*
   2  * Copyright (c) 2003, 2019, 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.InputStream;
  33 import java.io.UncheckedIOException;
  34 import java.net.URI;
  35 import java.net.URL;
  36 import java.net.URLClassLoader;
  37 import java.nio.file.DirectoryIteratorException;
  38 import java.nio.file.DirectoryStream;
  39 import java.nio.file.FileSystem;
  40 import java.nio.file.FileSystemNotFoundException;
  41 import java.nio.file.FileSystems;
  42 import java.nio.file.Files;
  43 import java.nio.file.InvalidPathException;
  44 import java.nio.file.Path;
  45 import java.nio.file.Paths;
  46 import java.nio.file.ProviderNotFoundException;
  47 import java.nio.file.spi.FileSystemProvider;
  48 import java.util.ArrayList;
  49 import java.util.Arrays;
  50 import java.util.Collection;
  51 import java.util.Collections;
  52 import java.util.EnumMap;
  53 import java.util.EnumSet;
  54 import java.util.HashMap;
  55 import java.util.HashSet;
  56 import java.util.Iterator;
  57 import java.util.LinkedHashMap;
  58 import java.util.LinkedHashSet;
  59 import java.util.List;
  60 import java.util.Map;
  61 import java.util.Objects;
  62 import java.util.NoSuchElementException;
  63 import java.util.Set;
  64 import java.util.function.Predicate;
  65 import java.util.regex.Matcher;
  66 import java.util.regex.Pattern;
  67 import java.util.stream.Collectors;
  68 import java.util.stream.Stream;
  69 import java.util.jar.Attributes;
  70 import java.util.jar.Manifest;
  71 
  72 import javax.lang.model.SourceVersion;
  73 import javax.tools.JavaFileManager;
  74 import javax.tools.JavaFileManager.Location;
  75 import javax.tools.JavaFileObject;
  76 import javax.tools.StandardJavaFileManager;
  77 import javax.tools.StandardJavaFileManager.PathFactory;
  78 import javax.tools.StandardLocation;
  79 
  80 import jdk.internal.jmod.JmodFile;
  81 
  82 import com.sun.tools.javac.code.Lint;
  83 import com.sun.tools.javac.code.Lint.LintCategory;
  84 import com.sun.tools.javac.main.Option;
  85 import com.sun.tools.javac.resources.CompilerProperties.Errors;
  86 import com.sun.tools.javac.resources.CompilerProperties.Warnings;
  87 import com.sun.tools.javac.util.DefinedBy;
  88 import com.sun.tools.javac.util.DefinedBy.Api;
  89 import com.sun.tools.javac.util.JCDiagnostic.Warning;
  90 import com.sun.tools.javac.util.ListBuffer;
  91 import com.sun.tools.javac.util.Log;
  92 import com.sun.tools.javac.jvm.ModuleNameReader;
  93 import com.sun.tools.javac.util.Iterators;
  94 import com.sun.tools.javac.util.Pair;
  95 import com.sun.tools.javac.util.StringUtils;
  96 
  97 import static javax.tools.StandardLocation.SYSTEM_MODULES;
  98 import static javax.tools.StandardLocation.PLATFORM_CLASS_PATH;
  99 
 100 import static com.sun.tools.javac.main.Option.BOOT_CLASS_PATH;
 101 import static com.sun.tools.javac.main.Option.ENDORSEDDIRS;
 102 import static com.sun.tools.javac.main.Option.EXTDIRS;
 103 import static com.sun.tools.javac.main.Option.XBOOTCLASSPATH_APPEND;
 104 import static com.sun.tools.javac.main.Option.XBOOTCLASSPATH_PREPEND;
 105 
 106 /**
 107  * This class converts command line arguments, environment variables and system properties (in
 108  * File.pathSeparator-separated String form) into a boot class path, user class path, and source
 109  * path (in {@code Collection<String>} form).
 110  *
 111  * <p>
 112  * <b>This is NOT part of any supported API. If you write code that depends on this, you do so at
 113  * your own risk. This code and its internal interfaces are subject to change or deletion without
 114  * notice.</b>
 115  */
 116 public class Locations {
 117 
 118     /**
 119      * The log to use for warning output
 120      */
 121     private Log log;
 122 
 123     /**
 124      * Access to (possibly cached) file info
 125      */
 126     private FSInfo fsInfo;
 127 
 128     /**
 129      * Whether to warn about non-existent path elements
 130      */
 131     private boolean warn;
 132 
 133     private ModuleNameReader moduleNameReader;
 134 
 135     private PathFactory pathFactory = Paths::get;
 136 
 137     static final Path javaHome = FileSystems.getDefault().getPath(System.getProperty("java.home"));
 138     static final Path thisSystemModules = javaHome.resolve("lib").resolve("modules");
 139 
 140     Map<Path, FileSystem> fileSystems = new LinkedHashMap<>();
 141     List<Closeable> closeables = new ArrayList<>();
 142     private Map<String,String> fsEnv = Collections.emptyMap();
 143 
 144     Locations() {
 145         initHandlers();
 146     }
 147 
 148     Path getPath(String first, String... more) {
 149         try {
 150             return pathFactory.getPath(first, more);
 151         } catch (InvalidPathException ipe) {
 152             throw new IllegalArgumentException(ipe);
 153         }
 154     }
 155 
 156     public void close() throws IOException {
 157         ListBuffer<IOException> list = new ListBuffer<>();
 158         closeables.forEach(closeable -> {
 159             try {
 160                 closeable.close();
 161             } catch (IOException ex) {
 162                 list.add(ex);
 163             }
 164         });
 165         if (list.nonEmpty()) {
 166             IOException ex = new IOException();
 167             for (IOException e: list)
 168                 ex.addSuppressed(e);
 169             throw ex;
 170         }
 171     }
 172 
 173     void update(Log log, boolean warn, FSInfo fsInfo) {
 174         this.log = log;
 175         this.warn = warn;
 176         this.fsInfo = fsInfo;
 177     }
 178 
 179     void setPathFactory(PathFactory f) {
 180         pathFactory = f;
 181     }
 182 
 183     boolean isDefaultBootClassPath() {
 184         BootClassPathLocationHandler h
 185                 = (BootClassPathLocationHandler) getHandler(PLATFORM_CLASS_PATH);
 186         return h.isDefault();
 187     }
 188 
 189     boolean isDefaultSystemModulesPath() {
 190         SystemModulesLocationHandler h
 191                 = (SystemModulesLocationHandler) getHandler(SYSTEM_MODULES);
 192         return !h.isExplicit();
 193     }
 194 
 195     /**
 196      * Split a search path into its elements. Empty path elements will be ignored.
 197      *
 198      * @param searchPath The search path to be split
 199      * @return The elements of the path
 200      */
 201     private Iterable<Path> getPathEntries(String searchPath) {
 202         return getPathEntries(searchPath, null);
 203     }
 204 
 205     /**
 206      * Split a search path into its elements. If emptyPathDefault is not null, all empty elements in the
 207      * path, including empty elements at either end of the path, will be replaced with the value of
 208      * emptyPathDefault.
 209      *
 210      * @param searchPath The search path to be split
 211      * @param emptyPathDefault The value to substitute for empty path elements, or null, to ignore
 212      * empty path elements
 213      * @return The elements of the path
 214      */
 215     private Iterable<Path> getPathEntries(String searchPath, Path emptyPathDefault) {
 216         ListBuffer<Path> entries = new ListBuffer<>();
 217         for (String s: searchPath.split(Pattern.quote(File.pathSeparator), -1)) {
 218             if (s.isEmpty()) {
 219                 if (emptyPathDefault != null) {
 220                     entries.add(emptyPathDefault);
 221                 }
 222             } else {
 223                 try {
 224                     entries.add(getPath(s));
 225                 } catch (IllegalArgumentException e) {
 226                     if (warn) {
 227                         log.warning(LintCategory.PATH, Warnings.InvalidPath(s));
 228                     }
 229                 }
 230             }
 231         }
 232         return entries;
 233     }
 234 
 235     public void setMultiReleaseValue(String multiReleaseValue) {
 236         fsEnv = Collections.singletonMap("releaseVersion", multiReleaseValue);
 237     }
 238 
 239     private boolean contains(Collection<Path> searchPath, Path file) throws IOException {
 240 
 241         if (searchPath == null) {
 242             return false;
 243         }
 244 
 245         Path enclosingJar = null;
 246         if (file.getFileSystem().provider() == fsInfo.getJarFSProvider()) {
 247             URI uri = file.toUri();
 248             if (uri.getScheme().equals("jar")) {
 249                 String ssp = uri.getSchemeSpecificPart();
 250                 int sep = ssp.lastIndexOf("!");
 251                 if (ssp.startsWith("file:") && sep > 0) {
 252                     enclosingJar = Paths.get(URI.create(ssp.substring(0, sep)));
 253                 }
 254             }
 255         }
 256 
 257         Path nf = normalize(file);
 258         for (Path p : searchPath) {
 259             Path np = normalize(p);
 260             if (np.getFileSystem() == nf.getFileSystem()
 261                     && Files.isDirectory(np)
 262                     && nf.startsWith(np)) {
 263                 return true;
 264             }
 265             if (enclosingJar != null
 266                     && Files.isSameFile(enclosingJar, np)) {
 267                 return true;
 268             }
 269         }
 270 
 271         return false;
 272     }
 273 
 274     /**
 275      * Utility class to help evaluate a path option. Duplicate entries are ignored, jar class paths
 276      * can be expanded.
 277      */
 278     private class SearchPath extends LinkedHashSet<Path> {
 279 
 280         private static final long serialVersionUID = 0;
 281 
 282         private boolean expandJarClassPaths = false;
 283         private final transient Set<Path> canonicalValues = new HashSet<>();
 284 
 285         public SearchPath expandJarClassPaths(boolean x) {
 286             expandJarClassPaths = x;
 287             return this;
 288         }
 289 
 290         /**
 291          * What to use when path element is the empty string
 292          */
 293         private transient Path emptyPathDefault = null;
 294 
 295         public SearchPath emptyPathDefault(Path x) {
 296             emptyPathDefault = x;
 297             return this;
 298         }
 299 
 300         public SearchPath addDirectories(String dirs, boolean warn) {
 301             boolean prev = expandJarClassPaths;
 302             expandJarClassPaths = true;
 303             try {
 304                 if (dirs != null) {
 305                     for (Path dir : getPathEntries(dirs)) {
 306                         addDirectory(dir, warn);
 307                     }
 308                 }
 309                 return this;
 310             } finally {
 311                 expandJarClassPaths = prev;
 312             }
 313         }
 314 
 315         public SearchPath addDirectories(String dirs) {
 316             return addDirectories(dirs, warn);
 317         }
 318 
 319         private void addDirectory(Path dir, boolean warn) {
 320             if (!Files.isDirectory(dir)) {
 321                 if (warn) {
 322                     log.warning(Lint.LintCategory.PATH,
 323                                 Warnings.DirPathElementNotFound(dir));
 324                 }
 325                 return;
 326             }
 327 
 328             try (Stream<Path> s = Files.list(dir)) {
 329                 s.filter(Locations.this::isArchive)
 330                         .forEach(dirEntry -> addFile(dirEntry, warn));
 331             } catch (IOException ignore) {
 332             }
 333         }
 334 
 335         public SearchPath addFiles(String files, boolean warn) {
 336             if (files != null) {
 337                 addFiles(getPathEntries(files, emptyPathDefault), warn);
 338             }
 339             return this;
 340         }
 341 
 342         public SearchPath addFiles(String files) {
 343             return addFiles(files, warn);
 344         }
 345 
 346         public SearchPath addFiles(Iterable<? extends Path> files, boolean warn) {
 347             if (files != null) {
 348                 for (Path file : files) {
 349                     addFile(file, warn);
 350                 }
 351             }
 352             return this;
 353         }
 354 
 355         public SearchPath addFiles(Iterable<? extends Path> files) {
 356             return addFiles(files, warn);
 357         }
 358 
 359         public void addFile(Path file, boolean warn) {
 360             if (contains(file)) {
 361                 // discard duplicates
 362                 return;
 363             }
 364 
 365             if (!fsInfo.exists(file)) {
 366                 /* No such file or directory exists */
 367                 if (warn) {
 368                     log.warning(Lint.LintCategory.PATH,
 369                                 Warnings.PathElementNotFound(file));
 370                 }
 371                 super.add(file);
 372                 return;
 373             }
 374 
 375             Path canonFile = fsInfo.getCanonicalFile(file);
 376             if (canonicalValues.contains(canonFile)) {
 377                 /* Discard duplicates and avoid infinite recursion */
 378                 return;
 379             }
 380 
 381             if (fsInfo.isFile(file)) {
 382                 /* File is an ordinary file. */
 383                 if (   !file.getFileName().toString().endsWith(".jmod")
 384                     && !file.endsWith("modules")) {
 385                     if (!isArchive(file)) {
 386                         /* Not a recognized extension; open it to see if
 387                          it looks like a valid zip file. */
 388                         try {
 389                             FileSystems.newFileSystem(file, (ClassLoader)null).close();
 390                             if (warn) {
 391                                 log.warning(Lint.LintCategory.PATH,
 392                                             Warnings.UnexpectedArchiveFile(file));
 393                             }
 394                         } catch (IOException | ProviderNotFoundException e) {
 395                             // FIXME: include e.getLocalizedMessage in warning
 396                             if (warn) {
 397                                 log.warning(Lint.LintCategory.PATH,
 398                                             Warnings.InvalidArchiveFile(file));
 399                             }
 400                             return;
 401                         }
 402                     } else {
 403                         if (fsInfo.getJarFSProvider() == null) {
 404                             log.error(Errors.NoZipfsForArchive(file));
 405                             return ;
 406                         }
 407                     }
 408                 }
 409             }
 410 
 411             /* Now what we have left is either a directory or a file name
 412              conforming to archive naming convention */
 413             super.add(file);
 414             canonicalValues.add(canonFile);
 415 
 416             if (expandJarClassPaths && fsInfo.isFile(file) && !file.endsWith("modules")) {
 417                 addJarClassPath(file, warn);
 418             }
 419         }
 420 
 421         // Adds referenced classpath elements from a jar's Class-Path
 422         // Manifest entry.  In some future release, we may want to
 423         // update this code to recognize URLs rather than simple
 424         // filenames, but if we do, we should redo all path-related code.
 425         private void addJarClassPath(Path jarFile, boolean warn) {
 426             try {
 427                 for (Path f : fsInfo.getJarClassPath(jarFile)) {
 428                     addFile(f, warn);
 429                 }
 430             } catch (IOException e) {
 431                 log.error(Errors.ErrorReadingFile(jarFile, JavacFileManager.getMessage(e)));
 432             }
 433         }
 434     }
 435 
 436     /**
 437      * Base class for handling support for the representation of Locations.
 438      *
 439      * Locations are (by design) opaque handles that can easily be implemented
 440      * by enums like StandardLocation. Within JavacFileManager, each Location
 441      * has an associated LocationHandler, which provides much of the appropriate
 442      * functionality for the corresponding Location.
 443      *
 444      * @see #initHandlers
 445      * @see #getHandler
 446      */
 447     protected static abstract class LocationHandler {
 448 
 449         /**
 450          * @see JavaFileManager#handleOption
 451          */
 452         abstract boolean handleOption(Option option, String value);
 453 
 454         /**
 455          * @see StandardJavaFileManager#hasLocation
 456          */
 457         boolean isSet() {
 458             return (getPaths() != null);
 459         }
 460 
 461         abstract boolean isExplicit();
 462 
 463         /**
 464          * @see StandardJavaFileManager#getLocation
 465          */
 466         abstract Collection<Path> getPaths();
 467 
 468         /**
 469          * @see StandardJavaFileManager#setLocation
 470          */
 471         abstract void setPaths(Iterable<? extends Path> paths) throws IOException;
 472 
 473         /**
 474          * @see StandardJavaFileManager#setLocationForModule
 475          */
 476         abstract void setPathsForModule(String moduleName, Iterable<? extends Path> paths)
 477                 throws IOException;
 478 
 479         /**
 480          * @see JavaFileManager#getLocationForModule(Location, String)
 481          */
 482         Location getLocationForModule(String moduleName) throws IOException {
 483             return null;
 484         }
 485 
 486         /**
 487          * @see JavaFileManager#getLocationForModule(Location, JavaFileObject, String)
 488          */
 489         Location getLocationForModule(Path file) throws IOException  {
 490             return null;
 491         }
 492 
 493         /**
 494          * @see JavaFileManager#inferModuleName
 495          */
 496         String inferModuleName() {
 497             return null;
 498         }
 499 
 500         /**
 501          * @see JavaFileManager#listLocationsForModules
 502          */
 503         Iterable<Set<Location>> listLocationsForModules() throws IOException {
 504             return null;
 505         }
 506 
 507         /**
 508          * @see JavaFileManager#contains
 509          */
 510         abstract boolean contains(Path file) throws IOException;
 511     }
 512 
 513     /**
 514      * A LocationHandler for a given Location, and associated set of options.
 515      */
 516     private static abstract class BasicLocationHandler extends LocationHandler {
 517 
 518         final Location location;
 519         final Set<Option> options;
 520 
 521         boolean explicit;
 522 
 523         /**
 524          * Create a handler. The location and options provide a way to map from a location or an
 525          * option to the corresponding handler.
 526          *
 527          * @param location the location for which this is the handler
 528          * @param options the options affecting this location
 529          * @see #initHandlers
 530          */
 531         protected BasicLocationHandler(Location location, Option... options) {
 532             this.location = location;
 533             this.options = options.length == 0
 534                     ? EnumSet.noneOf(Option.class)
 535                     : EnumSet.copyOf(Arrays.asList(options));
 536         }
 537 
 538         @Override
 539         void setPathsForModule(String moduleName, Iterable<? extends Path> files) throws IOException {
 540             // should not happen: protected by check in JavacFileManager
 541             throw new UnsupportedOperationException("not supported for " + location);
 542         }
 543 
 544         protected Path checkSingletonDirectory(Iterable<? extends Path> paths) throws IOException {
 545             Iterator<? extends Path> pathIter = paths.iterator();
 546             if (!pathIter.hasNext()) {
 547                 throw new IllegalArgumentException("empty path for directory");
 548             }
 549             Path path = pathIter.next();
 550             if (pathIter.hasNext()) {
 551                 throw new IllegalArgumentException("path too long for directory");
 552             }
 553             checkDirectory(path);
 554             return path;
 555         }
 556 
 557         protected Path checkDirectory(Path path) throws IOException {
 558             Objects.requireNonNull(path);
 559             if (!Files.exists(path)) {
 560                 throw new FileNotFoundException(path + ": does not exist");
 561             }
 562             if (!Files.isDirectory(path)) {
 563                 throw new IOException(path + ": not a directory");
 564             }
 565             return path;
 566         }
 567 
 568         @Override
 569         boolean isExplicit() {
 570             return explicit;
 571         }
 572 
 573     }
 574 
 575     /**
 576      * General purpose implementation for output locations, such as -d/CLASS_OUTPUT and
 577      * -s/SOURCE_OUTPUT. All options are treated as equivalent (i.e. aliases.)
 578      * The value is a single file, possibly null.
 579      */
 580     private class OutputLocationHandler extends BasicLocationHandler {
 581 
 582         private Path outputDir;
 583         private ModuleTable moduleTable;
 584 
 585         OutputLocationHandler(Location location, Option... options) {
 586             super(location, options);
 587         }
 588 
 589         @Override
 590         boolean handleOption(Option option, String value) {
 591             if (!options.contains(option)) {
 592                 return false;
 593             }
 594 
 595             explicit = true;
 596 
 597             // TODO: could/should validate outputDir exists and is a directory
 598             // need to decide how best to report issue for benefit of
 599             // direct API call on JavaFileManager.handleOption(specifies IAE)
 600             // vs. command line decoding.
 601             outputDir = (value == null) ? null : getPath(value);
 602             return true;
 603         }
 604 
 605         @Override
 606         Collection<Path> getPaths() {
 607             return (outputDir == null) ? null : Collections.singleton(outputDir);
 608         }
 609 
 610         @Override
 611         void setPaths(Iterable<? extends Path> paths) throws IOException {
 612             if (paths == null) {
 613                 outputDir = null;
 614             } else {
 615                 explicit = true;
 616                 outputDir = checkSingletonDirectory(paths);
 617             }
 618             moduleTable = null;
 619             listed = false;
 620         }
 621 
 622         @Override
 623         Location getLocationForModule(String name) {
 624             if (moduleTable == null) {
 625                 moduleTable = new ModuleTable();
 626             }
 627             ModuleLocationHandler l = moduleTable.get(name);
 628             if (l == null) {
 629                 Path out = outputDir.resolve(name);
 630                 l = new ModuleLocationHandler(this, location.getName() + "[" + name + "]",
 631                         name, Collections.singletonList(out), true);
 632                 moduleTable.add(l);
 633             }
 634             return l;
 635         }
 636 
 637         @Override
 638         void setPathsForModule(String name, Iterable<? extends Path> paths) throws IOException {
 639             Path out = checkSingletonDirectory(paths);
 640             if (moduleTable == null) {
 641                 moduleTable = new ModuleTable();
 642             }
 643             ModuleLocationHandler l = moduleTable.get(name);
 644             if (l == null) {
 645                 l = new ModuleLocationHandler(this, location.getName() + "[" + name + "]",
 646                         name, Collections.singletonList(out), true);
 647                 moduleTable.add(l);
 648             } else {
 649                 l.searchPath = Collections.singletonList(out);
 650                 moduleTable.updatePaths(l);
 651             }
 652             explicit = true;
 653         }
 654 
 655         @Override
 656         Location getLocationForModule(Path file) {
 657             return (moduleTable == null) ? null : moduleTable.get(file);
 658         }
 659 
 660         private boolean listed;
 661 
 662         @Override
 663         Iterable<Set<Location>> listLocationsForModules() throws IOException {
 664             if (!listed && outputDir != null) {
 665                 try (DirectoryStream<Path> stream = Files.newDirectoryStream(outputDir)) {
 666                     for (Path p : stream) {
 667                         getLocationForModule(p.getFileName().toString());
 668                     }
 669                 }
 670                 listed = true;
 671             }
 672 
 673             if (moduleTable == null || moduleTable.isEmpty())
 674                 return Collections.emptySet();
 675 
 676             return Collections.singleton(moduleTable.locations());
 677         }
 678 
 679         @Override
 680         boolean contains(Path file) throws IOException {
 681             if (moduleTable != null) {
 682                 return moduleTable.contains(file);
 683             } else {
 684                 return (outputDir) != null && normalize(file).startsWith(normalize(outputDir));
 685             }
 686         }
 687     }
 688 
 689     /**
 690      * General purpose implementation for search path locations,
 691      * such as -sourcepath/SOURCE_PATH and -processorPath/ANNOTATION_PROCESSOR_PATH.
 692      * All options are treated as equivalent (i.e. aliases.)
 693      * The value is an ordered set of files and/or directories.
 694      */
 695     private class SimpleLocationHandler extends BasicLocationHandler {
 696 
 697         protected Collection<Path> searchPath;
 698 
 699         SimpleLocationHandler(Location location, Option... options) {
 700             super(location, options);
 701         }
 702 
 703         @Override
 704         boolean handleOption(Option option, String value) {
 705             if (!options.contains(option)) {
 706                 return false;
 707             }
 708 
 709             explicit = true;
 710 
 711             searchPath = value == null ? null
 712                     : Collections.unmodifiableCollection(createPath().addFiles(value));
 713             return true;
 714         }
 715 
 716         @Override
 717         Collection<Path> getPaths() {
 718             return searchPath;
 719         }
 720 
 721         @Override
 722         void setPaths(Iterable<? extends Path> files) {
 723             SearchPath p;
 724             if (files == null) {
 725                 p = computePath(null);
 726             } else {
 727                 explicit = true;
 728                 p = createPath().addFiles(files);
 729             }
 730             searchPath = Collections.unmodifiableCollection(p);
 731         }
 732 
 733         protected SearchPath computePath(String value) {
 734             return createPath().addFiles(value);
 735         }
 736 
 737         protected SearchPath createPath() {
 738             return new SearchPath();
 739         }
 740 
 741         @Override
 742         boolean contains(Path file) throws IOException {
 743             return Locations.this.contains(searchPath, file);
 744         }
 745     }
 746 
 747     /**
 748      * Subtype of SimpleLocationHandler for -classpath/CLASS_PATH.
 749      * If no value is given, a default is provided, based on system properties and other values.
 750      */
 751     private class ClassPathLocationHandler extends SimpleLocationHandler {
 752 
 753         ClassPathLocationHandler() {
 754             super(StandardLocation.CLASS_PATH, Option.CLASS_PATH);
 755         }
 756 
 757         @Override
 758         Collection<Path> getPaths() {
 759             lazy();
 760             return searchPath;
 761         }
 762 
 763         @Override
 764         protected SearchPath computePath(String value) {
 765             String cp = value;
 766 
 767             // CLASSPATH environment variable when run from `javac'.
 768             if (cp == null) {
 769                 cp = System.getProperty("env.class.path");
 770             }
 771 
 772             // If invoked via a java VM (not the javac launcher), use the
 773             // platform class path
 774             if (cp == null && System.getProperty("application.home") == null) {
 775                 cp = System.getProperty("java.class.path");
 776             }
 777 
 778             // Default to current working directory.
 779             if (cp == null) {
 780                 cp = ".";
 781             }
 782 
 783             return createPath().addFiles(cp);
 784         }
 785 
 786         @Override
 787         protected SearchPath createPath() {
 788             return new SearchPath()
 789                     .expandJarClassPaths(true) // Only search user jars for Class-Paths
 790                     .emptyPathDefault(getPath("."));  // Empty path elt ==> current directory
 791         }
 792 
 793         private void lazy() {
 794             if (searchPath == null) {
 795                 setPaths(null);
 796             }
 797         }
 798     }
 799 
 800     /**
 801      * Custom subtype of LocationHandler for PLATFORM_CLASS_PATH.
 802      * Various options are supported for different components of the
 803      * platform class path.
 804      * Setting a value with setLocation overrides all existing option values.
 805      * Setting any option overrides any value set with setLocation, and
 806      * reverts to using default values for options that have not been set.
 807      * Setting -bootclasspath or -Xbootclasspath overrides any existing
 808      * value for -Xbootclasspath/p: and -Xbootclasspath/a:.
 809      */
 810     private class BootClassPathLocationHandler extends BasicLocationHandler {
 811 
 812         private Collection<Path> searchPath;
 813         final Map<Option, String> optionValues = new EnumMap<>(Option.class);
 814 
 815         /**
 816          * Is the bootclasspath the default?
 817          */
 818         private boolean isDefault;
 819 
 820         BootClassPathLocationHandler() {
 821             super(StandardLocation.PLATFORM_CLASS_PATH,
 822                     Option.BOOT_CLASS_PATH, Option.XBOOTCLASSPATH,
 823                     Option.XBOOTCLASSPATH_PREPEND,
 824                     Option.XBOOTCLASSPATH_APPEND,
 825                     Option.ENDORSEDDIRS, Option.DJAVA_ENDORSED_DIRS,
 826                     Option.EXTDIRS, Option.DJAVA_EXT_DIRS);
 827         }
 828 
 829         boolean isDefault() {
 830             lazy();
 831             return isDefault;
 832         }
 833 
 834         @Override
 835         boolean handleOption(Option option, String value) {
 836             if (!options.contains(option)) {
 837                 return false;
 838             }
 839 
 840             explicit = true;
 841 
 842             option = canonicalize(option);
 843             optionValues.put(option, value);
 844             if (option == BOOT_CLASS_PATH) {
 845                 optionValues.remove(XBOOTCLASSPATH_PREPEND);
 846                 optionValues.remove(XBOOTCLASSPATH_APPEND);
 847             }
 848             searchPath = null;  // reset to "uninitialized"
 849             return true;
 850         }
 851         // where
 852         // TODO: would be better if option aliasing was handled at a higher
 853         // level
 854         private Option canonicalize(Option option) {
 855             switch (option) {
 856                 case XBOOTCLASSPATH:
 857                     return Option.BOOT_CLASS_PATH;
 858                 case DJAVA_ENDORSED_DIRS:
 859                     return Option.ENDORSEDDIRS;
 860                 case DJAVA_EXT_DIRS:
 861                     return Option.EXTDIRS;
 862                 default:
 863                     return option;
 864             }
 865         }
 866 
 867         @Override
 868         Collection<Path> getPaths() {
 869             lazy();
 870             return searchPath;
 871         }
 872 
 873         @Override
 874         void setPaths(Iterable<? extends Path> files) {
 875             if (files == null) {
 876                 searchPath = null;  // reset to "uninitialized"
 877             } else {
 878                 isDefault = false;
 879                 explicit = true;
 880                 SearchPath p = new SearchPath().addFiles(files, false);
 881                 searchPath = Collections.unmodifiableCollection(p);
 882                 optionValues.clear();
 883             }
 884         }
 885 
 886         SearchPath computePath() throws IOException {
 887             SearchPath path = new SearchPath();
 888 
 889             String bootclasspathOpt = optionValues.get(BOOT_CLASS_PATH);
 890             String endorseddirsOpt = optionValues.get(ENDORSEDDIRS);
 891             String extdirsOpt = optionValues.get(EXTDIRS);
 892             String xbootclasspathPrependOpt = optionValues.get(XBOOTCLASSPATH_PREPEND);
 893             String xbootclasspathAppendOpt = optionValues.get(XBOOTCLASSPATH_APPEND);
 894             path.addFiles(xbootclasspathPrependOpt);
 895 
 896             if (endorseddirsOpt != null) {
 897                 path.addDirectories(endorseddirsOpt);
 898             } else {
 899                 path.addDirectories(System.getProperty("java.endorsed.dirs"), false);
 900             }
 901 
 902             if (bootclasspathOpt != null) {
 903                 path.addFiles(bootclasspathOpt);
 904             } else {
 905                 // Standard system classes for this compiler's release.
 906                 Collection<Path> systemClasses = systemClasses();
 907                 if (systemClasses != null) {
 908                     path.addFiles(systemClasses, false);
 909                 } else {
 910                     // fallback to the value of sun.boot.class.path
 911                     String files = System.getProperty("sun.boot.class.path");
 912                     path.addFiles(files, false);
 913                 }
 914             }
 915 
 916             path.addFiles(xbootclasspathAppendOpt);
 917 
 918             // Strictly speaking, standard extensions are not bootstrap
 919             // classes, but we treat them identically, so we'll pretend
 920             // that they are.
 921             if (extdirsOpt != null) {
 922                 path.addDirectories(extdirsOpt);
 923             } else {
 924                 // Add lib/jfxrt.jar to the search path
 925                Path jfxrt = javaHome.resolve("lib/jfxrt.jar");
 926                 if (Files.exists(jfxrt)) {
 927                     path.addFile(jfxrt, false);
 928                 }
 929                 path.addDirectories(System.getProperty("java.ext.dirs"), false);
 930             }
 931 
 932             isDefault =
 933                        (xbootclasspathPrependOpt == null)
 934                     && (bootclasspathOpt == null)
 935                     && (xbootclasspathAppendOpt == null);
 936 
 937             return path;
 938         }
 939 
 940         /**
 941          * Return a collection of files containing system classes.
 942          * Returns {@code null} if not running on a modular image.
 943          *
 944          * @throws UncheckedIOException if an I/O errors occurs
 945          */
 946         private Collection<Path> systemClasses() throws IOException {
 947             // Return "modules" jimage file if available
 948             if (Files.isRegularFile(thisSystemModules)) {
 949                 return Collections.singleton(thisSystemModules);
 950             }
 951 
 952             // Exploded module image
 953             Path modules = javaHome.resolve("modules");
 954             if (Files.isDirectory(modules.resolve("java.base"))) {
 955                 try (Stream<Path> listedModules = Files.list(modules)) {
 956                     return listedModules.collect(Collectors.toList());
 957                 }
 958             }
 959 
 960             // not a modular image that we know about
 961             return null;
 962         }
 963 
 964         private void lazy() {
 965             if (searchPath == null) {
 966                 try {
 967                     searchPath = Collections.unmodifiableCollection(computePath());
 968                 } catch (IOException e) {
 969                     // TODO: need better handling here, e.g. javac Abort?
 970                     throw new UncheckedIOException(e);
 971                 }
 972             }
 973         }
 974 
 975         @Override
 976         boolean contains(Path file) throws IOException {
 977             return Locations.this.contains(searchPath, file);
 978         }
 979     }
 980 
 981     /**
 982      * A LocationHandler to represent modules found from a module-oriented
 983      * location such as MODULE_SOURCE_PATH, UPGRADE_MODULE_PATH,
 984      * SYSTEM_MODULES and MODULE_PATH.
 985      *
 986      * The Location can be specified to accept overriding classes from the
 987      * {@code --patch-module <module>=<path> } parameter.
 988      */
 989     private class ModuleLocationHandler extends LocationHandler implements Location {
 990         private final LocationHandler parent;
 991         private final String name;
 992         private final String moduleName;
 993         private final boolean output;
 994         boolean explicit;
 995         Collection<Path> searchPath;
 996 
 997         ModuleLocationHandler(LocationHandler parent, String name, String moduleName,
 998                 Collection<Path> searchPath, boolean output) {
 999             this.parent = parent;
1000             this.name = name;
1001             this.moduleName = moduleName;
1002             this.searchPath = searchPath;
1003             this.output = output;
1004         }
1005 
1006         @Override @DefinedBy(Api.COMPILER)
1007         public String getName() {
1008             return name;
1009         }
1010 
1011         @Override @DefinedBy(Api.COMPILER)
1012         public boolean isOutputLocation() {
1013             return output;
1014         }
1015 
1016         @Override // defined by LocationHandler
1017         boolean handleOption(Option option, String value) {
1018             throw new UnsupportedOperationException();
1019         }
1020 
1021         @Override // defined by LocationHandler
1022         Collection<Path> getPaths() {
1023             return Collections.unmodifiableCollection(searchPath);
1024         }
1025 
1026         @Override
1027         boolean isExplicit() {
1028             return true;
1029         }
1030 
1031         @Override // defined by LocationHandler
1032         void setPaths(Iterable<? extends Path> paths) throws IOException {
1033             // defer to the parent to determine if this is acceptable
1034             parent.setPathsForModule(moduleName, paths);
1035         }
1036 
1037         @Override // defined by LocationHandler
1038         void setPathsForModule(String moduleName, Iterable<? extends Path> paths) {
1039             throw new UnsupportedOperationException("not supported for " + name);
1040         }
1041 
1042         @Override // defined by LocationHandler
1043         String inferModuleName() {
1044             return moduleName;
1045         }
1046 
1047         @Override
1048         boolean contains(Path file) throws IOException {
1049             return Locations.this.contains(searchPath, file);
1050         }
1051 
1052         @Override
1053         public String toString() {
1054             return name;
1055         }
1056     }
1057 
1058     /**
1059      * A table of module location handlers, indexed by name and path.
1060      */
1061     private class ModuleTable {
1062         private final Map<String, ModuleLocationHandler> nameMap = new LinkedHashMap<>();
1063         private final Map<Path, ModuleLocationHandler> pathMap = new LinkedHashMap<>();
1064 
1065         void add(ModuleLocationHandler h) {
1066             nameMap.put(h.moduleName, h);
1067             for (Path p : h.searchPath) {
1068                 pathMap.put(normalize(p), h);
1069             }
1070         }
1071 
1072         void updatePaths(ModuleLocationHandler h) {
1073             // use iterator, to be able to remove old entries
1074             for (Iterator<Map.Entry<Path, ModuleLocationHandler>> iter = pathMap.entrySet().iterator();
1075                     iter.hasNext(); ) {
1076                 Map.Entry<Path, ModuleLocationHandler> e = iter.next();
1077                 if (e.getValue() == h) {
1078                     iter.remove();
1079                 }
1080             }
1081             for (Path p : h.searchPath) {
1082                 pathMap.put(normalize(p), h);
1083             }
1084         }
1085 
1086         ModuleLocationHandler get(String name) {
1087             return nameMap.get(name);
1088         }
1089 
1090         ModuleLocationHandler get(Path path) {
1091             while (path != null) {
1092                 ModuleLocationHandler l = pathMap.get(path);
1093 
1094                 if (l != null)
1095                     return l;
1096 
1097                 path = path.getParent();
1098             }
1099 
1100             return null;
1101         }
1102 
1103         void clear() {
1104             nameMap.clear();
1105             pathMap.clear();
1106         }
1107 
1108         boolean isEmpty() {
1109             return nameMap.isEmpty();
1110         }
1111 
1112         boolean contains(Path file) throws IOException {
1113             return Locations.this.contains(pathMap.keySet(), file);
1114         }
1115 
1116         Set<Location> locations() {
1117             return Collections.unmodifiableSet(nameMap.values().stream().collect(Collectors.toSet()));
1118         }
1119 
1120         Set<Location> explicitLocations() {
1121             return Collections.unmodifiableSet(nameMap.entrySet()
1122                                                       .stream()
1123                                                       .filter(e -> e.getValue().explicit)
1124                                                       .map(e -> e.getValue())
1125                                                       .collect(Collectors.toSet()));
1126         }
1127     }
1128 
1129     /**
1130      * A LocationHandler for simple module-oriented search paths,
1131      * like UPGRADE_MODULE_PATH and MODULE_PATH.
1132      */
1133     private class ModulePathLocationHandler extends SimpleLocationHandler {
1134         private ModuleTable moduleTable;
1135 
1136         ModulePathLocationHandler(Location location, Option... options) {
1137             super(location, options);
1138         }
1139 
1140         @Override
1141         public boolean handleOption(Option option, String value) {
1142             if (!options.contains(option)) {
1143                 return false;
1144             }
1145             setPaths(value == null ? null : getPathEntries(value));
1146             return true;
1147         }
1148 
1149         @Override
1150         public Location getLocationForModule(String moduleName) {
1151             initModuleLocations();
1152             return moduleTable.get(moduleName);
1153         }
1154 
1155         @Override
1156         public Location getLocationForModule(Path file) {
1157             initModuleLocations();
1158             return moduleTable.get(file);
1159         }
1160 
1161         @Override
1162         Iterable<Set<Location>> listLocationsForModules() {
1163             Set<Location> explicitLocations = moduleTable != null ?
1164                     moduleTable.explicitLocations() : Collections.emptySet();
1165             Iterable<Set<Location>> explicitLocationsList = !explicitLocations.isEmpty()
1166                     ? Collections.singletonList(explicitLocations)
1167                     : Collections.emptyList();
1168 
1169             if (searchPath == null)
1170                 return explicitLocationsList;
1171 
1172             Iterable<Set<Location>> searchPathLocations =
1173                     () -> new ModulePathIterator();
1174             return () -> Iterators.createCompoundIterator(Arrays.asList(explicitLocationsList,
1175                                                                         searchPathLocations),
1176                                                           Iterable::iterator);
1177         }
1178 
1179         @Override
1180         boolean contains(Path file) throws IOException {
1181             if (moduleTable == null) {
1182                 initModuleLocations();
1183             }
1184             return moduleTable.contains(file);
1185         }
1186 
1187         @Override
1188         void setPaths(Iterable<? extends Path> paths) {
1189             if (paths != null) {
1190                 for (Path p: paths) {
1191                     checkValidModulePathEntry(p);
1192                 }
1193             }
1194             super.setPaths(paths);
1195             moduleTable = null;
1196         }
1197 
1198         @Override
1199         void setPathsForModule(String name, Iterable<? extends Path> paths) throws IOException {
1200             List<Path> checkedPaths = checkPaths(paths);
1201             // how far should we go to validate the paths provide a module?
1202             // e.g. contain module-info with the correct name?
1203             initModuleLocations();
1204             ModuleLocationHandler l = moduleTable.get(name);
1205             if (l == null) {
1206                 l = new ModuleLocationHandler(this, location.getName() + "[" + name + "]",
1207                         name, checkedPaths, true);
1208                 moduleTable.add(l);
1209            } else {
1210                 l.searchPath = checkedPaths;
1211                 moduleTable.updatePaths(l);
1212             }
1213             l.explicit = true;
1214             explicit = true;
1215         }
1216 
1217         private List<Path> checkPaths(Iterable<? extends Path> paths) throws IOException {
1218             Objects.requireNonNull(paths);
1219             List<Path> validPaths = new ArrayList<>();
1220             for (Path p : paths) {
1221                 validPaths.add(checkDirectory(p));
1222             }
1223             return validPaths;
1224         }
1225 
1226         private void initModuleLocations() {
1227             if (moduleTable != null) {
1228                 return;
1229             }
1230 
1231             moduleTable = new ModuleTable();
1232 
1233             for (Set<Location> set : listLocationsForModules()) {
1234                 for (Location locn : set) {
1235                     if (locn instanceof ModuleLocationHandler) {
1236                         ModuleLocationHandler l = (ModuleLocationHandler) locn;
1237                         if (!moduleTable.nameMap.containsKey(l.moduleName)) {
1238                             moduleTable.add(l);
1239                         }
1240                     }
1241                 }
1242             }
1243         }
1244 
1245         private void checkValidModulePathEntry(Path p) {
1246             if (!Files.exists(p)) {
1247                 // warning may be generated later
1248                 return;
1249             }
1250 
1251             if (Files.isDirectory(p)) {
1252                 // either an exploded module or a directory of modules
1253                 return;
1254             }
1255 
1256             String name = p.getFileName().toString();
1257             int lastDot = name.lastIndexOf(".");
1258             if (lastDot > 0) {
1259                 switch (name.substring(lastDot)) {
1260                     case ".jar":
1261                     case ".jmod":
1262                         return;
1263                 }
1264             }
1265             throw new IllegalArgumentException(p.toString());
1266         }
1267 
1268         class ModulePathIterator implements Iterator<Set<Location>> {
1269             Iterator<Path> pathIter = searchPath.iterator();
1270             int pathIndex = 0;
1271             Set<Location> next = null;
1272 
1273             @Override
1274             public boolean hasNext() {
1275                 if (next != null)
1276                     return true;
1277 
1278                 while (next == null) {
1279                     if (pathIter.hasNext()) {
1280                         Path path = pathIter.next();
1281                         if (Files.isDirectory(path)) {
1282                             next = scanDirectory(path);
1283                         } else {
1284                             next = scanFile(path);
1285                         }
1286                         pathIndex++;
1287                     } else
1288                         return false;
1289                 }
1290                 return true;
1291             }
1292 
1293             @Override
1294             public Set<Location> next() {
1295                 hasNext();
1296                 if (next != null) {
1297                     Set<Location> result = next;
1298                     next = null;
1299                     return result;
1300                 }
1301                 throw new NoSuchElementException();
1302             }
1303 
1304             private Set<Location> scanDirectory(Path path) {
1305                 Set<Path> paths = new LinkedHashSet<>();
1306                 Path moduleInfoClass = null;
1307                 try (DirectoryStream<Path> stream = Files.newDirectoryStream(path)) {
1308                     for (Path entry: stream) {
1309                         if (entry.endsWith("module-info.class")) {
1310                             moduleInfoClass = entry;
1311                             break;  // no need to continue scanning
1312                         }
1313                         paths.add(entry);
1314                     }
1315                 } catch (DirectoryIteratorException | IOException ignore) {
1316                     log.error(Errors.LocnCantReadDirectory(path));
1317                     return Collections.emptySet();
1318                 }
1319 
1320                 if (moduleInfoClass != null) {
1321                     // It's an exploded module directly on the module path.
1322                     // We can't infer module name from the directory name, so have to
1323                     // read module-info.class.
1324                     try {
1325                         String moduleName = readModuleName(moduleInfoClass);
1326                         String name = location.getName()
1327                                 + "[" + pathIndex + ":" + moduleName + "]";
1328                         ModuleLocationHandler l = new ModuleLocationHandler(
1329                                 ModulePathLocationHandler.this, name, moduleName,
1330                                 Collections.singletonList(path), false);
1331                         return Collections.singleton(l);
1332                     } catch (ModuleNameReader.BadClassFile e) {
1333                         log.error(Errors.LocnBadModuleInfo(path));
1334                         return Collections.emptySet();
1335                     } catch (IOException e) {
1336                         log.error(Errors.LocnCantReadFile(path));
1337                         return Collections.emptySet();
1338                     }
1339                 }
1340 
1341                 // A directory of modules
1342                 Set<Location> result = new LinkedHashSet<>();
1343                 int index = 0;
1344                 for (Path entry : paths) {
1345                     Pair<String,Path> module = inferModuleName(entry);
1346                     if (module == null) {
1347                         // diagnostic reported if necessary; skip to next
1348                         continue;
1349                     }
1350                     String moduleName = module.fst;
1351                     Path modulePath = module.snd;
1352                     String name = location.getName()
1353                             + "[" + pathIndex + "." + (index++) + ":" + moduleName + "]";
1354                     ModuleLocationHandler l = new ModuleLocationHandler(
1355                             ModulePathLocationHandler.this, name, moduleName,
1356                             Collections.singletonList(modulePath), false);
1357                     result.add(l);
1358                 }
1359                 return result;
1360             }
1361 
1362             private Set<Location> scanFile(Path path) {
1363                 Pair<String,Path> module = inferModuleName(path);
1364                 if (module == null) {
1365                     // diagnostic reported if necessary
1366                     return Collections.emptySet();
1367                 }
1368                 String moduleName = module.fst;
1369                 Path modulePath = module.snd;
1370                 String name = location.getName()
1371                         + "[" + pathIndex + ":" + moduleName + "]";
1372                 ModuleLocationHandler l = new ModuleLocationHandler(
1373                         ModulePathLocationHandler.this, name, moduleName,
1374                         Collections.singletonList(modulePath), false);
1375                 return Collections.singleton(l);
1376             }
1377 
1378             private Pair<String,Path> inferModuleName(Path p) {
1379                 if (Files.isDirectory(p)) {
1380                     if (Files.exists(p.resolve("module-info.class")) ||
1381                         Files.exists(p.resolve("module-info.sig"))) {
1382                         String name = p.getFileName().toString();
1383                         if (SourceVersion.isName(name))
1384                             return new Pair<>(name, p);
1385                     }
1386                     return null;
1387                 }
1388 
1389                 if (p.getFileName().toString().endsWith(".jar") && fsInfo.exists(p)) {
1390                     FileSystemProvider jarFSProvider = fsInfo.getJarFSProvider();
1391                     if (jarFSProvider == null) {
1392                         log.error(Errors.NoZipfsForArchive(p));
1393                         return null;
1394                     }
1395                     try (FileSystem fs = jarFSProvider.newFileSystem(p, fsEnv)) {
1396                         Path moduleInfoClass = fs.getPath("module-info.class");
1397                         if (Files.exists(moduleInfoClass)) {
1398                             String moduleName = readModuleName(moduleInfoClass);
1399                             return new Pair<>(moduleName, p);
1400                         }
1401                         Path mf = fs.getPath("META-INF/MANIFEST.MF");
1402                         if (Files.exists(mf)) {
1403                             try (InputStream in = Files.newInputStream(mf)) {
1404                                 Manifest man = new Manifest(in);
1405                                 Attributes attrs = man.getMainAttributes();
1406                                 if (attrs != null) {
1407                                     String moduleName = attrs.getValue(new Attributes.Name("Automatic-Module-Name"));
1408                                     if (moduleName != null) {
1409                                         if (isModuleName(moduleName)) {
1410                                             return new Pair<>(moduleName, p);
1411                                         } else {
1412                                             log.error(Errors.LocnCantGetModuleNameForJar(p));
1413                                             return null;
1414                                         }
1415                                     }
1416                                 }
1417                             }
1418                         }
1419                     } catch (ModuleNameReader.BadClassFile e) {
1420                         log.error(Errors.LocnBadModuleInfo(p));
1421                         return null;
1422                     } catch (IOException e) {
1423                         log.error(Errors.LocnCantReadFile(p));
1424                         return null;
1425                     }
1426 
1427                     //automatic module:
1428                     String fn = p.getFileName().toString();
1429                     //from ModulePath.deriveModuleDescriptor:
1430 
1431                     // drop .jar
1432                     String mn = fn.substring(0, fn.length()-4);
1433 
1434                     // find first occurrence of -${NUMBER}. or -${NUMBER}$
1435                     Matcher matcher = Pattern.compile("-(\\d+(\\.|$))").matcher(mn);
1436                     if (matcher.find()) {
1437                         int start = matcher.start();
1438 
1439                         mn = mn.substring(0, start);
1440                     }
1441 
1442                     // finally clean up the module name
1443                     mn =  mn.replaceAll("[^A-Za-z0-9]", ".")  // replace non-alphanumeric
1444                             .replaceAll("(\\.)(\\1)+", ".")   // collapse repeating dots
1445                             .replaceAll("^\\.", "")           // drop leading dots
1446                             .replaceAll("\\.$", "");          // drop trailing dots
1447 
1448 
1449                     if (!mn.isEmpty()) {
1450                         return new Pair<>(mn, p);
1451                     }
1452 
1453                     log.error(Errors.LocnCantGetModuleNameForJar(p));
1454                     return null;
1455                 }
1456 
1457                 if (p.getFileName().toString().endsWith(".jmod")) {
1458                     try {
1459                         // check if the JMOD file is valid
1460                         JmodFile.checkMagic(p);
1461 
1462                         // No JMOD file system.  Use JarFileSystem to
1463                         // workaround for now
1464                         FileSystem fs = fileSystems.get(p);
1465                         if (fs == null) {
1466                             FileSystemProvider jarFSProvider = fsInfo.getJarFSProvider();
1467                             if (jarFSProvider == null) {
1468                                 log.error(Errors.LocnCantReadFile(p));
1469                                 return null;
1470                             }
1471                             fs = jarFSProvider.newFileSystem(p, Collections.emptyMap());
1472                             try {
1473                                 Path moduleInfoClass = fs.getPath("classes/module-info.class");
1474                                 String moduleName = readModuleName(moduleInfoClass);
1475                                 Path modulePath = fs.getPath("classes");
1476                                 fileSystems.put(p, fs);
1477                                 closeables.add(fs);
1478                                 fs = null; // prevent fs being closed in the finally clause
1479                                 return new Pair<>(moduleName, modulePath);
1480                             } finally {
1481                                 if (fs != null)
1482                                     fs.close();
1483                             }
1484                         }
1485                     } catch (ModuleNameReader.BadClassFile e) {
1486                         log.error(Errors.LocnBadModuleInfo(p));
1487                     } catch (IOException e) {
1488                         log.error(Errors.LocnCantReadFile(p));
1489                         return null;
1490                     }
1491                 }
1492 
1493                 if (warn && false) {  // temp disable, when enabled, massage examples.not-yet.txt suitably.
1494                     log.warning(Warnings.LocnUnknownFileOnModulePath(p));
1495                 }
1496                 return null;
1497             }
1498 
1499             private String readModuleName(Path path) throws IOException, ModuleNameReader.BadClassFile {
1500                 if (moduleNameReader == null)
1501                     moduleNameReader = new ModuleNameReader();
1502                 return moduleNameReader.readModuleName(path);
1503             }
1504         }
1505 
1506         //from jdk.internal.module.Checks:
1507         /**
1508          * Returns {@code true} if the given name is a legal module name.
1509          */
1510         private boolean isModuleName(String name) {
1511             int next;
1512             int off = 0;
1513             while ((next = name.indexOf('.', off)) != -1) {
1514                 String id = name.substring(off, next);
1515                 if (!SourceVersion.isName(id))
1516                     return false;
1517                 off = next+1;
1518             }
1519             String last = name.substring(off);
1520             return SourceVersion.isName(last);
1521         }
1522     }
1523 
1524     private class ModuleSourcePathLocationHandler extends BasicLocationHandler {
1525         private ModuleTable moduleTable;
1526         private List<Path> paths;
1527 
1528         ModuleSourcePathLocationHandler() {
1529             super(StandardLocation.MODULE_SOURCE_PATH,
1530                     Option.MODULE_SOURCE_PATH);
1531         }
1532 
1533         @Override
1534         boolean handleOption(Option option, String value) {
1535             explicit = true;
1536             init(value);
1537             return true;
1538         }
1539 
1540         /**
1541          * Initializes the module table, based on a string containing the composition
1542          * of a series of command-line options.
1543          * At most one pattern to initialize a series of modules can be given.
1544          * At most one module-specific search path per module can be given.
1545          *
1546          * @param value a series of values, separated by NUL.
1547          */
1548         void init(String value) {
1549             Pattern moduleSpecificForm = Pattern.compile("([\\p{Alnum}$_.]+)=(.*)");
1550             List<String> pathsForModules = new ArrayList<>();
1551             String modulePattern = null;
1552             for (String v : value.split("\0")) {
1553                 if (moduleSpecificForm.matcher(v).matches()) {
1554                     pathsForModules.add(v);
1555                 } else {
1556                     modulePattern = v;
1557                 }
1558             }
1559             // set the general module pattern first, if given
1560             if (modulePattern != null) {
1561                 initFromPattern(modulePattern);
1562             }
1563             pathsForModules.forEach(this::initForModule);
1564         }
1565 
1566         /**
1567          * Initializes a module-specific override, using {@code setPathsForModule}.
1568          *
1569          * @param value a string of the form: module-name=search-path
1570          */
1571         void initForModule(String value) {
1572             int eq = value.indexOf('=');
1573             String name = value.substring(0, eq);
1574             List<Path> paths = new ArrayList<>();
1575             for (String v : value.substring(eq + 1).split(File.pathSeparator)) {
1576                 try {
1577                     paths.add(Paths.get(v));
1578                 } catch (InvalidPathException e) {
1579                     throw new IllegalArgumentException("invalid path: " + v, e);
1580                 }
1581             }
1582             try {
1583                 setPathsForModule(name, paths);
1584             } catch (IOException e) {
1585                 e.printStackTrace();
1586                 throw new IllegalArgumentException("cannot set path for module " + name, e);
1587             }
1588         }
1589 
1590         /**
1591          * Initializes the module table based on a custom option syntax.
1592          *
1593          * @param value the value such as may be given to a --module-source-path option
1594          */
1595         void initFromPattern(String value) {
1596             Collection<String> segments = new ArrayList<>();
1597             for (String s: value.split(File.pathSeparator)) {
1598                 expandBraces(s, segments);
1599             }
1600 
1601             Map<String, List<Path>> map = new LinkedHashMap<>();
1602             List<Path> noSuffixPaths = new ArrayList<>();
1603             boolean anySuffix = false;
1604             final String MARKER = "*";
1605             for (String seg: segments) {
1606                 int markStart = seg.indexOf(MARKER);
1607                 if (markStart == -1) {
1608                     Path p = getPath(seg);
1609                     add(map, p, null);
1610                     noSuffixPaths.add(p);
1611                 } else {
1612                     if (markStart == 0 || !isSeparator(seg.charAt(markStart - 1))) {
1613                         throw new IllegalArgumentException("illegal use of " + MARKER + " in " + seg);
1614                     }
1615                     Path prefix = getPath(seg.substring(0, markStart - 1));
1616                     Path suffix;
1617                     int markEnd = markStart + MARKER.length();
1618                     if (markEnd == seg.length()) {
1619                         suffix = null;
1620                     } else if (!isSeparator(seg.charAt(markEnd))
1621                             || seg.indexOf(MARKER, markEnd) != -1) {
1622                         throw new IllegalArgumentException("illegal use of " + MARKER + " in " + seg);
1623                     } else {
1624                         suffix = getPath(seg.substring(markEnd + 1));
1625                         anySuffix = true;
1626                     }
1627                     add(map, prefix, suffix);
1628                     if (suffix == null) {
1629                         noSuffixPaths.add(prefix);
1630                     }
1631                 }
1632             }
1633 
1634             initModuleTable(map);
1635             paths = anySuffix ? null : noSuffixPaths;
1636         }
1637 
1638         private void initModuleTable(Map<String, List<Path>> map) {
1639             moduleTable = new ModuleTable();
1640             map.forEach((modName, modPath) -> {
1641                 boolean hasModuleInfo = modPath.stream().anyMatch(checkModuleInfo);
1642                 if (hasModuleInfo) {
1643                     String locnName = location.getName() + "[" + modName + "]";
1644                     ModuleLocationHandler l = new ModuleLocationHandler(this, locnName, modName,
1645                             modPath, false);
1646                     moduleTable.add(l);
1647                 }
1648             });
1649         }
1650         //where:
1651             private final Predicate<Path> checkModuleInfo =
1652                     p -> Files.exists(p.resolve("module-info.java"));
1653 
1654 
1655         private boolean isSeparator(char ch) {
1656             // allow both separators on Windows
1657             return (ch == File.separatorChar) || (ch == '/');
1658         }
1659 
1660         void add(Map<String, List<Path>> map, Path prefix, Path suffix) {
1661             if (!Files.isDirectory(prefix)) {
1662                 if (warn) {
1663                     Warning key = Files.exists(prefix)
1664                             ? Warnings.DirPathElementNotDirectory(prefix)
1665                             : Warnings.DirPathElementNotFound(prefix);
1666                     log.warning(Lint.LintCategory.PATH, key);
1667                 }
1668                 return;
1669             }
1670             try (DirectoryStream<Path> stream = Files.newDirectoryStream(prefix, path -> Files.isDirectory(path))) {
1671                 for (Path entry: stream) {
1672                     Path path = (suffix == null) ? entry : entry.resolve(suffix);
1673                     if (Files.isDirectory(path)) {
1674                         String name = entry.getFileName().toString();
1675                         List<Path> paths = map.get(name);
1676                         if (paths == null)
1677                             map.put(name, paths = new ArrayList<>());
1678                         paths.add(path);
1679                     }
1680                 }
1681             } catch (IOException e) {
1682                 // TODO? What to do?
1683                 System.err.println(e);
1684             }
1685         }
1686 
1687         private void expandBraces(String value, Collection<String> results) {
1688             int depth = 0;
1689             int start = -1;
1690             String prefix = null;
1691             String suffix = null;
1692             for (int i = 0; i < value.length(); i++) {
1693                 switch (value.charAt(i)) {
1694                     case '{':
1695                         depth++;
1696                         if (depth == 1) {
1697                             prefix = value.substring(0, i);
1698                             suffix = value.substring(getMatchingBrace(value, i) + 1);
1699                             start = i + 1;
1700                         }
1701                         break;
1702 
1703                     case ',':
1704                         if (depth == 1) {
1705                             String elem = value.substring(start, i);
1706                             expandBraces(prefix + elem + suffix, results);
1707                             start = i + 1;
1708                         }
1709                         break;
1710 
1711                     case '}':
1712                         switch (depth) {
1713                             case 0:
1714                                 throw new IllegalArgumentException("mismatched braces");
1715 
1716                             case 1:
1717                                 String elem = value.substring(start, i);
1718                                 expandBraces(prefix + elem + suffix, results);
1719                                 return;
1720 
1721                             default:
1722                                 depth--;
1723                         }
1724                         break;
1725                 }
1726             }
1727             if (depth > 0)
1728                 throw new IllegalArgumentException("mismatched braces");
1729             results.add(value);
1730         }
1731 
1732         int getMatchingBrace(String value, int offset) {
1733             int depth = 1;
1734             for (int i = offset + 1; i < value.length(); i++) {
1735                 switch (value.charAt(i)) {
1736                     case '{':
1737                         depth++;
1738                         break;
1739 
1740                     case '}':
1741                         if (--depth == 0)
1742                             return i;
1743                         break;
1744                 }
1745             }
1746             throw new IllegalArgumentException("mismatched braces");
1747         }
1748 
1749         @Override
1750         boolean isSet() {
1751             return (moduleTable != null);
1752         }
1753 
1754         @Override
1755         Collection<Path> getPaths() {
1756             if (paths == null) {
1757                 // This may occur for a complex setting with --module-source-path option
1758                 // i.e. one that cannot be represented by a simple series of paths.
1759                 throw new IllegalStateException("paths not available");
1760             }
1761             return paths;
1762         }
1763 
1764         @Override
1765         void setPaths(Iterable<? extends Path> files) throws IOException {
1766             Map<String, List<Path>> map = new LinkedHashMap<>();
1767             List<Path> newPaths = new ArrayList<>();
1768             for (Path file : files) {
1769                 add(map, file, null);
1770                 newPaths.add(file);
1771             }
1772 
1773             initModuleTable(map);
1774             explicit = true;
1775             paths = Collections.unmodifiableList(newPaths);
1776         }
1777 
1778         @Override
1779         void setPathsForModule(String name, Iterable<? extends Path> paths) throws IOException {
1780             List<Path> validPaths = checkPaths(paths);
1781 
1782             if (moduleTable == null)
1783                 moduleTable = new ModuleTable();
1784 
1785             ModuleLocationHandler l = moduleTable.get(name);
1786             if (l == null) {
1787                 l = new ModuleLocationHandler(this,
1788                         location.getName() + "[" + name + "]",
1789                         name,
1790                         validPaths,
1791                         true);
1792                 moduleTable.add(l);
1793            } else {
1794                 l.searchPath = validPaths;
1795                 moduleTable.updatePaths(l);
1796             }
1797             explicit = true;
1798         }
1799 
1800         private List<Path> checkPaths(Iterable<? extends Path> paths) throws IOException {
1801             Objects.requireNonNull(paths);
1802             List<Path> validPaths = new ArrayList<>();
1803             for (Path p : paths) {
1804                 validPaths.add(checkDirectory(p));
1805             }
1806             return validPaths;
1807         }
1808 
1809         @Override
1810         Location getLocationForModule(String name) {
1811             return (moduleTable == null) ? null : moduleTable.get(name);
1812         }
1813 
1814         @Override
1815         Location getLocationForModule(Path file) {
1816             return (moduleTable == null) ? null : moduleTable.get(file);
1817         }
1818 
1819         @Override
1820         Iterable<Set<Location>> listLocationsForModules() {
1821             if (moduleTable == null)
1822                 return Collections.emptySet();
1823 
1824             return Collections.singleton(moduleTable.locations());
1825         }
1826 
1827         @Override
1828         boolean contains(Path file) throws IOException {
1829             return (moduleTable == null) ? false : moduleTable.contains(file);
1830         }
1831 
1832     }
1833 
1834     private class SystemModulesLocationHandler extends BasicLocationHandler {
1835         private Path systemJavaHome;
1836         private Path modules;
1837         private ModuleTable moduleTable;
1838 
1839         SystemModulesLocationHandler() {
1840             super(StandardLocation.SYSTEM_MODULES, Option.SYSTEM);
1841             systemJavaHome = Locations.javaHome;
1842         }
1843 
1844         @Override
1845         boolean handleOption(Option option, String value) {
1846             if (!options.contains(option)) {
1847                 return false;
1848             }
1849 
1850             explicit = true;
1851 
1852             if (value == null) {
1853                 systemJavaHome = Locations.javaHome;
1854             } else if (value.equals("none")) {
1855                 systemJavaHome = null;
1856             } else {
1857                 update(getPath(value));
1858             }
1859 
1860             modules = null;
1861             return true;
1862         }
1863 
1864         @Override
1865         Collection<Path> getPaths() {
1866             return (systemJavaHome == null) ? null : Collections.singleton(systemJavaHome);
1867         }
1868 
1869         @Override
1870         void setPaths(Iterable<? extends Path> files) throws IOException {
1871             if (files == null) {
1872                 systemJavaHome = null;
1873             } else {
1874                 explicit = true;
1875 
1876                 Path dir = checkSingletonDirectory(files);
1877                 update(dir);
1878             }
1879         }
1880 
1881         @Override
1882         void setPathsForModule(String name, Iterable<? extends Path> paths) throws IOException {
1883             List<Path> checkedPaths = checkPaths(paths);
1884             initSystemModules();
1885             ModuleLocationHandler l = moduleTable.get(name);
1886             if (l == null) {
1887                 l = new ModuleLocationHandler(this,
1888                         location.getName() + "[" + name + "]",
1889                         name,
1890                         checkedPaths,
1891                         true);
1892                 moduleTable.add(l);
1893            } else {
1894                 l.searchPath = checkedPaths;
1895                 moduleTable.updatePaths(l);
1896             }
1897             explicit = true;
1898         }
1899 
1900         private List<Path> checkPaths(Iterable<? extends Path> paths) throws IOException {
1901             Objects.requireNonNull(paths);
1902             List<Path> validPaths = new ArrayList<>();
1903             for (Path p : paths) {
1904                 validPaths.add(checkDirectory(p));
1905             }
1906             return validPaths;
1907         }
1908 
1909         private void update(Path p) {
1910             if (!isCurrentPlatform(p) && !Files.exists(p.resolve("lib").resolve("jrt-fs.jar")) &&
1911                     !Files.exists(systemJavaHome.resolve("modules")))
1912                 throw new IllegalArgumentException(p.toString());
1913             systemJavaHome = p;
1914             modules = null;
1915         }
1916 
1917         private boolean isCurrentPlatform(Path p) {
1918             try {
1919                 return Files.isSameFile(p, Locations.javaHome);
1920             } catch (IOException ex) {
1921                 throw new IllegalArgumentException(p.toString(), ex);
1922             }
1923         }
1924 
1925         @Override
1926         Location getLocationForModule(String name) throws IOException {
1927             initSystemModules();
1928             return moduleTable.get(name);
1929         }
1930 
1931         @Override
1932         Location getLocationForModule(Path file) throws IOException {
1933             initSystemModules();
1934             return moduleTable.get(file);
1935         }
1936 
1937         @Override
1938         Iterable<Set<Location>> listLocationsForModules() throws IOException {
1939             initSystemModules();
1940             return Collections.singleton(moduleTable.locations());
1941         }
1942 
1943         @Override
1944         boolean contains(Path file) throws IOException {
1945             initSystemModules();
1946             return moduleTable.contains(file);
1947         }
1948 
1949         private void initSystemModules() throws IOException {
1950             if (moduleTable != null)
1951                 return;
1952 
1953             if (systemJavaHome == null) {
1954                 moduleTable = new ModuleTable();
1955                 return;
1956             }
1957 
1958             if (modules == null) {
1959                 try {
1960                     URI jrtURI = URI.create("jrt:/");
1961                     FileSystem jrtfs;
1962 
1963                     if (isCurrentPlatform(systemJavaHome)) {
1964                         jrtfs = FileSystems.getFileSystem(jrtURI);
1965                     } else {
1966                         try {
1967                             Map<String, String> attrMap =
1968                                     Collections.singletonMap("java.home", systemJavaHome.toString());
1969                             jrtfs = FileSystems.newFileSystem(jrtURI, attrMap);
1970                         } catch (ProviderNotFoundException ex) {
1971                             URL javaHomeURL = systemJavaHome.resolve("jrt-fs.jar").toUri().toURL();
1972                             ClassLoader currentLoader = Locations.class.getClassLoader();
1973                             URLClassLoader fsLoader =
1974                                     new URLClassLoader(new URL[] {javaHomeURL}, currentLoader);
1975 
1976                             jrtfs = FileSystems.newFileSystem(jrtURI, Collections.emptyMap(), fsLoader);
1977 
1978                             closeables.add(fsLoader);
1979                         }
1980 
1981                         closeables.add(jrtfs);
1982                     }
1983 
1984                     modules = jrtfs.getPath("/modules");
1985                 } catch (FileSystemNotFoundException | ProviderNotFoundException e) {
1986                     modules = systemJavaHome.resolve("modules");
1987                     if (!Files.exists(modules))
1988                         throw new IOException("can't find system classes", e);
1989                 }
1990             }
1991 
1992             moduleTable = new ModuleTable();
1993             try (DirectoryStream<Path> stream = Files.newDirectoryStream(modules, Files::isDirectory)) {
1994                 for (Path entry : stream) {
1995                     String moduleName = entry.getFileName().toString();
1996                     String name = location.getName() + "[" + moduleName + "]";
1997                     ModuleLocationHandler h = new ModuleLocationHandler(this,
1998                             name, moduleName, Collections.singletonList(entry), false);
1999                     moduleTable.add(h);
2000                 }
2001             }
2002         }
2003     }
2004 
2005     private class PatchModulesLocationHandler extends BasicLocationHandler {
2006         private final ModuleTable moduleTable = new ModuleTable();
2007 
2008         PatchModulesLocationHandler() {
2009             super(StandardLocation.PATCH_MODULE_PATH, Option.PATCH_MODULE);
2010         }
2011 
2012         @Override
2013         boolean handleOption(Option option, String value) {
2014             if (!options.contains(option)) {
2015                 return false;
2016             }
2017 
2018             explicit = true;
2019 
2020             moduleTable.clear();
2021 
2022             // Allow an extended syntax for --patch-module consisting of a series
2023             // of values separated by NULL characters. This is to facilitate
2024             // supporting deferred file manager options on the command line.
2025             // See Option.PATCH_MODULE for the code that composes these multiple
2026             // values.
2027             for (String v : value.split("\0")) {
2028                 int eq = v.indexOf('=');
2029                 if (eq > 0) {
2030                     String moduleName = v.substring(0, eq);
2031                     SearchPath mPatchPath = new SearchPath()
2032                             .addFiles(v.substring(eq + 1));
2033                     String name = location.getName() + "[" + moduleName + "]";
2034                     ModuleLocationHandler h = new ModuleLocationHandler(this, name,
2035                             moduleName, mPatchPath, false);
2036                     moduleTable.add(h);
2037                 } else {
2038                     // Should not be able to get here;
2039                     // this should be caught and handled in Option.PATCH_MODULE
2040                     log.error(Errors.LocnInvalidArgForXpatch(value));
2041                 }
2042             }
2043 
2044             return true;
2045         }
2046 
2047         @Override
2048         boolean isSet() {
2049             return !moduleTable.isEmpty();
2050         }
2051 
2052         @Override
2053         Collection<Path> getPaths() {
2054             throw new UnsupportedOperationException();
2055         }
2056 
2057         @Override
2058         void setPaths(Iterable<? extends Path> files) throws IOException {
2059             throw new UnsupportedOperationException();
2060         }
2061 
2062         @Override // defined by LocationHandler
2063         void setPathsForModule(String moduleName, Iterable<? extends Path> files) throws IOException {
2064             throw new UnsupportedOperationException(); // not yet
2065         }
2066 
2067         @Override
2068         Location getLocationForModule(String name) throws IOException {
2069             return moduleTable.get(name);
2070         }
2071 
2072         @Override
2073         Location getLocationForModule(Path file) throws IOException {
2074             return moduleTable.get(file);
2075         }
2076 
2077         @Override
2078         Iterable<Set<Location>> listLocationsForModules() throws IOException {
2079             return Collections.singleton(moduleTable.locations());
2080         }
2081 
2082         @Override
2083         boolean contains(Path file) throws IOException {
2084             return moduleTable.contains(file);
2085         }
2086     }
2087 
2088     Map<Location, LocationHandler> handlersForLocation;
2089     Map<Option, LocationHandler> handlersForOption;
2090 
2091     void initHandlers() {
2092         handlersForLocation = new HashMap<>();
2093         handlersForOption = new EnumMap<>(Option.class);
2094 
2095         BasicLocationHandler[] handlers = {
2096             new BootClassPathLocationHandler(),
2097             new ClassPathLocationHandler(),
2098             new SimpleLocationHandler(StandardLocation.SOURCE_PATH, Option.SOURCE_PATH),
2099             new SimpleLocationHandler(StandardLocation.ANNOTATION_PROCESSOR_PATH, Option.PROCESSOR_PATH),
2100             new SimpleLocationHandler(StandardLocation.ANNOTATION_PROCESSOR_MODULE_PATH, Option.PROCESSOR_MODULE_PATH),
2101             new OutputLocationHandler(StandardLocation.CLASS_OUTPUT, Option.D),
2102             new OutputLocationHandler(StandardLocation.SOURCE_OUTPUT, Option.S),
2103             new OutputLocationHandler(StandardLocation.NATIVE_HEADER_OUTPUT, Option.H),
2104             new ModuleSourcePathLocationHandler(),
2105             new PatchModulesLocationHandler(),
2106             new ModulePathLocationHandler(StandardLocation.UPGRADE_MODULE_PATH, Option.UPGRADE_MODULE_PATH),
2107             new ModulePathLocationHandler(StandardLocation.MODULE_PATH, Option.MODULE_PATH),
2108             new SystemModulesLocationHandler(),
2109         };
2110 
2111         for (BasicLocationHandler h : handlers) {
2112             handlersForLocation.put(h.location, h);
2113             for (Option o : h.options) {
2114                 handlersForOption.put(o, h);
2115             }
2116         }
2117     }
2118 
2119     boolean handleOption(Option option, String value) {
2120         LocationHandler h = handlersForOption.get(option);
2121         return (h == null ? false : h.handleOption(option, value));
2122     }
2123 
2124     boolean hasLocation(Location location) {
2125         LocationHandler h = getHandler(location);
2126         return (h == null ? false : h.isSet());
2127     }
2128 
2129     boolean hasExplicitLocation(Location location) {
2130         LocationHandler h = getHandler(location);
2131         return (h == null ? false : h.isExplicit());
2132     }
2133 
2134     Collection<Path> getLocation(Location location) {
2135         LocationHandler h = getHandler(location);
2136         return (h == null ? null : h.getPaths());
2137     }
2138 
2139     Path getOutputLocation(Location location) {
2140         if (!location.isOutputLocation()) {
2141             throw new IllegalArgumentException();
2142         }
2143         LocationHandler h = getHandler(location);
2144         return ((OutputLocationHandler) h).outputDir;
2145     }
2146 
2147     void setLocation(Location location, Iterable<? extends Path> files) throws IOException {
2148         LocationHandler h = getHandler(location);
2149         if (h == null) {
2150             if (location.isOutputLocation()) {
2151                 h = new OutputLocationHandler(location);
2152             } else {
2153                 h = new SimpleLocationHandler(location);
2154             }
2155             handlersForLocation.put(location, h);
2156         }
2157         h.setPaths(files);
2158     }
2159 
2160     Location getLocationForModule(Location location, String name) throws IOException {
2161         LocationHandler h = getHandler(location);
2162         return (h == null ? null : h.getLocationForModule(name));
2163     }
2164 
2165     Location getLocationForModule(Location location, Path file) throws IOException {
2166         LocationHandler h = getHandler(location);
2167         return (h == null ? null : h.getLocationForModule(file));
2168     }
2169 
2170     void setLocationForModule(Location location, String moduleName,
2171             Iterable<? extends Path> files) throws IOException {
2172         LocationHandler h = getHandler(location);
2173         if (h == null) {
2174             if (location.isOutputLocation()) {
2175                 h = new OutputLocationHandler(location);
2176             } else {
2177                 h = new ModulePathLocationHandler(location);
2178             }
2179             handlersForLocation.put(location, h);
2180         }
2181         h.setPathsForModule(moduleName, files);
2182     }
2183 
2184     String inferModuleName(Location location) {
2185         LocationHandler h = getHandler(location);
2186         return (h == null ? null : h.inferModuleName());
2187     }
2188 
2189     Iterable<Set<Location>> listLocationsForModules(Location location) throws IOException {
2190         LocationHandler h = getHandler(location);
2191         return (h == null ? null : h.listLocationsForModules());
2192     }
2193 
2194     boolean contains(Location location, Path file) throws IOException {
2195         LocationHandler h = getHandler(location);
2196         if (h == null)
2197             throw new IllegalArgumentException("unknown location");
2198         return h.contains(file);
2199     }
2200 
2201     protected LocationHandler getHandler(Location location) {
2202         Objects.requireNonNull(location);
2203         return (location instanceof LocationHandler)
2204                 ? (LocationHandler) location
2205                 : handlersForLocation.get(location);
2206     }
2207 
2208     /**
2209      * Is this the name of an archive file?
2210      */
2211     private boolean isArchive(Path file) {
2212         String n = StringUtils.toLowerCase(file.getFileName().toString());
2213         return fsInfo.isFile(file)
2214                 && (n.endsWith(".jar") || n.endsWith(".zip"));
2215     }
2216 
2217     static Path normalize(Path p) {
2218         try {
2219             return p.toRealPath();
2220         } catch (IOException e) {
2221             return p.toAbsolutePath().normalize();
2222         }
2223     }
2224 }