Locations.java revision 3980:f34b5b81ef55
14Srgrimes/*
24Srgrimes * Copyright (c) 2003, 2017, Oracle and/or its affiliates. All rights reserved.
34Srgrimes * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
44Srgrimes *
54Srgrimes * This code is free software; you can redistribute it and/or modify it
64Srgrimes * under the terms of the GNU General Public License version 2 only, as
74Srgrimes * published by the Free Software Foundation.  Oracle designates this
84Srgrimes * particular file as subject to the "Classpath" exception as provided
94Srgrimes * by Oracle in the LICENSE file that accompanied this code.
104Srgrimes *
114Srgrimes * This code is distributed in the hope that it will be useful, but WITHOUT
124Srgrimes * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
134Srgrimes * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
144Srgrimes * version 2 for more details (a copy is included in the LICENSE file that
154Srgrimes * accompanied this code).
164Srgrimes *
174Srgrimes * You should have received a copy of the GNU General Public License version
184Srgrimes * 2 along with this work; if not, write to the Free Software Foundation,
194Srgrimes * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
204Srgrimes *
214Srgrimes * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
224Srgrimes * or visit www.oracle.com if you need additional information or have any
234Srgrimes * questions.
244Srgrimes */
254Srgrimes
264Srgrimespackage com.sun.tools.javac.file;
274Srgrimes
284Srgrimesimport java.io.Closeable;
294Srgrimesimport java.io.File;
304Srgrimesimport java.io.FileNotFoundException;
314Srgrimesimport java.io.IOException;
324Srgrimesimport java.io.UncheckedIOException;
334Srgrimesimport java.net.URI;
344Srgrimesimport java.net.URL;
354Srgrimesimport java.net.URLClassLoader;
36619Srgrimesimport java.nio.file.DirectoryIteratorException;
3750477Speterimport java.nio.file.DirectoryStream;
384Srgrimesimport java.nio.file.FileSystem;
394Srgrimesimport java.nio.file.FileSystemNotFoundException;
403185Ssosimport java.nio.file.FileSystems;
4119173Sbdeimport java.nio.file.Files;
4219173Sbdeimport java.nio.file.InvalidPathException;
4319173Sbdeimport java.nio.file.Path;
4419173Sbdeimport java.nio.file.Paths;
453185Ssosimport java.nio.file.ProviderNotFoundException;
463185Ssosimport java.nio.file.spi.FileSystemProvider;
473185Ssosimport java.util.ArrayList;
483185Ssosimport java.util.Arrays;
492913Sacheimport java.util.Collection;
502913Sacheimport java.util.Collections;
5116299Spstimport java.util.EnumMap;
5233929Sphkimport java.util.EnumSet;
5313228Swollmanimport java.util.HashMap;
542056Swollmanimport java.util.HashSet;
552056Swollmanimport java.util.Iterator;
562056Swollmanimport java.util.LinkedHashMap;
572056Swollmanimport java.util.LinkedHashSet;
5831253Sbdeimport java.util.List;
5931253Sbdeimport java.util.Map;
6031253Sbdeimport java.util.Objects;
6115508Sbdeimport java.util.NoSuchElementException;
6249558Sphkimport java.util.Set;
6315508Sbdeimport java.util.regex.Matcher;
644180Sbdeimport java.util.regex.Pattern;
6515508Sbdeimport java.util.stream.Collectors;
6615508Sbdeimport java.util.stream.Stream;
6730805Sbde
682056Swollmanimport javax.lang.model.SourceVersion;
6926309Speterimport javax.tools.JavaFileManager;
7028551Sbdeimport javax.tools.JavaFileManager.Location;
7132054Sphkimport javax.tools.JavaFileObject;
7247588Sbdeimport javax.tools.StandardJavaFileManager;
7330805Sbdeimport javax.tools.StandardJavaFileManager.PathFactory;
7430805Sbdeimport javax.tools.StandardLocation;
7530805Sbde
7628921Sfsmpimport com.sun.tools.javac.code.Lint;
7726949Sfsmpimport com.sun.tools.javac.code.Lint.LintCategory;
7828921Sfsmpimport com.sun.tools.javac.main.Option;
7932054Sphkimport com.sun.tools.javac.resources.CompilerProperties.Errors;
8015508Sbdeimport com.sun.tools.javac.resources.CompilerProperties.Warnings;
812056Swollmanimport com.sun.tools.javac.util.DefinedBy;
822056Swollmanimport com.sun.tools.javac.util.DefinedBy.Api;
8347642Sdfrimport com.sun.tools.javac.util.JDK9Wrappers;
842056Swollmanimport com.sun.tools.javac.util.ListBuffer;
854Srgrimesimport com.sun.tools.javac.util.Log;
8645897Speterimport com.sun.tools.javac.jvm.ModuleNameReader;
8728487Sfsmpimport com.sun.tools.javac.util.Pair;
8850823Smdoddimport com.sun.tools.javac.util.StringUtils;
8950823Smdodd
9050823Smdoddimport static javax.tools.StandardLocation.PLATFORM_CLASS_PATH;
9150823Smdodd
9250823Smdoddimport static com.sun.tools.javac.main.Option.BOOT_CLASS_PATH;
9328921Sfsmpimport static com.sun.tools.javac.main.Option.DJAVA_ENDORSED_DIRS;
9429000Sfsmpimport static com.sun.tools.javac.main.Option.DJAVA_EXT_DIRS;
9529000Sfsmpimport static com.sun.tools.javac.main.Option.ENDORSEDDIRS;
9634058Steggeimport static com.sun.tools.javac.main.Option.EXTDIRS;
9734571Steggeimport static com.sun.tools.javac.main.Option.XBOOTCLASSPATH;
9834571Steggeimport static com.sun.tools.javac.main.Option.XBOOTCLASSPATH_APPEND;
9934058Steggeimport static com.sun.tools.javac.main.Option.XBOOTCLASSPATH_PREPEND;
10034058Stegge
10134571Stegge/**
10234571Stegge * This class converts command line arguments, environment variables and system properties (in
10334571Stegge * File.pathSeparator-separated String form) into a boot class path, user class path, and source
10428921Sfsmp * path (in {@code Collection<String>} form).
10528921Sfsmp *
1062873Sbde * <p>
1072873Sbde * <b>This is NOT part of any supported API. If you write code that depends on this, you do so at
1082873Sbde * your own risk. This code and its internal interfaces are subject to change or deletion without
1092873Sbde * notice.</b>
1102873Sbde */
1112913Sachepublic class Locations {
1122873Sbde
11315508Sbde    /**
1144Srgrimes     * The log to use for warning output
1154180Sbde     */
1164180Sbde    private Log log;
1174180Sbde
1184180Sbde    /**
1194180Sbde     * Access to (possibly cached) file info
1204180Sbde     */
1214180Sbde    private FSInfo fsInfo;
1224180Sbde
1234180Sbde    /**
1244180Sbde     * Whether to warn about non-existent path elements
12517236Sjoerg     */
12617231Sjoerg    private boolean warn;
12733690Sphk
1284180Sbde    private ModuleNameReader moduleNameReader;
12917231Sjoerg
1304180Sbde    private PathFactory pathFactory = Paths::get;
13141787Smckay
13247588Sbde    static final Path javaHome = FileSystems.getDefault().getPath(System.getProperty("java.home"));
13315045Sache    static final Path thisSystemModules = javaHome.resolve("lib").resolve("modules");
13446847Speter
13532052Sphk    Map<Path, FileSystem> fileSystems = new LinkedHashMap<>();
13632052Sphk    List<Closeable> closeables = new ArrayList<>();
13733690Sphk    private Map<String,String> fsEnv = Collections.emptyMap();
13833690Sphk
13933690Sphk    Locations() {
14032052Sphk        initHandlers();
14132052Sphk    }
14232005Sphk
14347592Sphk    Path getPath(String first, String... more) {
14441787Smckay        try {
1451390Ssos            return pathFactory.getPath(first, more);
1464180Sbde        } catch (InvalidPathException ipe) {
1475291Sbde            throw new IllegalArgumentException(ipe);
1484180Sbde        }
14919173Sbde    }
15033690Sphk
15133690Sphk    public void close() throws IOException {
15233690Sphk        ListBuffer<IOException> list = new ListBuffer<>();
1534180Sbde        closeables.forEach(closeable -> {
1544180Sbde            try {
1554180Sbde                closeable.close();
1564180Sbde            } catch (IOException ex) {
1574180Sbde                list.add(ex);
1584180Sbde            }
15919173Sbde        });
16019173Sbde        if (list.nonEmpty()) {
1614180Sbde            IOException ex = new IOException();
16215345Snate            for (IOException e: list)
16333690Sphk                ex.addSuppressed(e);
16417231Sjoerg            throw ex;
16517231Sjoerg        }
16617236Sjoerg    }
16717236Sjoerg
16817236Sjoerg    void update(Log log, boolean warn, FSInfo fsInfo) {
16917236Sjoerg        this.log = log;
17017231Sjoerg        this.warn = warn;
17117231Sjoerg        this.fsInfo = fsInfo;
17217231Sjoerg    }
17319173Sbde
17433690Sphk    void setPathFactory(PathFactory f) {
1754180Sbde        pathFactory = f;
17636719Sphk    }
17736719Sphk
17821783Sbde    boolean isDefaultBootClassPath() {
17917353Sbde        BootClassPathLocationHandler h
18040610Sphk                = (BootClassPathLocationHandler) getHandler(PLATFORM_CLASS_PATH);
18133690Sphk        return h.isDefault();
18236741Sphk    }
18336198Sphk
18433690Sphk    /**
18533690Sphk     * Split a search path into its elements. Empty path elements will be ignored.
18633690Sphk     *
18733690Sphk     * @param searchPath The search path to be split
18833690Sphk     * @return The elements of the path
18940610Sphk     */
19033690Sphk    private Iterable<Path> getPathEntries(String searchPath) {
19140610Sphk        return getPathEntries(searchPath, null);
19233690Sphk    }
19336741Sphk
19436198Sphk    /**
19533690Sphk     * Split a search path into its elements. If emptyPathDefault is not null, all empty elements in the
19633690Sphk     * path, including empty elements at either end of the path, will be replaced with the value of
19733690Sphk     * emptyPathDefault.
19833690Sphk     *
19933690Sphk     * @param searchPath The search path to be split
20040610Sphk     * @param emptyPathDefault The value to substitute for empty path elements, or null, to ignore
20133690Sphk     * empty path elements
20212724Sphk     * @return The elements of the path
2033185Ssos     */
2042074Swollman    private Iterable<Path> getPathEntries(String searchPath, Path emptyPathDefault) {
20539503Sbde        ListBuffer<Path> entries = new ListBuffer<>();
20647588Sbde        for (String s: searchPath.split(Pattern.quote(File.pathSeparator), -1)) {
20747588Sbde            if (s.isEmpty()) {
20839503Sbde                if (emptyPathDefault != null) {
20947588Sbde                    entries.add(emptyPathDefault);
21039503Sbde                }
21139503Sbde            } else {
21239503Sbde                try {
21347588Sbde                    entries.add(getPath(s));
21447588Sbde                } catch (IllegalArgumentException e) {
21539503Sbde                    if (warn) {
2161549Srgrimes                        log.warning(LintCategory.PATH, "invalid.path", s);
2171442Ssos                    }
21817236Sjoerg                }
21917231Sjoerg            }
2208448Sbde        }
2211442Ssos        return entries;
22217236Sjoerg    }
22317231Sjoerg
2244180Sbde    public void setMultiReleaseValue(String multiReleaseValue) {
2254180Sbde        fsEnv = Collections.singletonMap("multi-release", multiReleaseValue);
22633309Sbde    }
2271549Srgrimes
2288448Sbde    /**
2291390Ssos     * Utility class to help evaluate a path option. Duplicate entries are ignored, jar class paths
2301442Ssos     * can be expanded.
23117236Sjoerg     */
23217231Sjoerg    private class SearchPath extends LinkedHashSet<Path> {
23348889Sbde
23448889Sbde        private static final long serialVersionUID = 0;
23548889Sbde
2364180Sbde        private boolean expandJarClassPaths = false;
2374180Sbde        private final Set<Path> canonicalValues = new HashSet<>();
2384180Sbde
2394180Sbde        public SearchPath expandJarClassPaths(boolean x) {
24029000Sfsmp            expandJarClassPaths = x;
2411442Ssos            return this;
24217231Sjoerg        }
24348889Sbde
2441442Ssos        /**
24517236Sjoerg         * What to use when path element is the empty string
24617231Sjoerg         */
2474180Sbde        private Path emptyPathDefault = null;
2484180Sbde
24948889Sbde        public SearchPath emptyPathDefault(Path x) {
25048889Sbde            emptyPathDefault = x;
25148889Sbde            return this;
2525291Sbde        }
2534180Sbde
2544180Sbde        public SearchPath addDirectories(String dirs, boolean warn) {
2554180Sbde            boolean prev = expandJarClassPaths;
2564180Sbde            expandJarClassPaths = true;
25729000Sfsmp            try {
2584180Sbde                if (dirs != null) {
25917231Sjoerg                    for (Path dir : getPathEntries(dirs)) {
26017231Sjoerg                        addDirectory(dir, warn);
26148889Sbde                    }
26248889Sbde                }
2631442Ssos                return this;
2641442Ssos            } finally {
2651442Ssos                expandJarClassPaths = prev;
26650823Smdodd            }
26750823Smdodd        }
26850823Smdodd
26950823Smdodd        public SearchPath addDirectories(String dirs) {
27050823Smdodd            return addDirectories(dirs, warn);
2711390Ssos        }
2721390Ssos
27317231Sjoerg        private void addDirectory(Path dir, boolean warn) {
27417236Sjoerg            if (!Files.isDirectory(dir)) {
27517231Sjoerg                if (warn) {
2761390Ssos                    log.warning(Lint.LintCategory.PATH,
2774180Sbde                            "dir.path.element.not.found", dir);
2781390Ssos                }
27917231Sjoerg                return;
28017231Sjoerg            }
28117231Sjoerg
28217231Sjoerg            try (Stream<Path> s = Files.list(dir)) {
28317231Sjoerg                s.filter(Locations.this::isArchive)
28417231Sjoerg                        .forEach(dirEntry -> addFile(dirEntry, warn));
28517236Sjoerg            } catch (IOException ignore) {
28617236Sjoerg            }
28717236Sjoerg        }
28817231Sjoerg
28917236Sjoerg        public SearchPath addFiles(String files, boolean warn) {
29017236Sjoerg            if (files != null) {
29117236Sjoerg                addFiles(getPathEntries(files, emptyPathDefault), warn);
29217236Sjoerg            }
29317236Sjoerg            return this;
29417236Sjoerg        }
29517236Sjoerg
29617236Sjoerg        public SearchPath addFiles(String files) {
29717236Sjoerg            return addFiles(files, warn);
29817236Sjoerg        }
29917236Sjoerg
30017236Sjoerg        public SearchPath addFiles(Iterable<? extends Path> files, boolean warn) {
30117236Sjoerg            if (files != null) {
30217236Sjoerg                for (Path file : files) {
30317231Sjoerg                    addFile(file, warn);
3041442Ssos                }
30517231Sjoerg            }
30617231Sjoerg            return this;
3071390Ssos        }
3081390Ssos
3091390Ssos        public SearchPath addFiles(Iterable<? extends Path> files) {
3101390Ssos            return addFiles(files, warn);
3111390Ssos        }
31217231Sjoerg
31317231Sjoerg        public void addFile(Path file, boolean warn) {
31417231Sjoerg            if (contains(file)) {
31517231Sjoerg                // discard duplicates
31617236Sjoerg                return;
31717236Sjoerg            }
31817236Sjoerg
31917236Sjoerg            if (!fsInfo.exists(file)) {
32017236Sjoerg                /* No such file or directory exists */
32117236Sjoerg                if (warn) {
32217236Sjoerg                    log.warning(Lint.LintCategory.PATH,
32317236Sjoerg                            "path.element.not.found", file);
32417236Sjoerg                }
32517236Sjoerg                super.add(file);
32617231Sjoerg                return;
3271390Ssos            }
3281390Ssos
3291390Ssos            Path canonFile = fsInfo.getCanonicalFile(file);
3301390Ssos            if (canonicalValues.contains(canonFile)) {
3311390Ssos                /* Discard duplicates and avoid infinite recursion */
33217231Sjoerg                return;
33317231Sjoerg            }
33417236Sjoerg
33517236Sjoerg            if (fsInfo.isFile(file)) {
33617236Sjoerg                /* File is an ordinary file. */
33717231Sjoerg                if (   !file.getFileName().toString().endsWith(".jmod")
33817236Sjoerg                    && !file.endsWith("modules")) {
33917236Sjoerg                    if (!isArchive(file)) {
34017236Sjoerg                        /* Not a recognized extension; open it to see if
34117236Sjoerg                         it looks like a valid zip file. */
34217236Sjoerg                        try {
34317236Sjoerg                            FileSystems.newFileSystem(file, null).close();
34417236Sjoerg                            if (warn) {
34517231Sjoerg                                log.warning(Lint.LintCategory.PATH,
34617231Sjoerg                                        "unexpected.archive.file", file);
3471390Ssos                            }
3481390Ssos                        } catch (IOException | ProviderNotFoundException e) {
3491390Ssos                            // FIXME: include e.getLocalizedMessage in warning
3501390Ssos                            if (warn) {
3511390Ssos                                log.warning(Lint.LintCategory.PATH,
35217231Sjoerg                                        "invalid.archive.file", file);
35317231Sjoerg                            }
35417231Sjoerg                            return;
35517231Sjoerg                        }
35617236Sjoerg                    } else {
35717231Sjoerg                        if (fsInfo.getJarFSProvider() == null) {
3581390Ssos                            log.error(Errors.NoZipfsForArchive(file));
3591390Ssos                            return ;
3603185Ssos                        }
3613185Ssos                    }
3623185Ssos                }
3633185Ssos            }
3643185Ssos
3653185Ssos            /* Now what we have left is either a directory or a file name
3663185Ssos             conforming to archive naming convention */
3673185Ssos            super.add(file);
3683185Ssos            canonicalValues.add(canonFile);
3693185Ssos
3703185Ssos            if (expandJarClassPaths && fsInfo.isFile(file) && !file.endsWith("modules")) {
3713185Ssos                addJarClassPath(file, warn);
37224676Smckay            }
37324676Smckay        }
37424676Smckay
37524676Smckay        // Adds referenced classpath elements from a jar's Class-Path
37624676Smckay        // Manifest entry.  In some future release, we may want to
37724676Smckay        // update this code to recognize URLs rather than simple
37824676Smckay        // filenames, but if we do, we should redo all path-related code.
37924676Smckay        private void addJarClassPath(Path jarFile, boolean warn) {
3803185Ssos            try {
38112724Sphk                for (Path f : fsInfo.getJarClassPath(jarFile)) {
3823185Ssos                    addFile(f, warn);
3833185Ssos                }
38424676Smckay            } catch (IOException e) {
3853185Ssos                log.error("error.reading.file", jarFile, JavacFileManager.getMessage(e));
3863185Ssos            }
3871390Ssos        }
38818297Sbde    }
3895291Sbde
39018297Sbde    /**
39118297Sbde     * Base class for handling support for the representation of Locations.
39218297Sbde     *
3933185Ssos     * Locations are (by design) opaque handles that can easily be implemented
3945291Sbde     * by enums like StandardLocation. Within JavacFileManager, each Location
3955291Sbde     * has an associated LocationHandler, which provides much of the appropriate
3965291Sbde     * functionality for the corresponding Location.
3975291Sbde     *
3983185Ssos     * @see #initHandlers
39918297Sbde     * @see #getHandler
4003185Ssos     */
4011390Ssos    protected abstract class LocationHandler {
40210268Sbde
4031390Ssos        /**
40416428Sbde         * @see JavaFileManager#handleOption
4051390Ssos         */
4061390Ssos        abstract boolean handleOption(Option option, String value);
40716428Sbde
40829000Sfsmp        /**
40916428Sbde         * @see StandardJavaFileManager#hasLocation
41016428Sbde         */
41119173Sbde        boolean isSet() {
41216428Sbde            return (getPaths() != null);
4131390Ssos        }
4141390Ssos
41516428Sbde        /**
41628921Sfsmp         * @see StandardJavaFileManager#getLocation
41716428Sbde         */
4181390Ssos        abstract Collection<Path> getPaths();
4191390Ssos
4201390Ssos        /**
4212017Swollman         * @see StandardJavaFileManager#setLocation
4221390Ssos         */
42315508Sbde        abstract void setPaths(Iterable<? extends Path> files) throws IOException;
4241390Ssos
4251390Ssos        /**
4261390Ssos         * @see JavaFileManager#getLocationForModule(Location, String)
4271390Ssos         */
4281390Ssos        Location getLocationForModule(String moduleName) throws IOException {
42922106Sbde            return null;
4301390Ssos        }
4311390Ssos
4321390Ssos        /**
4331390Ssos         * @see JavaFileManager#getLocationForModule(Location, JavaFileObject, String)
4341390Ssos         */
4351390Ssos        Location getLocationForModule(Path dir) {
4361390Ssos            return null;
4371390Ssos        }
4381390Ssos
4391390Ssos        /**
4401390Ssos         * @see JavaFileManager#inferModuleName
4411390Ssos         */
4421390Ssos        String inferModuleName() {
4431390Ssos            return null;
4441390Ssos        }
4451390Ssos
44621783Sbde        /**
44721783Sbde         * @see JavaFileManager#listLocationsForModules
44821783Sbde         */
44921783Sbde        Iterable<Set<Location>> listLocationsForModules() throws IOException {
45021783Sbde            return null;
45121783Sbde        }
45221783Sbde    }
4531390Ssos
4541390Ssos    /**
4551390Ssos     * A LocationHandler for a given Location, and associated set of options.
4561390Ssos     */
4571390Ssos    private abstract class BasicLocationHandler extends LocationHandler {
4581390Ssos
45910268Sbde        final Location location;
46022106Sbde        final Set<Option> options;
4611390Ssos
46215508Sbde        /**
4631390Ssos         * Create a handler. The location and options provide a way to map from a location or an
4641390Ssos         * option to the corresponding handler.
46522106Sbde         *
46622106Sbde         * @param location the location for which this is the handler
46722106Sbde         * @param options the options affecting this location
46822106Sbde         * @see #initHandlers
46922106Sbde         */
47022106Sbde        protected BasicLocationHandler(Location location, Option... options) {
47122106Sbde            this.location = location;
47222106Sbde            this.options = options.length == 0
47322106Sbde                    ? EnumSet.noneOf(Option.class)
47422106Sbde                    : EnumSet.copyOf(Arrays.asList(options));
47522106Sbde        }
47622106Sbde    }
47722106Sbde
47822106Sbde    /**
47922106Sbde     * General purpose implementation for output locations, such as -d/CLASS_OUTPUT and
48022106Sbde     * -s/SOURCE_OUTPUT. All options are treated as equivalent (i.e. aliases.)
48122106Sbde     * The value is a single file, possibly null.
48222106Sbde     */
48322106Sbde    private class OutputLocationHandler extends BasicLocationHandler {
4841390Ssos
4851390Ssos        private Path outputDir;
48610268Sbde        private Map<String, Location> moduleLocations;
4871390Ssos        private Map<Path, Location> pathLocations;
4881390Ssos
4891390Ssos        OutputLocationHandler(Location location, Option... options) {
49021783Sbde            super(location, options);
4911390Ssos        }
49221783Sbde
49321783Sbde        @Override
49421783Sbde        boolean handleOption(Option option, String value) {
49521783Sbde            if (!options.contains(option)) {
49621783Sbde                return false;
49721783Sbde            }
49821783Sbde
49921783Sbde            // TODO: could/should validate outputDir exists and is a directory
50021783Sbde            // need to decide how best to report issue for benefit of
50121783Sbde            // direct API call on JavaFileManager.handleOption(specifies IAE)
50221783Sbde            // vs. command line decoding.
50321783Sbde            outputDir = (value == null) ? null : getPath(value);
5041390Ssos            return true;
5051390Ssos        }
5061390Ssos
5071390Ssos        @Override
5081390Ssos        Collection<Path> getPaths() {
5091390Ssos            return (outputDir == null) ? null : Collections.singleton(outputDir);
5101390Ssos        }
5111390Ssos
5121390Ssos        @Override
5133185Ssos        void setPaths(Iterable<? extends Path> files) throws IOException {
5141390Ssos            if (files == null) {
5151390Ssos                outputDir = null;
5161390Ssos            } else {
5171390Ssos                Iterator<? extends Path> pathIter = files.iterator();
5181390Ssos                if (!pathIter.hasNext()) {
5191390Ssos                    throw new IllegalArgumentException("empty path for directory");
5208876Srgrimes                }
5211390Ssos                Path dir = pathIter.next();
5221390Ssos                if (pathIter.hasNext()) {
52317231Sjoerg                    throw new IllegalArgumentException("path too long for directory");
5241390Ssos                }
5258876Srgrimes                if (!Files.exists(dir)) {
52617231Sjoerg                    throw new FileNotFoundException(dir + ": does not exist");
52717231Sjoerg                } else if (!Files.isDirectory(dir)) {
52817231Sjoerg                    throw new IOException(dir + ": not a directory");
52917231Sjoerg                }
53017231Sjoerg                outputDir = dir;
53129000Sfsmp            }
5321390Ssos            moduleLocations = null;
5331390Ssos            pathLocations = null;
53429000Sfsmp        }
5351390Ssos
53617231Sjoerg        @Override
53717231Sjoerg        Location getLocationForModule(String name) {
5381390Ssos            if (moduleLocations == null) {
5392873Sbde                moduleLocations = new HashMap<>();
5401390Ssos                pathLocations = new HashMap<>();
54117231Sjoerg            }
54217231Sjoerg            Location l = moduleLocations.get(name);
5431390Ssos            if (l == null) {
5441390Ssos                Path out = outputDir.resolve(name);
5452913Sache                l = new ModuleLocationHandler(location.getName() + "[" + name + "]",
5462913Sache                        name,
5472913Sache                        Collections.singleton(out),
5482913Sache                        true, false);
54914943Sbde                moduleLocations.put(name, l);
55014943Sbde                pathLocations.put(out.toAbsolutePath(), l);
55114943Sbde           }
55214943Sbde            return l;
55314943Sbde        }
55414943Sbde
55514943Sbde        @Override
55614943Sbde        Location getLocationForModule(Path dir) {
55714943Sbde            return (pathLocations == null) ? null : pathLocations.get(dir);
55814943Sbde        }
55914943Sbde
56014943Sbde        private boolean listed;
56114943Sbde
56213445Sphk        @Override
5635291Sbde        Iterable<Set<Location>> listLocationsForModules() throws IOException {
5642913Sache            if (!listed && outputDir != null) {
56533309Sbde                try (DirectoryStream<Path> stream = Files.newDirectoryStream(outputDir)) {
5665291Sbde                    for (Path p : stream) {
56733309Sbde                        getLocationForModule(p.getFileName().toString());
5685291Sbde                    }
56933309Sbde                }
5702913Sache                listed = true;
5712913Sache            }
57213445Sphk            if (moduleLocations == null)
5732913Sache                return Collections.emptySet();
5742913Sache            Set<Location> locns = new LinkedHashSet<>();
57513445Sphk            moduleLocations.forEach((k, v) -> locns.add(v));
5762913Sache            return Collections.singleton(locns);
5772913Sache        }
57815508Sbde    }
57915508Sbde
58015508Sbde    /**
58148160Sgreen     * General purpose implementation for search path locations,
58215508Sbde     * such as -sourcepath/SOURCE_PATH and -processorPath/ANNOTATION_PROCESSOR_PATH.
58315508Sbde     * All options are treated as equivalent (i.e. aliases.)
58415508Sbde     * The value is an ordered set of files and/or directories.
58523393Sbde     */
58623393Sbde    private class SimpleLocationHandler extends BasicLocationHandler {
58715508Sbde
58815508Sbde        protected Collection<Path> searchPath;
58915508Sbde
59015508Sbde        SimpleLocationHandler(Location location, Option... options) {
59115508Sbde            super(location, options);
59215508Sbde        }
59315508Sbde
59415508Sbde        @Override
59515508Sbde        boolean handleOption(Option option, String value) {
59615508Sbde            if (!options.contains(option)) {
59715508Sbde                return false;
59815508Sbde            }
59915508Sbde            searchPath = value == null ? null
60015508Sbde                    : Collections.unmodifiableCollection(createPath().addFiles(value));
60115508Sbde            return true;
60215508Sbde        }
60315508Sbde
60415508Sbde        @Override
60515508Sbde        Collection<Path> getPaths() {
60615508Sbde            return searchPath;
60715508Sbde        }
60815508Sbde
60915508Sbde        @Override
61015508Sbde        void setPaths(Iterable<? extends Path> files) {
61115508Sbde            SearchPath p;
61215508Sbde            if (files == null) {
61315508Sbde                p = computePath(null);
61415508Sbde            } else {
61515508Sbde                p = createPath().addFiles(files);
61615508Sbde            }
61715508Sbde            searchPath = Collections.unmodifiableCollection(p);
61815508Sbde        }
61932054Sphk
62048160Sgreen        protected SearchPath computePath(String value) {
62148266Speter            return createPath().addFiles(value);
62248266Speter        }
62315508Sbde
62415508Sbde        protected SearchPath createPath() {
62515508Sbde            return new SearchPath();
62615508Sbde        }
62715508Sbde    }
62815508Sbde
62915508Sbde    /**
63015508Sbde     * Subtype of SimpleLocationHandler for -classpath/CLASS_PATH.
63115508Sbde     * If no value is given, a default is provided, based on system properties and other values.
63215508Sbde     */
63315508Sbde    private class ClassPathLocationHandler extends SimpleLocationHandler {
63415508Sbde
63515508Sbde        ClassPathLocationHandler() {
63615508Sbde            super(StandardLocation.CLASS_PATH, Option.CLASS_PATH);
63715508Sbde        }
63815508Sbde
63915508Sbde        @Override
64015508Sbde        Collection<Path> getPaths() {
64115508Sbde            lazy();
64215508Sbde            return searchPath;
64315508Sbde        }
64415508Sbde
64515508Sbde        @Override
64615508Sbde        protected SearchPath computePath(String value) {
64715508Sbde            String cp = value;
64815508Sbde
64915508Sbde            // CLASSPATH environment variable when run from `javac'.
65015508Sbde            if (cp == null) {
65115508Sbde                cp = System.getProperty("env.class.path");
65215508Sbde            }
65315508Sbde
65415508Sbde            // If invoked via a java VM (not the javac launcher), use the
65515508Sbde            // platform class path
65633690Sphk            if (cp == null && System.getProperty("application.home") == null) {
65748160Sgreen                cp = System.getProperty("java.class.path");
65833690Sphk            }
65933690Sphk
66033690Sphk            // Default to current working directory.
66132005Sphk            if (cp == null) {
66233929Sphk                cp = ".";
66315508Sbde            }
66415508Sbde
66515508Sbde            return createPath().addFiles(cp);
66615508Sbde        }
66723393Sbde
66823393Sbde        @Override
66923393Sbde        protected SearchPath createPath() {
67015508Sbde            return new SearchPath()
67115508Sbde                    .expandJarClassPaths(true) // Only search user jars for Class-Paths
67215508Sbde                    .emptyPathDefault(getPath("."));  // Empty path elt ==> current directory
67315508Sbde        }
67415508Sbde
67515508Sbde        private void lazy() {
67616874Sbde            if (searchPath == null) {
67733309Sbde                setPaths(null);
67815508Sbde            }
67915508Sbde        }
68029000Sfsmp    }
68115508Sbde
68233309Sbde    /**
68333309Sbde     * Custom subtype of LocationHandler for PLATFORM_CLASS_PATH.
68433309Sbde     * Various options are supported for different components of the
68533309Sbde     * platform class path.
68633309Sbde     * Setting a value with setLocation overrides all existing option values.
68733309Sbde     * Setting any option overrides any value set with setLocation, and
68833309Sbde     * reverts to using default values for options that have not been set.
68928921Sfsmp     * Setting -bootclasspath or -Xbootclasspath overrides any existing
69015508Sbde     * value for -Xbootclasspath/p: and -Xbootclasspath/a:.
69115508Sbde     */
69215508Sbde    private class BootClassPathLocationHandler extends BasicLocationHandler {
6935291Sbde
69452669Siwasaki        private Collection<Path> searchPath;
69552669Siwasaki        final Map<Option, String> optionValues = new EnumMap<>(Option.class);
69652669Siwasaki
69752669Siwasaki        /**
69852669Siwasaki         * Is the bootclasspath the default?
69952669Siwasaki         */
70052669Siwasaki        private boolean isDefault;
70152669Siwasaki
70252669Siwasaki        BootClassPathLocationHandler() {
70352669Siwasaki            super(StandardLocation.PLATFORM_CLASS_PATH,
70452669Siwasaki                    Option.BOOT_CLASS_PATH, Option.XBOOTCLASSPATH,
70552669Siwasaki                    Option.XBOOTCLASSPATH_PREPEND,
70652669Siwasaki                    Option.XBOOTCLASSPATH_APPEND,
70752669Siwasaki                    Option.ENDORSEDDIRS, Option.DJAVA_ENDORSED_DIRS,
70852669Siwasaki                    Option.EXTDIRS, Option.DJAVA_EXT_DIRS);
70952669Siwasaki        }
71052669Siwasaki
71152669Siwasaki        boolean isDefault() {
71252669Siwasaki            lazy();
71352669Siwasaki            return isDefault;
71452669Siwasaki        }
71552669Siwasaki
71633690Sphk        @Override
7175291Sbde        boolean handleOption(Option option, String value) {
7185291Sbde            if (!options.contains(option)) {
7191390Ssos                return false;
7208876Srgrimes            }
721798Swollman
72215508Sbde            option = canonicalize(option);
72315508Sbde            optionValues.put(option, value);
72432054Sphk            if (option == BOOT_CLASS_PATH) {
72532054Sphk                optionValues.remove(XBOOTCLASSPATH_PREPEND);
72632054Sphk                optionValues.remove(XBOOTCLASSPATH_APPEND);
72732054Sphk            }
72832054Sphk            searchPath = null;  // reset to "uninitialized"
72915508Sbde            return true;
73015508Sbde        }
73115508Sbde        // where
73216874Sbde        // TODO: would be better if option aliasing was handled at a higher
73315508Sbde        // level
73415508Sbde        private Option canonicalize(Option option) {
73515508Sbde            switch (option) {
73615508Sbde                case XBOOTCLASSPATH:
73715508Sbde                    return Option.BOOT_CLASS_PATH;
73818288Sbde                case DJAVA_ENDORSED_DIRS:
73915508Sbde                    return Option.ENDORSEDDIRS;
74015508Sbde                case DJAVA_EXT_DIRS:
74115508Sbde                    return Option.EXTDIRS;
74215508Sbde                default:
74315508Sbde                    return option;
74415508Sbde            }
74515508Sbde        }
74615508Sbde
74715508Sbde        @Override
74815508Sbde        Collection<Path> getPaths() {
74915508Sbde            lazy();
75015508Sbde            return searchPath;
75116300Spst        }
75219173Sbde
75315508Sbde        @Override
75415508Sbde        void setPaths(Iterable<? extends Path> files) {
75515508Sbde            if (files == null) {
75615508Sbde                searchPath = null;  // reset to "uninitialized"
75715508Sbde            } else {
75823393Sbde                isDefault = false;
75923393Sbde                SearchPath p = new SearchPath().addFiles(files, false);
76023393Sbde                searchPath = Collections.unmodifiableCollection(p);
76123393Sbde                optionValues.clear();
76232005Sphk            }
76315508Sbde        }
76415508Sbde
76515508Sbde        SearchPath computePath() throws IOException {
76640610Sphk            SearchPath path = new SearchPath();
76740610Sphk
76815508Sbde            String bootclasspathOpt = optionValues.get(BOOT_CLASS_PATH);
76932005Sphk            String endorseddirsOpt = optionValues.get(ENDORSEDDIRS);
77032005Sphk            String extdirsOpt = optionValues.get(EXTDIRS);
77116300Spst            String xbootclasspathPrependOpt = optionValues.get(XBOOTCLASSPATH_PREPEND);
77219173Sbde            String xbootclasspathAppendOpt = optionValues.get(XBOOTCLASSPATH_APPEND);
77332005Sphk            path.addFiles(xbootclasspathPrependOpt);
77432005Sphk
77515508Sbde            if (endorseddirsOpt != null) {
77615508Sbde                path.addDirectories(endorseddirsOpt);
77732054Sphk            } else {
77815508Sbde                path.addDirectories(System.getProperty("java.endorsed.dirs"), false);
77915508Sbde            }
78015508Sbde
78115508Sbde            if (bootclasspathOpt != null) {
78215508Sbde                path.addFiles(bootclasspathOpt);
78348160Sgreen            } else {
78448160Sgreen                // Standard system classes for this compiler's release.
78515508Sbde                Collection<Path> systemClasses = systemClasses();
78648160Sgreen                if (systemClasses != null) {
78732054Sphk                    path.addFiles(systemClasses, false);
78823393Sbde                } else {
78933690Sphk                    // fallback to the value of sun.boot.class.path
79016300Spst                    String files = System.getProperty("sun.boot.class.path");
79115508Sbde                    path.addFiles(files, false);
79234617Sphk                }
79334617Sphk            }
79434617Sphk
79534617Sphk            path.addFiles(xbootclasspathAppendOpt);
79634617Sphk
79734617Sphk            // Strictly speaking, standard extensions are not bootstrap
79834617Sphk            // classes, but we treat them identically, so we'll pretend
79934617Sphk            // that they are.
80034617Sphk            if (extdirsOpt != null) {
80134617Sphk                path.addDirectories(extdirsOpt);
80234617Sphk            } else {
80349186Smsmith                // Add lib/jfxrt.jar to the search path
80449186Smsmith               Path jfxrt = javaHome.resolve("lib/jfxrt.jar");
80534617Sphk                if (Files.exists(jfxrt)) {
80634617Sphk                    path.addFile(jfxrt, false);
80734617Sphk                }
80834617Sphk                path.addDirectories(System.getProperty("java.ext.dirs"), false);
80949186Smsmith            }
81049186Smsmith
81134617Sphk            isDefault =
81249186Smsmith                       (xbootclasspathPrependOpt == null)
81334617Sphk                    && (bootclasspathOpt == null)
81434617Sphk                    && (xbootclasspathAppendOpt == null);
81547592Sphk
81640610Sphk            return path;
81740610Sphk        }
81833690Sphk
81934617Sphk        /**
82034617Sphk         * Return a collection of files containing system classes.
8214Srgrimes         * Returns {@code null} if not running on a modular image.
8224Srgrimes         *
8232913Sache         * @throws UncheckedIOException if an I/O errors occurs
82441787Smckay         */
82541787Smckay        private Collection<Path> systemClasses() throws IOException {
8262913Sache            // Return "modules" jimage file if available
8272913Sache            if (Files.isRegularFile(thisSystemModules)) {
8283185Ssos                return Collections.singleton(thisSystemModules);
8294Srgrimes            }
8302913Sache
8312913Sache            // Exploded module image
8322913Sache            Path modules = javaHome.resolve("modules");
8332913Sache            if (Files.isDirectory(modules.resolve("java.base"))) {
83433690Sphk                try (Stream<Path> listedModules = Files.list(modules)) {
8354Srgrimes                    return listedModules.collect(Collectors.toList());
83632850Sphk                }
83732850Sphk            }
83833690Sphk
83933690Sphk            // not a modular image that we know about
84033690Sphk            return null;
84132850Sphk        }
84232850Sphk
8431390Ssos        private void lazy() {
84441787Smckay            if (searchPath == null) {
8459202Srgrimes                try {
8462913Sache                searchPath = Collections.unmodifiableCollection(computePath());
8474Srgrimes                } catch (IOException e) {
84841787Smckay                    // TODO: need better handling here, e.g. javac Abort?
84941787Smckay                    throw new UncheckedIOException(e);
8502913Sache                }
8514Srgrimes            }
8522913Sache        }
8533355Sache    }
85441787Smckay
8553355Sache    /**
8563355Sache     * A LocationHander to represent modules found from a module-oriented
8572913Sache     * location such as MODULE_SOURCE_PATH, UPGRADE_MODULE_PATH,
8583355Sache     * SYSTEM_MODULES and MODULE_PATH.
8593355Sache     *
8603355Sache     * The Location can be specified to accept overriding classes from the
8612913Sache     * {@code --patch-module <module>=<path> } parameter.
86241787Smckay     */
86341787Smckay    private class ModuleLocationHandler extends LocationHandler implements Location {
86441787Smckay        protected final String name;
86541787Smckay        protected final String moduleName;
8662913Sache        protected final Collection<Path> searchPath;
86741787Smckay        protected final Collection<Path> searchPathWithOverrides;
8682913Sache        protected final boolean output;
8692913Sache
87041787Smckay        ModuleLocationHandler(String name, String moduleName, Collection<Path> searchPath,
8712913Sache                boolean output, boolean allowOverrides) {
8722913Sache            this.name = name;
8732913Sache            this.moduleName = moduleName;
8742913Sache            this.searchPath = searchPath;
87541787Smckay            this.output = output;
87641787Smckay
8771390Ssos            if (allowOverrides && patchMap != null) {
87815054Sache                SearchPath mPatch = patchMap.get(moduleName);
8794Srgrimes                if (mPatch != null) {
88034961Sphk                    SearchPath sp = new SearchPath();
88133690Sphk                    sp.addAll(mPatch);
88233690Sphk                    sp.addAll(searchPath);
88333690Sphk                    searchPathWithOverrides = sp;
88433690Sphk                } else {
88533690Sphk                    searchPathWithOverrides = searchPath;
88633690Sphk                }
88733690Sphk            } else {
88833690Sphk                searchPathWithOverrides = searchPath;
8892913Sache            }
8902913Sache        }
8912913Sache
89241787Smckay        @Override @DefinedBy(Api.COMPILER)
89341787Smckay        public String getName() {
8944Srgrimes            return name;
8954Srgrimes        }
8964Srgrimes
89741787Smckay        @Override @DefinedBy(Api.COMPILER)
8984Srgrimes        public boolean isOutputLocation() {
8994180Sbde            return output;
9004180Sbde        }
9014Srgrimes
9022913Sache        @Override // defined by LocationHandler
90311872Sphk        boolean handleOption(Option option, String value) {
9044Srgrimes            throw new UnsupportedOperationException();
9053366Sache        }
9063366Sache
9073366Sache        @Override // defined by LocationHandler
9082913Sache        Collection<Path> getPaths() {
90934961Sphk            // For now, we always return searchPathWithOverrides. This may differ from the
9102913Sache            // JVM behavior if there is a module-info.class to be found in the overriding
9114Srgrimes            // classes.
9125291Sbde            return searchPathWithOverrides;
9132913Sache        }
9144Srgrimes
91541787Smckay        @Override // defined by LocationHandler
9161390Ssos        void setPaths(Iterable<? extends Path> files) throws IOException {
91715054Sache            throw new UnsupportedOperationException();
9182913Sache        }
91913445Sphk
92013445Sphk        @Override // defined by LocationHandler
92113445Sphk        String inferModuleName() {
9222913Sache            return moduleName;
92341787Smckay        }
9242913Sache    }
92513350Sache
92613350Sache    /**
92713350Sache     * A LocationHandler for simple module-oriented search paths,
92813350Sache     * like UPGRADE_MODULE_PATH and MODULE_PATH.
9292913Sache     */
9302913Sache    private class ModulePathLocationHandler extends SimpleLocationHandler {
93113453Sache        private Map<String, ModuleLocationHandler> pathModules;
93213350Sache
93313445Sphk        ModulePathLocationHandler(Location location, Option... options) {
9343355Sache            super(location, options);
93513402Sbde        }
93613402Sbde
9372913Sache        @Override
93813402Sbde        public boolean handleOption(Option option, String value) {
93913402Sbde            if (!options.contains(option)) {
94013402Sbde                return false;
94113402Sbde            }
94213402Sbde            setPaths(value == null ? null : getPathEntries(value));
94313402Sbde            return true;
94413402Sbde        }
94513402Sbde
94613445Sphk        @Override
94713445Sphk        public Location getLocationForModule(String moduleName) {
9482913Sache            initPathModules();
9495291Sbde            return pathModules.get(moduleName);
95015345Snate        }
9514Srgrimes
9524Srgrimes        @Override
95327560Sfsmp        Iterable<Set<Location>> listLocationsForModules() {
9544Srgrimes            if (searchPath == null)
9555291Sbde                return Collections.emptyList();
9564Srgrimes
9575291Sbde            return ModulePathIterator::new;
9585291Sbde        }
9594Srgrimes
9605291Sbde        @Override
96126949Sfsmp        void setPaths(Iterable<? extends Path> paths) {
96234571Stegge            if (paths != null) {
96345900Speter                for (Path p: paths) {
96425164Speter                    checkValidModulePathEntry(p);
9654Srgrimes                }
96615345Snate            }
96715345Snate            super.setPaths(paths);
96815345Snate        }
96915345Snate
97015345Snate        private void initPathModules() {
97115345Snate            if (pathModules != null) {
97215345Snate                return;
97315345Snate            }
97415345Snate
97515345Snate            pathModules = new LinkedHashMap<>();
97615345Snate
97715345Snate            for (Set<Location> set : listLocationsForModules()) {
97815345Snate                for (Location locn : set) {
97915345Snate                    if (locn instanceof ModuleLocationHandler) {
9804Srgrimes                        ModuleLocationHandler h = (ModuleLocationHandler) locn;
9815291Sbde                        pathModules.put(h.moduleName, h);
98226949Sfsmp                    }
98327563Sfsmp                }
98438888Stegge            }
98534571Stegge        }
98634571Stegge
98734571Stegge        private void checkValidModulePathEntry(Path p) {
98834571Stegge            if (!Files.exists(p)) {
98934571Stegge                // warning may be generated later
99034571Stegge                return;
99134571Stegge            }
99234571Stegge
99334571Stegge            if (Files.isDirectory(p)) {
99434571Stegge                // either an exploded module or a directory of modules
99534571Stegge                return;
99627563Sfsmp            }
99727563Sfsmp
99845897Speter            String name = p.getFileName().toString();
99945897Speter            int lastDot = name.lastIndexOf(".");
100034571Stegge            if (lastDot > 0) {
100134571Stegge                switch (name.substring(lastDot)) {
100227696Sfsmp                    case ".jar":
100327563Sfsmp                    case ".jmod":
100445897Speter                        return;
100545897Speter                }
10064Srgrimes            }
100727696Sfsmp            throw new IllegalArgumentException(p.toString());
100825164Speter        }
100926949Sfsmp
10105291Sbde        class ModulePathIterator implements Iterator<Set<Location>> {
10115291Sbde            Iterator<Path> pathIter = searchPath.iterator();
10125291Sbde            int pathIndex = 0;
101315345Snate            Set<Location> next = null;
101415345Snate
101515345Snate            @Override
101615345Snate            public boolean hasNext() {
10175291Sbde                if (next != null)
10185291Sbde                    return true;
10195291Sbde
102027520Sfsmp                while (next == null) {
102126949Sfsmp                    if (pathIter.hasNext()) {
102238888Stegge                        Path path = pathIter.next();
102327520Sfsmp                        if (Files.isDirectory(path)) {
102427563Sfsmp                            next = scanDirectory(path);
102528487Sfsmp                        } else {
102645897Speter                            next = scanFile(path);
102745897Speter                        }
102828487Sfsmp                        pathIndex++;
102927616Sfsmp                    } else
103027616Sfsmp                        return false;
103127616Sfsmp                }
10322074Swollman                return true;
103327616Sfsmp            }
103427520Sfsmp
103515345Snate            @Override
103634571Stegge            public Set<Location> next() {
103734571Stegge                hasNext();
103834571Stegge                if (next != null) {
103934571Stegge                    Set<Location> result = next;
104034571Stegge                    next = null;
104134571Stegge                    return result;
104235035Stegge                }
104334571Stegge                throw new NoSuchElementException();
104434571Stegge            }
104534571Stegge
104634571Stegge            private Set<Location> scanDirectory(Path path) {
104734571Stegge                Set<Path> paths = new LinkedHashSet<>();
104834571Stegge                Path moduleInfoClass = null;
104934571Stegge                try (DirectoryStream<Path> stream = Files.newDirectoryStream(path)) {
105034571Stegge                    for (Path entry: stream) {
105145897Speter                        if (entry.endsWith("module-info.class")) {
105234571Stegge                            moduleInfoClass = entry;
105334571Stegge                            break;  // no need to continue scanning
105434571Stegge                        }
105534571Stegge                        paths.add(entry);
105634571Stegge                    }
105734571Stegge                } catch (DirectoryIteratorException | IOException ignore) {
105845897Speter                    log.error(Errors.LocnCantReadDirectory(path));
105945897Speter                    return Collections.emptySet();
106034571Stegge                }
106134571Stegge
106234571Stegge                if (moduleInfoClass != null) {
106334571Stegge                    // It's an exploded module directly on the module path.
106434571Stegge                    // We can't infer module name from the directory name, so have to
106534571Stegge                    // read module-info.class.
106634571Stegge                    try {
106734571Stegge                        String moduleName = readModuleName(moduleInfoClass);
106834571Stegge                        String name = location.getName()
106934571Stegge                                + "[" + pathIndex + ":" + moduleName + "]";
10704Srgrimes                        ModuleLocationHandler l = new ModuleLocationHandler(name, moduleName,
10714Srgrimes                                Collections.singleton(path), false, true);
107234571Stegge                        return Collections.singleton(l);
107334571Stegge                    } catch (ModuleNameReader.BadClassFile e) {
107434571Stegge                        log.error(Errors.LocnBadModuleInfo(path));
107534571Stegge                        return Collections.emptySet();
107634571Stegge                    } catch (IOException e) {
107734571Stegge                        log.error(Errors.LocnCantReadFile(path));
107834571Stegge                        return Collections.emptySet();
107934571Stegge                    }
108034571Stegge                }
108134571Stegge
108234571Stegge                // A directory of modules
108334571Stegge                Set<Location> result = new LinkedHashSet<>();
108434571Stegge                int index = 0;
108534571Stegge                for (Path entry : paths) {
108634571Stegge                    Pair<String,Path> module = inferModuleName(entry);
108734571Stegge                    if (module == null) {
108834571Stegge                        // diagnostic reported if necessary; skip to next
108934571Stegge                        continue;
109034571Stegge                    }
109134571Stegge                    String moduleName = module.fst;
109234571Stegge                    Path modulePath = module.snd;
109334571Stegge                    String name = location.getName()
109434571Stegge                            + "[" + pathIndex + "." + (index++) + ":" + moduleName + "]";
109534571Stegge                    ModuleLocationHandler l = new ModuleLocationHandler(name, moduleName,
109634571Stegge                            Collections.singleton(modulePath), false, true);
109734571Stegge                    result.add(l);
109834571Stegge                }
109934571Stegge                return result;
110034571Stegge            }
110134571Stegge
110234571Stegge            private Set<Location> scanFile(Path path) {
11034Srgrimes                Pair<String,Path> module = inferModuleName(path);
11041549Srgrimes                if (module == null) {
11051549Srgrimes                    // diagnostic reported if necessary
11065291Sbde                    return Collections.emptySet();
11072074Swollman                }
11085291Sbde                String moduleName = module.fst;
11092074Swollman                Path modulePath = module.snd;
11105291Sbde                String name = location.getName()
11111549Srgrimes                        + "[" + pathIndex + ":" + moduleName + "]";
111215508Sbde                ModuleLocationHandler l = new ModuleLocationHandler(name, moduleName,
111315508Sbde                        Collections.singleton(modulePath), false, true);
111415508Sbde                return Collections.singleton(l);
111515508Sbde            }
111615508Sbde
111715508Sbde            private Pair<String,Path> inferModuleName(Path p) {
111815508Sbde                if (Files.isDirectory(p)) {
111915508Sbde                    if (Files.exists(p.resolve("module-info.class"))) {
112015508Sbde                        String name = p.getFileName().toString();
112115508Sbde                        if (SourceVersion.isName(name))
112215508Sbde                            return new Pair<>(name, p);
112315508Sbde                    }
112448888Sbde                    return null;
112517353Sbde                }
112633690Sphk
112715508Sbde                if (p.getFileName().toString().endsWith(".jar") && fsInfo.exists(p)) {
112815508Sbde                    FileSystemProvider jarFSProvider = fsInfo.getJarFSProvider();
112940610Sphk                    if (jarFSProvider == null) {
113046054Sphk                        log.error(Errors.NoZipfsForArchive(p));
113115508Sbde                        return null;
113215508Sbde                    }
113315508Sbde                    try (FileSystem fs = jarFSProvider.newFileSystem(p, fsEnv)) {
113415508Sbde                        Path moduleInfoClass = fs.getPath("module-info.class");
113515508Sbde                        if (Files.exists(moduleInfoClass)) {
113648888Sbde                            String moduleName = readModuleName(moduleInfoClass);
113715508Sbde                            return new Pair<>(moduleName, p);
113815508Sbde                        }
113932054Sphk                    } catch (ModuleNameReader.BadClassFile e) {
114015508Sbde                        log.error(Errors.LocnBadModuleInfo(p));
114115508Sbde                        return null;
114215508Sbde                    } catch (IOException e) {
114315508Sbde                        log.error(Errors.LocnCantReadFile(p));
114448888Sbde                        return null;
114515508Sbde                    }
114632005Sphk
114748888Sbde                    //automatic module:
114833690Sphk                    String fn = p.getFileName().toString();
114933690Sphk                    //from ModulePath.deriveModuleDescriptor:
115040610Sphk
115146054Sphk                    // drop .jar
115233690Sphk                    String mn = fn.substring(0, fn.length()-4);
115315508Sbde
115415508Sbde                    // find first occurrence of -${NUMBER}. or -${NUMBER}$
115515508Sbde                    Matcher matcher = Pattern.compile("-(\\d+(\\.|$))").matcher(mn);
115632054Sphk                    if (matcher.find()) {
115748888Sbde                        int start = matcher.start();
115833690Sphk
115936441Sphk                        mn = mn.substring(0, start);
116036719Sphk                    }
116133690Sphk
116236198Sphk                    // finally clean up the module name
116333690Sphk                    mn =  mn.replaceAll("(\\.|\\d)*$", "")    // remove trailing version
116433690Sphk                            .replaceAll("[^A-Za-z0-9]", ".")  // replace non-alphanumeric
116533690Sphk                            .replaceAll("(\\.)(\\1)+", ".")   // collapse repeating dots
116633690Sphk                            .replaceAll("^\\.", "")           // drop leading dots
116733690Sphk                            .replaceAll("\\.$", "");          // drop trailing dots
116833690Sphk
116933690Sphk
117033690Sphk                    if (!mn.isEmpty()) {
117133690Sphk                        return new Pair<>(mn, p);
117233690Sphk                    }
117333690Sphk
117447588Sbde                    log.error(Errors.LocnCantGetModuleNameForJar(p));
117547588Sbde                    return null;
117647588Sbde                }
117747588Sbde
117847588Sbde                if (p.getFileName().toString().endsWith(".jmod")) {
117947588Sbde                    try {
118047588Sbde                        // check if the JMOD file is valid
118147588Sbde                        JDK9Wrappers.JmodFile.checkMagic(p);
118247588Sbde
118347588Sbde                        // No JMOD file system.  Use JarFileSystem to
118447588Sbde                        // workaround for now
118547588Sbde                        FileSystem fs = fileSystems.get(p);
118633690Sphk                        if (fs == null) {
118747588Sbde                            FileSystemProvider jarFSProvider = fsInfo.getJarFSProvider();
118833690Sphk                            if (jarFSProvider == null) {
118933690Sphk                                log.error(Errors.LocnCantReadFile(p));
119033690Sphk                                return null;
119133727Sjkh                            }
119233690Sphk                            fs = jarFSProvider.newFileSystem(p, Collections.emptyMap());
119333690Sphk                            try {
119433690Sphk                                Path moduleInfoClass = fs.getPath("classes/module-info.class");
119533690Sphk                                String moduleName = readModuleName(moduleInfoClass);
119636441Sphk                                Path modulePath = fs.getPath("classes");
119736719Sphk                                fileSystems.put(p, fs);
119833690Sphk                                closeables.add(fs);
119936198Sphk                                fs = null; // prevent fs being closed in the finally clause
120033690Sphk                                return new Pair<>(moduleName, modulePath);
1201                            } finally {
1202                                if (fs != null)
1203                                    fs.close();
1204                            }
1205                        }
1206                    } catch (ModuleNameReader.BadClassFile e) {
1207                        log.error(Errors.LocnBadModuleInfo(p));
1208                    } catch (IOException e) {
1209                        log.error(Errors.LocnCantReadFile(p));
1210                        return null;
1211                    }
1212                }
1213
1214                if (warn && false) {  // temp disable, when enabled, massage examples.not-yet.txt suitably.
1215                    log.warning(Warnings.LocnUnknownFileOnModulePath(p));
1216                }
1217                return null;
1218            }
1219
1220            private String readModuleName(Path path) throws IOException, ModuleNameReader.BadClassFile {
1221                if (moduleNameReader == null)
1222                    moduleNameReader = new ModuleNameReader();
1223                return moduleNameReader.readModuleName(path);
1224            }
1225        }
1226
1227    }
1228
1229    private class ModuleSourcePathLocationHandler extends BasicLocationHandler {
1230
1231        private Map<String, Location> moduleLocations;
1232        private Map<Path, Location> pathLocations;
1233
1234        ModuleSourcePathLocationHandler() {
1235            super(StandardLocation.MODULE_SOURCE_PATH,
1236                    Option.MODULE_SOURCE_PATH);
1237        }
1238
1239        @Override
1240        boolean handleOption(Option option, String value) {
1241            init(value);
1242            return true;
1243        }
1244
1245        void init(String value) {
1246            Collection<String> segments = new ArrayList<>();
1247            for (String s: value.split(File.pathSeparator)) {
1248                expandBraces(s, segments);
1249            }
1250
1251            Map<String, Collection<Path>> map = new LinkedHashMap<>();
1252            final String MARKER = "*";
1253            for (String seg: segments) {
1254                int markStart = seg.indexOf(MARKER);
1255                if (markStart == -1) {
1256                    add(map, getPath(seg), null);
1257                } else {
1258                    if (markStart == 0 || !isSeparator(seg.charAt(markStart - 1))) {
1259                        throw new IllegalArgumentException("illegal use of " + MARKER + " in " + seg);
1260                    }
1261                    Path prefix = getPath(seg.substring(0, markStart - 1));
1262                    Path suffix;
1263                    int markEnd = markStart + MARKER.length();
1264                    if (markEnd == seg.length()) {
1265                        suffix = null;
1266                    } else if (!isSeparator(seg.charAt(markEnd))
1267                            || seg.indexOf(MARKER, markEnd) != -1) {
1268                        throw new IllegalArgumentException("illegal use of " + MARKER + " in " + seg);
1269                    } else {
1270                        suffix = getPath(seg.substring(markEnd + 1));
1271                    }
1272                    add(map, prefix, suffix);
1273                }
1274            }
1275
1276            moduleLocations = new LinkedHashMap<>();
1277            pathLocations = new LinkedHashMap<>();
1278            map.forEach((k, v) -> {
1279                String name = location.getName() + "[" + k + "]";
1280                ModuleLocationHandler h = new ModuleLocationHandler(name, k, v, false, false);
1281                moduleLocations.put(k, h);
1282                v.forEach(p -> pathLocations.put(normalize(p), h));
1283            });
1284        }
1285
1286        private boolean isSeparator(char ch) {
1287            // allow both separators on Windows
1288            return (ch == File.separatorChar) || (ch == '/');
1289        }
1290
1291        void add(Map<String, Collection<Path>> map, Path prefix, Path suffix) {
1292            if (!Files.isDirectory(prefix)) {
1293                if (warn) {
1294                    String key = Files.exists(prefix)
1295                            ? "dir.path.element.not.directory"
1296                            : "dir.path.element.not.found";
1297                    log.warning(Lint.LintCategory.PATH, key, prefix);
1298                }
1299                return;
1300            }
1301            try (DirectoryStream<Path> stream = Files.newDirectoryStream(prefix, path -> Files.isDirectory(path))) {
1302                for (Path entry: stream) {
1303                    Path path = (suffix == null) ? entry : entry.resolve(suffix);
1304                    if (Files.isDirectory(path)) {
1305                        String name = entry.getFileName().toString();
1306                        Collection<Path> paths = map.get(name);
1307                        if (paths == null)
1308                            map.put(name, paths = new ArrayList<>());
1309                        paths.add(path);
1310                    }
1311                }
1312            } catch (IOException e) {
1313                // TODO? What to do?
1314                System.err.println(e);
1315            }
1316        }
1317
1318        private void expandBraces(String value, Collection<String> results) {
1319            int depth = 0;
1320            int start = -1;
1321            String prefix = null;
1322            String suffix = null;
1323            for (int i = 0; i < value.length(); i++) {
1324                switch (value.charAt(i)) {
1325                    case '{':
1326                        depth++;
1327                        if (depth == 1) {
1328                            prefix = value.substring(0, i);
1329                            suffix = value.substring(getMatchingBrace(value, i) + 1);
1330                            start = i + 1;
1331                        }
1332                        break;
1333
1334                    case ',':
1335                        if (depth == 1) {
1336                            String elem = value.substring(start, i);
1337                            expandBraces(prefix + elem + suffix, results);
1338                            start = i + 1;
1339                        }
1340                        break;
1341
1342                    case '}':
1343                        switch (depth) {
1344                            case 0:
1345                                throw new IllegalArgumentException("mismatched braces");
1346
1347                            case 1:
1348                                String elem = value.substring(start, i);
1349                                expandBraces(prefix + elem + suffix, results);
1350                                return;
1351
1352                            default:
1353                                depth--;
1354                        }
1355                        break;
1356                }
1357            }
1358            if (depth > 0)
1359                throw new IllegalArgumentException("mismatched braces");
1360            results.add(value);
1361        }
1362
1363        int getMatchingBrace(String value, int offset) {
1364            int depth = 1;
1365            for (int i = offset + 1; i < value.length(); i++) {
1366                switch (value.charAt(i)) {
1367                    case '{':
1368                        depth++;
1369                        break;
1370
1371                    case '}':
1372                        if (--depth == 0)
1373                            return i;
1374                        break;
1375                }
1376            }
1377            throw new IllegalArgumentException("mismatched braces");
1378        }
1379
1380        @Override
1381        boolean isSet() {
1382            return (moduleLocations != null);
1383        }
1384
1385        @Override
1386        Collection<Path> getPaths() {
1387            throw new UnsupportedOperationException();
1388        }
1389
1390        @Override
1391        void setPaths(Iterable<? extends Path> files) throws IOException {
1392            throw new UnsupportedOperationException();
1393        }
1394
1395        @Override
1396        Location getLocationForModule(String name) {
1397            return (moduleLocations == null) ? null : moduleLocations.get(name);
1398        }
1399
1400        @Override
1401        Location getLocationForModule(Path dir) {
1402            return (pathLocations == null) ? null : pathLocations.get(dir);
1403        }
1404
1405        @Override
1406        Iterable<Set<Location>> listLocationsForModules() {
1407            if (moduleLocations == null)
1408                return Collections.emptySet();
1409            Set<Location> locns = new LinkedHashSet<>();
1410            moduleLocations.forEach((k, v) -> locns.add(v));
1411            return Collections.singleton(locns);
1412        }
1413
1414    }
1415
1416    private class SystemModulesLocationHandler extends BasicLocationHandler {
1417        private Path systemJavaHome;
1418        private Path modules;
1419        private Map<String, ModuleLocationHandler> systemModules;
1420
1421        SystemModulesLocationHandler() {
1422            super(StandardLocation.SYSTEM_MODULES, Option.SYSTEM);
1423            systemJavaHome = Locations.javaHome;
1424        }
1425
1426        @Override
1427        boolean handleOption(Option option, String value) {
1428            if (!options.contains(option)) {
1429                return false;
1430            }
1431
1432            if (value == null) {
1433                systemJavaHome = Locations.javaHome;
1434            } else if (value.equals("none")) {
1435                systemJavaHome = null;
1436            } else {
1437                update(getPath(value));
1438            }
1439
1440            modules = null;
1441            return true;
1442        }
1443
1444        @Override
1445        Collection<Path> getPaths() {
1446            return (systemJavaHome == null) ? null : Collections.singleton(systemJavaHome);
1447        }
1448
1449        @Override
1450        void setPaths(Iterable<? extends Path> files) throws IOException {
1451            if (files == null) {
1452                systemJavaHome = null;
1453            } else {
1454                Iterator<? extends Path> pathIter = files.iterator();
1455                if (!pathIter.hasNext()) {
1456                    throw new IllegalArgumentException("empty path for directory"); // TODO: FIXME
1457                }
1458                Path dir = pathIter.next();
1459                if (pathIter.hasNext()) {
1460                    throw new IllegalArgumentException("path too long for directory"); // TODO: FIXME
1461                }
1462                if (!Files.exists(dir)) {
1463                    throw new FileNotFoundException(dir + ": does not exist");
1464                } else if (!Files.isDirectory(dir)) {
1465                    throw new IOException(dir + ": not a directory");
1466                }
1467                update(dir);
1468            }
1469        }
1470
1471        private void update(Path p) {
1472            if (!isCurrentPlatform(p) && !Files.exists(p.resolve("lib").resolve("jrt-fs.jar")) &&
1473                    !Files.exists(systemJavaHome.resolve("modules")))
1474                throw new IllegalArgumentException(p.toString());
1475            systemJavaHome = p;
1476            modules = null;
1477        }
1478
1479        private boolean isCurrentPlatform(Path p) {
1480            try {
1481                return Files.isSameFile(p, Locations.javaHome);
1482            } catch (IOException ex) {
1483                throw new IllegalArgumentException(p.toString(), ex);
1484            }
1485        }
1486
1487        @Override
1488        Location getLocationForModule(String name) throws IOException {
1489            initSystemModules();
1490            return systemModules.get(name);
1491        }
1492
1493        @Override
1494        Iterable<Set<Location>> listLocationsForModules() throws IOException {
1495            initSystemModules();
1496            Set<Location> locns = new LinkedHashSet<>();
1497            for (Location l: systemModules.values())
1498                locns.add(l);
1499            return Collections.singleton(locns);
1500        }
1501
1502        private void initSystemModules() throws IOException {
1503            if (systemModules != null) {
1504                return;
1505            }
1506
1507            if (systemJavaHome == null) {
1508                systemModules = Collections.emptyMap();
1509                return;
1510            }
1511
1512            if (modules == null) {
1513                try {
1514                    URI jrtURI = URI.create("jrt:/");
1515                    FileSystem jrtfs;
1516
1517                    if (isCurrentPlatform(systemJavaHome)) {
1518                        jrtfs = FileSystems.getFileSystem(jrtURI);
1519                    } else {
1520                        try {
1521                            Map<String, String> attrMap =
1522                                    Collections.singletonMap("java.home", systemJavaHome.toString());
1523                            jrtfs = FileSystems.newFileSystem(jrtURI, attrMap);
1524                        } catch (ProviderNotFoundException ex) {
1525                            URL javaHomeURL = systemJavaHome.resolve("jrt-fs.jar").toUri().toURL();
1526                            ClassLoader currentLoader = Locations.class.getClassLoader();
1527                            URLClassLoader fsLoader =
1528                                    new URLClassLoader(new URL[] {javaHomeURL}, currentLoader);
1529
1530                            jrtfs = FileSystems.newFileSystem(jrtURI, Collections.emptyMap(), fsLoader);
1531
1532                            closeables.add(fsLoader);
1533                        }
1534
1535                        closeables.add(jrtfs);
1536                    }
1537
1538                    modules = jrtfs.getPath("/modules");
1539                } catch (FileSystemNotFoundException | ProviderNotFoundException e) {
1540                    modules = systemJavaHome.resolve("modules");
1541                    if (!Files.exists(modules))
1542                        throw new IOException("can't find system classes", e);
1543                }
1544            }
1545
1546            systemModules = new LinkedHashMap<>();
1547            try (DirectoryStream<Path> stream = Files.newDirectoryStream(modules, Files::isDirectory)) {
1548                for (Path entry : stream) {
1549                    String moduleName = entry.getFileName().toString();
1550                    String name = location.getName() + "[" + moduleName + "]";
1551                    ModuleLocationHandler h = new ModuleLocationHandler(name, moduleName,
1552                            Collections.singleton(entry), false, true);
1553                    systemModules.put(moduleName, h);
1554                }
1555            }
1556        }
1557    }
1558
1559    Map<Location, LocationHandler> handlersForLocation;
1560    Map<Option, LocationHandler> handlersForOption;
1561
1562    void initHandlers() {
1563        handlersForLocation = new HashMap<>();
1564        handlersForOption = new EnumMap<>(Option.class);
1565
1566        BasicLocationHandler[] handlers = {
1567            new BootClassPathLocationHandler(),
1568            new ClassPathLocationHandler(),
1569            new SimpleLocationHandler(StandardLocation.SOURCE_PATH, Option.SOURCE_PATH),
1570            new SimpleLocationHandler(StandardLocation.ANNOTATION_PROCESSOR_PATH, Option.PROCESSOR_PATH),
1571            new SimpleLocationHandler(StandardLocation.ANNOTATION_PROCESSOR_MODULE_PATH, Option.PROCESSOR_MODULE_PATH),
1572            new OutputLocationHandler(StandardLocation.CLASS_OUTPUT, Option.D),
1573            new OutputLocationHandler(StandardLocation.SOURCE_OUTPUT, Option.S),
1574            new OutputLocationHandler(StandardLocation.NATIVE_HEADER_OUTPUT, Option.H),
1575            new ModuleSourcePathLocationHandler(),
1576            // TODO: should UPGRADE_MODULE_PATH be merged with SYSTEM_MODULES?
1577            new ModulePathLocationHandler(StandardLocation.UPGRADE_MODULE_PATH, Option.UPGRADE_MODULE_PATH),
1578            new ModulePathLocationHandler(StandardLocation.MODULE_PATH, Option.MODULE_PATH),
1579            new SystemModulesLocationHandler(),
1580        };
1581
1582        for (BasicLocationHandler h : handlers) {
1583            handlersForLocation.put(h.location, h);
1584            for (Option o : h.options) {
1585                handlersForOption.put(o, h);
1586            }
1587        }
1588    }
1589
1590    private Map<String, SearchPath> patchMap;
1591
1592    boolean handleOption(Option option, String value) {
1593        switch (option) {
1594            case PATCH_MODULE:
1595                if (value == null) {
1596                    patchMap = null;
1597                } else {
1598                    // Allow an extended syntax for --patch-module consisting of a series
1599                    // of values separated by NULL characters. This is to facilitate
1600                    // supporting deferred file manager options on the command line.
1601                    // See Option.PATCH_MODULE for the code that composes these multiple
1602                    // values.
1603                    for (String v : value.split("\0")) {
1604                        int eq = v.indexOf('=');
1605                        if (eq > 0) {
1606                            String mName = v.substring(0, eq);
1607                            SearchPath mPatchPath = new SearchPath()
1608                                    .addFiles(v.substring(eq + 1));
1609                            boolean ok = true;
1610                            for (Path p : mPatchPath) {
1611                                Path mi = p.resolve("module-info.class");
1612                                if (Files.exists(mi)) {
1613                                    log.error(Errors.LocnModuleInfoNotAllowedOnPatchPath(mi));
1614                                    ok = false;
1615                                }
1616                            }
1617                            if (ok) {
1618                                if (patchMap == null) {
1619                                    patchMap = new LinkedHashMap<>();
1620                                }
1621                                patchMap.put(mName, mPatchPath);
1622                            }
1623                        } else {
1624                            // Should not be able to get here;
1625                            // this should be caught and handled in Option.PATCH_MODULE
1626                            log.error(Errors.LocnInvalidArgForXpatch(value));
1627                        }
1628                    }
1629                }
1630                return true;
1631            default:
1632                LocationHandler h = handlersForOption.get(option);
1633                return (h == null ? false : h.handleOption(option, value));
1634        }
1635    }
1636
1637    boolean hasLocation(Location location) {
1638        LocationHandler h = getHandler(location);
1639        return (h == null ? false : h.isSet());
1640    }
1641
1642    Collection<Path> getLocation(Location location) {
1643        LocationHandler h = getHandler(location);
1644        return (h == null ? null : h.getPaths());
1645    }
1646
1647    Path getOutputLocation(Location location) {
1648        if (!location.isOutputLocation()) {
1649            throw new IllegalArgumentException();
1650        }
1651        LocationHandler h = getHandler(location);
1652        return ((OutputLocationHandler) h).outputDir;
1653    }
1654
1655    void setLocation(Location location, Iterable<? extends Path> files) throws IOException {
1656        LocationHandler h = getHandler(location);
1657        if (h == null) {
1658            if (location.isOutputLocation()) {
1659                h = new OutputLocationHandler(location);
1660            } else {
1661                h = new SimpleLocationHandler(location);
1662            }
1663            handlersForLocation.put(location, h);
1664        }
1665        h.setPaths(files);
1666    }
1667
1668    Location getLocationForModule(Location location, String name) throws IOException {
1669        LocationHandler h = getHandler(location);
1670        return (h == null ? null : h.getLocationForModule(name));
1671    }
1672
1673    Location getLocationForModule(Location location, Path dir) {
1674        LocationHandler h = getHandler(location);
1675        return (h == null ? null : h.getLocationForModule(dir));
1676    }
1677
1678    String inferModuleName(Location location) {
1679        LocationHandler h = getHandler(location);
1680        return (h == null ? null : h.inferModuleName());
1681    }
1682
1683    Iterable<Set<Location>> listLocationsForModules(Location location) throws IOException {
1684        LocationHandler h = getHandler(location);
1685        return (h == null ? null : h.listLocationsForModules());
1686    }
1687
1688    protected LocationHandler getHandler(Location location) {
1689        Objects.requireNonNull(location);
1690        return (location instanceof LocationHandler)
1691                ? (LocationHandler) location
1692                : handlersForLocation.get(location);
1693    }
1694
1695    /**
1696     * Is this the name of an archive file?
1697     */
1698    private boolean isArchive(Path file) {
1699        String n = StringUtils.toLowerCase(file.getFileName().toString());
1700        return fsInfo.isFile(file)
1701                && (n.endsWith(".jar") || n.endsWith(".zip"));
1702    }
1703
1704    static Path normalize(Path p) {
1705        try {
1706            return p.toRealPath();
1707        } catch (IOException e) {
1708            return p.toAbsolutePath().normalize();
1709        }
1710    }
1711
1712}
1713