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