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