Locations.java revision 3902:63141c3a65a6
1170159Sariff/*
2170159Sariff * Copyright (c) 2003, 2017, Oracle and/or its affiliates. All rights reserved.
3170159Sariff * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4170159Sariff *
5170159Sariff * This code is free software; you can redistribute it and/or modify it
6170159Sariff * under the terms of the GNU General Public License version 2 only, as
7170159Sariff * published by the Free Software Foundation.  Oracle designates this
8170159Sariff * particular file as subject to the "Classpath" exception as provided
9170159Sariff * by Oracle in the LICENSE file that accompanied this code.
10170159Sariff *
11170159Sariff * This code is distributed in the hope that it will be useful, but WITHOUT
12170159Sariff * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13170159Sariff * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
14170159Sariff * version 2 for more details (a copy is included in the LICENSE file that
15170159Sariff * accompanied this code).
16170159Sariff *
17170159Sariff * You should have received a copy of the GNU General Public License version
18170159Sariff * 2 along with this work; if not, write to the Free Software Foundation,
19170159Sariff * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
20170159Sariff *
21170159Sariff * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
22170159Sariff * or visit www.oracle.com if you need additional information or have any
23170159Sariff * questions.
24170159Sariff */
25170159Sariff
26170159Sariffpackage com.sun.tools.javac.file;
27170159Sariff
28170159Sariffimport java.io.Closeable;
29170159Sariffimport java.io.File;
30170159Sariffimport java.io.FileNotFoundException;
31170159Sariffimport java.io.IOException;
32170159Sariffimport java.io.UncheckedIOException;
33170159Sariffimport java.net.URI;
34170159Sariffimport java.net.URL;
35170159Sariffimport java.net.URLClassLoader;
36193640Sariffimport java.nio.file.DirectoryIteratorException;
37193640Sariffimport java.nio.file.DirectoryStream;
38193640Sariffimport java.nio.file.FileSystem;
39193640Sariffimport java.nio.file.FileSystemNotFoundException;
40170159Sariffimport java.nio.file.FileSystems;
41170159Sariffimport java.nio.file.Files;
42170159Sariffimport java.nio.file.InvalidPathException;
43170159Sariffimport java.nio.file.Path;
44170159Sariffimport java.nio.file.Paths;
45170159Sariffimport java.nio.file.ProviderNotFoundException;
46170159Sariffimport java.util.ArrayList;
47170159Sariffimport java.util.Arrays;
48170159Sariffimport java.util.Collection;
49170159Sariffimport java.util.Collections;
50170159Sariffimport java.util.EnumMap;
51170159Sariffimport java.util.EnumSet;
52170159Sariffimport java.util.HashMap;
53170159Sariffimport java.util.HashSet;
54170159Sariffimport java.util.Iterator;
55170159Sariffimport java.util.LinkedHashMap;
56170159Sariffimport java.util.LinkedHashSet;
57170159Sariffimport java.util.List;
58170159Sariffimport java.util.Map;
59170159Sariffimport java.util.Objects;
60170159Sariffimport java.util.NoSuchElementException;
61170159Sariffimport java.util.Set;
62170159Sariffimport java.util.regex.Matcher;
63170159Sariffimport java.util.regex.Pattern;
64170159Sariffimport java.util.stream.Collectors;
65170159Sariffimport java.util.stream.Stream;
66170159Sariff
67170159Sariffimport javax.lang.model.SourceVersion;
68170159Sariffimport javax.tools.JavaFileManager;
69170159Sariffimport javax.tools.JavaFileManager.Location;
70170159Sariffimport javax.tools.JavaFileObject;
71170159Sariffimport javax.tools.StandardJavaFileManager;
72170159Sariffimport javax.tools.StandardJavaFileManager.PathFactory;
73170159Sariffimport javax.tools.StandardLocation;
74170159Sariff
75170159Sariffimport com.sun.tools.javac.code.Lint;
76170159Sariffimport com.sun.tools.javac.code.Lint.LintCategory;
77170159Sariffimport com.sun.tools.javac.main.Option;
78170159Sariffimport com.sun.tools.javac.resources.CompilerProperties.Errors;
79170159Sariffimport com.sun.tools.javac.resources.CompilerProperties.Warnings;
80170159Sariffimport com.sun.tools.javac.util.DefinedBy;
81170159Sariffimport com.sun.tools.javac.util.DefinedBy.Api;
82170159Sariffimport com.sun.tools.javac.util.JDK9Wrappers;
83170159Sariffimport com.sun.tools.javac.util.ListBuffer;
84170159Sariffimport com.sun.tools.javac.util.Log;
85170159Sariffimport com.sun.tools.javac.jvm.ModuleNameReader;
86170159Sariffimport com.sun.tools.javac.util.Pair;
87170159Sariffimport com.sun.tools.javac.util.StringUtils;
88170159Sariff
89170159Sariffimport static javax.tools.StandardLocation.PLATFORM_CLASS_PATH;
90170159Sariff
91170159Sariffimport static com.sun.tools.javac.main.Option.BOOT_CLASS_PATH;
92170159Sariffimport static com.sun.tools.javac.main.Option.DJAVA_ENDORSED_DIRS;
93170159Sariffimport static com.sun.tools.javac.main.Option.DJAVA_EXT_DIRS;
94170159Sariffimport static com.sun.tools.javac.main.Option.ENDORSEDDIRS;
95193640Sariffimport static com.sun.tools.javac.main.Option.EXTDIRS;
96170159Sariffimport static com.sun.tools.javac.main.Option.XBOOTCLASSPATH;
97193640Sariffimport static com.sun.tools.javac.main.Option.XBOOTCLASSPATH_APPEND;
98170159Sariffimport static com.sun.tools.javac.main.Option.XBOOTCLASSPATH_PREPEND;
99170159Sariff
100170159Sariff/**
101170159Sariff * This class converts command line arguments, environment variables and system properties (in
102170159Sariff * File.pathSeparator-separated String form) into a boot class path, user class path, and source
103170159Sariff * path (in {@code Collection<String>} form).
104170159Sariff *
105170159Sariff * <p>
106170159Sariff * <b>This is NOT part of any supported API. If you write code that depends on this, you do so at
107170159Sariff * your own risk. This code and its internal interfaces are subject to change or deletion without
108170159Sariff * notice.</b>
109170159Sariff */
110170159Sariffpublic class Locations {
111170159Sariff
112170159Sariff    /**
113170159Sariff     * The log to use for warning output
114170159Sariff     */
115170159Sariff    private Log log;
116170159Sariff
117170159Sariff    /**
118170159Sariff     * Access to (possibly cached) file info
119170159Sariff     */
120170159Sariff    private FSInfo fsInfo;
121170159Sariff
122170159Sariff    /**
123170159Sariff     * Whether to warn about non-existent path elements
124170159Sariff     */
125170159Sariff    private boolean warn;
126170159Sariff
127170159Sariff    private ModuleNameReader moduleNameReader;
128170159Sariff
129170159Sariff    private PathFactory pathFactory = Paths::get;
130170159Sariff
131170159Sariff    static final Path javaHome = FileSystems.getDefault().getPath(System.getProperty("java.home"));
132170159Sariff    static final Path thisSystemModules = javaHome.resolve("lib").resolve("modules");
133170159Sariff
134170159Sariff    Map<Path, FileSystem> fileSystems = new LinkedHashMap<>();
135170159Sariff    List<Closeable> closeables = new ArrayList<>();
136170159Sariff    private Map<String,String> fsEnv = Collections.emptyMap();
137170159Sariff
138170159Sariff    Locations() {
139170159Sariff        initHandlers();
140170159Sariff    }
141170159Sariff
142170159Sariff    Path getPath(String first, String... more) {
143170289Sdwmalone        try {
144170159Sariff            return pathFactory.getPath(first, more);
145170159Sariff        } catch (InvalidPathException ipe) {
146170159Sariff            throw new IllegalArgumentException(ipe);
147170159Sariff        }
148170159Sariff    }
149170159Sariff
150170159Sariff    public void close() throws IOException {
151170159Sariff        ListBuffer<IOException> list = new ListBuffer<>();
152170159Sariff        closeables.forEach(closeable -> {
153170159Sariff            try {
154170159Sariff                closeable.close();
155170159Sariff            } catch (IOException ex) {
156170159Sariff                list.add(ex);
157170159Sariff            }
158170159Sariff        });
159170159Sariff        if (list.nonEmpty()) {
160170159Sariff            IOException ex = new IOException();
161170159Sariff            for (IOException e: list)
162170159Sariff                ex.addSuppressed(e);
163170159Sariff            throw ex;
164170159Sariff        }
165170719Sariff    }
166170159Sariff
167170159Sariff    void update(Log log, boolean warn, FSInfo fsInfo) {
168170719Sariff        this.log = log;
169170159Sariff        this.warn = warn;
170170159Sariff        this.fsInfo = fsInfo;
171170159Sariff    }
172170159Sariff
173170159Sariff    void setPathFactory(PathFactory f) {
174170159Sariff        pathFactory = f;
175170159Sariff    }
176170159Sariff
177170159Sariff    boolean isDefaultBootClassPath() {
178170159Sariff        BootClassPathLocationHandler h
179170159Sariff                = (BootClassPathLocationHandler) getHandler(PLATFORM_CLASS_PATH);
180170159Sariff        return h.isDefault();
181170159Sariff    }
182170159Sariff
183170159Sariff    /**
184170159Sariff     * Split a search path into its elements. Empty path elements will be ignored.
185170159Sariff     *
186170159Sariff     * @param searchPath The search path to be split
187170159Sariff     * @return The elements of the path
188170159Sariff     */
189170159Sariff    private Iterable<Path> getPathEntries(String searchPath) {
190170159Sariff        return getPathEntries(searchPath, null);
191170159Sariff    }
192170159Sariff
193170159Sariff    /**
194170159Sariff     * Split a search path into its elements. If emptyPathDefault is not null, all empty elements in the
195170159Sariff     * path, including empty elements at either end of the path, will be replaced with the value of
196170159Sariff     * emptyPathDefault.
197170159Sariff     *
198170159Sariff     * @param searchPath The search path to be split
199170159Sariff     * @param emptyPathDefault The value to substitute for empty path elements, or null, to ignore
200170159Sariff     * empty path elements
201170159Sariff     * @return The elements of the path
202170159Sariff     */
203170159Sariff    private Iterable<Path> getPathEntries(String searchPath, Path emptyPathDefault) {
204170159Sariff        ListBuffer<Path> entries = new ListBuffer<>();
205170159Sariff        for (String s: searchPath.split(Pattern.quote(File.pathSeparator), -1)) {
206170159Sariff            if (s.isEmpty()) {
207170159Sariff                if (emptyPathDefault != null) {
208170159Sariff                    entries.add(emptyPathDefault);
209170159Sariff                }
210170159Sariff            } else {
211170159Sariff                try {
212170159Sariff                    entries.add(getPath(s));
213170159Sariff                } catch (IllegalArgumentException e) {
214170159Sariff                    if (warn) {
215170159Sariff                        log.warning(LintCategory.PATH, "invalid.path", s);
216170159Sariff                    }
217170159Sariff                }
218170159Sariff            }
219170159Sariff        }
220170159Sariff        return entries;
221170159Sariff    }
222170159Sariff
223170159Sariff    public void setMultiReleaseValue(String multiReleaseValue) {
224170159Sariff        fsEnv = Collections.singletonMap("multi-release", multiReleaseValue);
225170159Sariff    }
226170159Sariff
227170159Sariff    /**
228170159Sariff     * Utility class to help evaluate a path option. Duplicate entries are ignored, jar class paths
229170159Sariff     * can be expanded.
230170159Sariff     */
231170159Sariff    private class SearchPath extends LinkedHashSet<Path> {
232170159Sariff
233170159Sariff        private static final long serialVersionUID = 0;
234170159Sariff
235170159Sariff        private boolean expandJarClassPaths = false;
236170159Sariff        private final Set<Path> canonicalValues = new HashSet<>();
237170159Sariff
238170159Sariff        public SearchPath expandJarClassPaths(boolean x) {
239170159Sariff            expandJarClassPaths = x;
240170159Sariff            return this;
241170159Sariff        }
242170159Sariff
243170159Sariff        /**
244170159Sariff         * What to use when path element is the empty string
245170159Sariff         */
246170159Sariff        private Path emptyPathDefault = null;
247170159Sariff
248170159Sariff        public SearchPath emptyPathDefault(Path x) {
249170159Sariff            emptyPathDefault = x;
250170159Sariff            return this;
251170159Sariff        }
252170159Sariff
253170159Sariff        public SearchPath addDirectories(String dirs, boolean warn) {
254170159Sariff            boolean prev = expandJarClassPaths;
255170159Sariff            expandJarClassPaths = true;
256170159Sariff            try {
257170159Sariff                if (dirs != null) {
258170159Sariff                    for (Path dir : getPathEntries(dirs)) {
259170159Sariff                        addDirectory(dir, warn);
260170159Sariff                    }
261170159Sariff                }
262170159Sariff                return this;
263170159Sariff            } finally {
264170159Sariff                expandJarClassPaths = prev;
265170159Sariff            }
266170159Sariff        }
267170159Sariff
268170159Sariff        public SearchPath addDirectories(String dirs) {
269170159Sariff            return addDirectories(dirs, warn);
270170159Sariff        }
271170159Sariff
272170159Sariff        private void addDirectory(Path dir, boolean warn) {
273170159Sariff            if (!Files.isDirectory(dir)) {
274170159Sariff                if (warn) {
275170159Sariff                    log.warning(Lint.LintCategory.PATH,
276170159Sariff                            "dir.path.element.not.found", dir);
277170159Sariff                }
278170159Sariff                return;
279170159Sariff            }
280170159Sariff
281170159Sariff            try (Stream<Path> s = Files.list(dir)) {
282170159Sariff                s.filter(Locations.this::isArchive)
283170159Sariff                        .forEach(dirEntry -> addFile(dirEntry, warn));
284170159Sariff            } catch (IOException ignore) {
285170159Sariff            }
286170159Sariff        }
287170159Sariff
288170159Sariff        public SearchPath addFiles(String files, boolean warn) {
289170159Sariff            if (files != null) {
290170159Sariff                addFiles(getPathEntries(files, emptyPathDefault), warn);
291170159Sariff            }
292170159Sariff            return this;
293170159Sariff        }
294170159Sariff
295170159Sariff        public SearchPath addFiles(String files) {
296170159Sariff            return addFiles(files, warn);
297170159Sariff        }
298170159Sariff
299170159Sariff        public SearchPath addFiles(Iterable<? extends Path> files, boolean warn) {
300170159Sariff            if (files != null) {
301170159Sariff                for (Path file : files) {
302170159Sariff                    addFile(file, warn);
303170159Sariff                }
304170159Sariff            }
305170159Sariff            return this;
306170159Sariff        }
307170159Sariff
308170159Sariff        public SearchPath addFiles(Iterable<? extends Path> files) {
309170159Sariff            return addFiles(files, warn);
310170159Sariff        }
311170159Sariff
312170159Sariff        public void addFile(Path file, boolean warn) {
313170159Sariff            if (contains(file)) {
314170159Sariff                // discard duplicates
315170159Sariff                return;
316170159Sariff            }
317170159Sariff
318170159Sariff            if (!fsInfo.exists(file)) {
319170159Sariff                /* No such file or directory exists */
320170159Sariff                if (warn) {
321170159Sariff                    log.warning(Lint.LintCategory.PATH,
322170159Sariff                            "path.element.not.found", file);
323170159Sariff                }
324170159Sariff                super.add(file);
325170159Sariff                return;
326170159Sariff            }
327170159Sariff
328170159Sariff            Path canonFile = fsInfo.getCanonicalFile(file);
329170159Sariff            if (canonicalValues.contains(canonFile)) {
330170159Sariff                /* Discard duplicates and avoid infinite recursion */
331170159Sariff                return;
332170159Sariff            }
333170159Sariff
334170159Sariff            if (fsInfo.isFile(file)) {
335170159Sariff                /* File is an ordinary file. */
336170159Sariff                if (   !file.getFileName().toString().endsWith(".jmod")
337170159Sariff                    && !file.endsWith("modules")) {
338170159Sariff                    if (!isArchive(file)) {
339170159Sariff                        /* Not a recognized extension; open it to see if
340170159Sariff                         it looks like a valid zip file. */
341170159Sariff                        try {
342170159Sariff                            FileSystems.newFileSystem(file, null).close();
343170159Sariff                            if (warn) {
344170159Sariff                                log.warning(Lint.LintCategory.PATH,
345170159Sariff                                        "unexpected.archive.file", file);
346170159Sariff                            }
347170159Sariff                        } catch (IOException | ProviderNotFoundException e) {
348170159Sariff                            // FIXME: include e.getLocalizedMessage in warning
349170159Sariff                            if (warn) {
350170159Sariff                                log.warning(Lint.LintCategory.PATH,
351170159Sariff                                        "invalid.archive.file", file);
352170159Sariff                            }
353170159Sariff                            return;
354170159Sariff                        }
355170159Sariff                    } else {
356170159Sariff                        if (fsInfo.getJarFSProvider() == null) {
357170159Sariff                            log.error(Errors.NoZipfsForArchive(file));
358170159Sariff                            return ;
359170159Sariff                        }
360170159Sariff                    }
361170159Sariff                }
362170159Sariff            }
363170159Sariff
364170159Sariff            /* Now what we have left is either a directory or a file name
365170159Sariff             conforming to archive naming convention */
366170159Sariff            super.add(file);
367170159Sariff            canonicalValues.add(canonFile);
368170159Sariff
369170159Sariff            if (expandJarClassPaths && fsInfo.isFile(file) && !file.endsWith("modules")) {
370170159Sariff                addJarClassPath(file, warn);
371170159Sariff            }
372170159Sariff        }
373170159Sariff
374170159Sariff        // Adds referenced classpath elements from a jar's Class-Path
375170159Sariff        // Manifest entry.  In some future release, we may want to
376170159Sariff        // update this code to recognize URLs rather than simple
377170159Sariff        // filenames, but if we do, we should redo all path-related code.
378170159Sariff        private void addJarClassPath(Path jarFile, boolean warn) {
379170159Sariff            try {
380170159Sariff                for (Path f : fsInfo.getJarClassPath(jarFile)) {
381170159Sariff                    addFile(f, warn);
382170159Sariff                }
383170159Sariff            } catch (IOException e) {
384170159Sariff                log.error("error.reading.file", jarFile, JavacFileManager.getMessage(e));
385170159Sariff            }
386170159Sariff        }
387170159Sariff    }
388170159Sariff
389170159Sariff    /**
390170159Sariff     * Base class for handling support for the representation of Locations.
391170159Sariff     *
392170159Sariff     * Locations are (by design) opaque handles that can easily be implemented
393170159Sariff     * by enums like StandardLocation. Within JavacFileManager, each Location
394170159Sariff     * has an associated LocationHandler, which provides much of the appropriate
395170159Sariff     * functionality for the corresponding Location.
396170159Sariff     *
397170159Sariff     * @see #initHandlers
398170159Sariff     * @see #getHandler
399170159Sariff     */
400170159Sariff    protected abstract class LocationHandler {
401170159Sariff
402170159Sariff        /**
403170159Sariff         * @see JavaFileManager#handleOption
404170159Sariff         */
405170159Sariff        abstract boolean handleOption(Option option, String value);
406170159Sariff
407170159Sariff        /**
408170159Sariff         * @see StandardJavaFileManager#hasLocation
409170159Sariff         */
410170159Sariff        boolean isSet() {
411170159Sariff            return (getPaths() != null);
412170159Sariff        }
413170159Sariff
414170159Sariff        /**
415170159Sariff         * @see StandardJavaFileManager#getLocation
416170159Sariff         */
417170159Sariff        abstract Collection<Path> getPaths();
418170159Sariff
419170159Sariff        /**
420170159Sariff         * @see StandardJavaFileManager#setLocation
421170159Sariff         */
422170159Sariff        abstract void setPaths(Iterable<? extends Path> files) throws IOException;
423170159Sariff
424170159Sariff        /**
425170159Sariff         * @see JavaFileManager#getLocationForModule(Location, String)
426170159Sariff         */
427170159Sariff        Location getLocationForModule(String moduleName) throws IOException {
428170159Sariff            return null;
429170159Sariff        }
430170159Sariff
431170159Sariff        /**
432170159Sariff         * @see JavaFileManager#getLocationForModule(Location, JavaFileObject, String)
433170159Sariff         */
434170159Sariff        Location getLocationForModule(Path dir) {
435170159Sariff            return null;
436170159Sariff        }
437170159Sariff
438170159Sariff        /**
439170159Sariff         * @see JavaFileManager#inferModuleName
440170159Sariff         */
441170159Sariff        String inferModuleName() {
442170159Sariff            return null;
443170159Sariff        }
444170159Sariff
445170159Sariff        /**
446170159Sariff         * @see JavaFileManager#listLocationsForModules
447170159Sariff         */
448170159Sariff        Iterable<Set<Location>> listLocationsForModules() throws IOException {
449170159Sariff            return null;
450170159Sariff        }
451170159Sariff    }
452170159Sariff
453170159Sariff    /**
454170719Sariff     * A LocationHandler for a given Location, and associated set of options.
455170159Sariff     */
456170159Sariff    private abstract class BasicLocationHandler extends LocationHandler {
457170159Sariff
458170719Sariff        final Location location;
459170719Sariff        final Set<Option> options;
460170719Sariff
461170159Sariff        /**
462170159Sariff         * Create a handler. The location and options provide a way to map from a location or an
463170159Sariff         * option to the corresponding handler.
464170719Sariff         *
465170159Sariff         * @param location the location for which this is the handler
466170159Sariff         * @param options the options affecting this location
467170159Sariff         * @see #initHandlers
468170159Sariff         */
469170159Sariff        protected BasicLocationHandler(Location location, Option... options) {
470170159Sariff            this.location = location;
471170159Sariff            this.options = options.length == 0
472170159Sariff                    ? EnumSet.noneOf(Option.class)
473170159Sariff                    : EnumSet.copyOf(Arrays.asList(options));
474170159Sariff        }
475170159Sariff    }
476170159Sariff
477170159Sariff    /**
478170159Sariff     * General purpose implementation for output locations, such as -d/CLASS_OUTPUT and
479170159Sariff     * -s/SOURCE_OUTPUT. All options are treated as equivalent (i.e. aliases.)
480170159Sariff     * The value is a single file, possibly null.
481170159Sariff     */
482170159Sariff    private class OutputLocationHandler extends BasicLocationHandler {
483170159Sariff
484170159Sariff        private Path outputDir;
485170159Sariff        private Map<String, Location> moduleLocations;
486170159Sariff        private Map<Path, Location> pathLocations;
487170159Sariff
488170159Sariff        OutputLocationHandler(Location location, Option... options) {
489170159Sariff            super(location, options);
490170159Sariff        }
491170159Sariff
492170159Sariff        @Override
493170159Sariff        boolean handleOption(Option option, String value) {
494170159Sariff            if (!options.contains(option)) {
495170159Sariff                return false;
496170159Sariff            }
497170159Sariff
498170159Sariff            // TODO: could/should validate outputDir exists and is a directory
499170159Sariff            // need to decide how best to report issue for benefit of
500170159Sariff            // direct API call on JavaFileManager.handleOption(specifies IAE)
501170159Sariff            // vs. command line decoding.
502170159Sariff            outputDir = (value == null) ? null : getPath(value);
503170159Sariff            return true;
504170159Sariff        }
505170159Sariff
506170159Sariff        @Override
507170159Sariff        Collection<Path> getPaths() {
508170159Sariff            return (outputDir == null) ? null : Collections.singleton(outputDir);
509170159Sariff        }
510170159Sariff
511170159Sariff        @Override
512170159Sariff        void setPaths(Iterable<? extends Path> files) throws IOException {
513170159Sariff            if (files == null) {
514170159Sariff                outputDir = null;
515170159Sariff            } else {
516170159Sariff                Iterator<? extends Path> pathIter = files.iterator();
517170159Sariff                if (!pathIter.hasNext()) {
518170159Sariff                    throw new IllegalArgumentException("empty path for directory");
519170159Sariff                }
520170159Sariff                Path dir = pathIter.next();
521170159Sariff                if (pathIter.hasNext()) {
522170159Sariff                    throw new IllegalArgumentException("path too long for directory");
523170159Sariff                }
524170159Sariff                if (!Files.exists(dir)) {
525170159Sariff                    throw new FileNotFoundException(dir + ": does not exist");
526170159Sariff                } else if (!Files.isDirectory(dir)) {
527170159Sariff                    throw new IOException(dir + ": not a directory");
528170159Sariff                }
529170159Sariff                outputDir = dir;
530170159Sariff            }
531170159Sariff            moduleLocations = null;
532170159Sariff            pathLocations = null;
533170159Sariff        }
534170159Sariff
535170159Sariff        @Override
536170159Sariff        Location getLocationForModule(String name) {
537170159Sariff            if (moduleLocations == null) {
538170159Sariff                moduleLocations = new HashMap<>();
539170159Sariff                pathLocations = new HashMap<>();
540170159Sariff            }
541170159Sariff            Location l = moduleLocations.get(name);
542170159Sariff            if (l == null) {
543170159Sariff                Path out = outputDir.resolve(name);
544170159Sariff                l = new ModuleLocationHandler(location.getName() + "[" + name + "]",
545170159Sariff                        name,
546170159Sariff                        Collections.singleton(out),
547170159Sariff                        true, false);
548170159Sariff                moduleLocations.put(name, l);
549170159Sariff                pathLocations.put(out.toAbsolutePath(), l);
550170159Sariff           }
551170159Sariff            return l;
552170159Sariff        }
553170159Sariff
554170159Sariff        @Override
555170159Sariff        Location getLocationForModule(Path dir) {
556170159Sariff            return (pathLocations == null) ? null : pathLocations.get(dir);
557170159Sariff        }
558170159Sariff
559170159Sariff        private boolean listed;
560170159Sariff
561170159Sariff        @Override
562170159Sariff        Iterable<Set<Location>> listLocationsForModules() throws IOException {
563170159Sariff            if (!listed && outputDir != null) {
564170159Sariff                try (DirectoryStream<Path> stream = Files.newDirectoryStream(outputDir)) {
565170159Sariff                    for (Path p : stream) {
566170159Sariff                        getLocationForModule(p.getFileName().toString());
567170159Sariff                    }
568170159Sariff                }
569170159Sariff                listed = true;
570170159Sariff            }
571170159Sariff            if (moduleLocations == null)
572170159Sariff                return Collections.emptySet();
573170159Sariff            Set<Location> locns = new LinkedHashSet<>();
574170159Sariff            moduleLocations.forEach((k, v) -> locns.add(v));
575170159Sariff            return Collections.singleton(locns);
576170159Sariff        }
577170159Sariff    }
578170159Sariff
579170159Sariff    /**
580170159Sariff     * General purpose implementation for search path locations,
581170159Sariff     * such as -sourcepath/SOURCE_PATH and -processorPath/ANNOTATION_PROCESSOR_PATH.
582170159Sariff     * All options are treated as equivalent (i.e. aliases.)
583170159Sariff     * The value is an ordered set of files and/or directories.
584170159Sariff     */
585170159Sariff    private class SimpleLocationHandler extends BasicLocationHandler {
586170159Sariff
587170159Sariff        protected Collection<Path> searchPath;
588170159Sariff
589170159Sariff        SimpleLocationHandler(Location location, Option... options) {
590170159Sariff            super(location, options);
591170159Sariff        }
592170159Sariff
593170159Sariff        @Override
594170159Sariff        boolean handleOption(Option option, String value) {
595170159Sariff            if (!options.contains(option)) {
596170159Sariff                return false;
597170159Sariff            }
598170159Sariff            searchPath = value == null ? null
599170159Sariff                    : Collections.unmodifiableCollection(createPath().addFiles(value));
600170159Sariff            return true;
601170159Sariff        }
602170159Sariff
603170159Sariff        @Override
604170159Sariff        Collection<Path> getPaths() {
605170159Sariff            return searchPath;
606170159Sariff        }
607170159Sariff
608170159Sariff        @Override
609170159Sariff        void setPaths(Iterable<? extends Path> files) {
610170159Sariff            SearchPath p;
611170159Sariff            if (files == null) {
612170159Sariff                p = computePath(null);
613170159Sariff            } else {
614170159Sariff                p = createPath().addFiles(files);
615170159Sariff            }
616170159Sariff            searchPath = Collections.unmodifiableCollection(p);
617170159Sariff        }
618170159Sariff
619170159Sariff        protected SearchPath computePath(String value) {
620170159Sariff            return createPath().addFiles(value);
621170159Sariff        }
622170159Sariff
623170159Sariff        protected SearchPath createPath() {
624170159Sariff            return new SearchPath();
625170159Sariff        }
626170159Sariff    }
627170159Sariff
628170159Sariff    /**
629170159Sariff     * Subtype of SimpleLocationHandler for -classpath/CLASS_PATH.
630170159Sariff     * If no value is given, a default is provided, based on system properties and other values.
631170159Sariff     */
632170159Sariff    private class ClassPathLocationHandler extends SimpleLocationHandler {
633170159Sariff
634170159Sariff        ClassPathLocationHandler() {
635170159Sariff            super(StandardLocation.CLASS_PATH, Option.CLASS_PATH);
636170159Sariff        }
637170159Sariff
638170159Sariff        @Override
639170159Sariff        Collection<Path> getPaths() {
640170159Sariff            lazy();
641170159Sariff            return searchPath;
642170159Sariff        }
643170159Sariff
644170159Sariff        @Override
645170159Sariff        protected SearchPath computePath(String value) {
646170159Sariff            String cp = value;
647170159Sariff
648170159Sariff            // CLASSPATH environment variable when run from `javac'.
649170159Sariff            if (cp == null) {
650170159Sariff                cp = System.getProperty("env.class.path");
651170159Sariff            }
652170159Sariff
653170159Sariff            // If invoked via a java VM (not the javac launcher), use the
654170159Sariff            // platform class path
655170159Sariff            if (cp == null && System.getProperty("application.home") == null) {
656170159Sariff                cp = System.getProperty("java.class.path");
657170159Sariff            }
658170159Sariff
659170159Sariff            // Default to current working directory.
660170159Sariff            if (cp == null) {
661170159Sariff                cp = ".";
662170159Sariff            }
663170159Sariff
664170159Sariff            return createPath().addFiles(cp);
665170159Sariff        }
666170159Sariff
667170159Sariff        @Override
668170159Sariff        protected SearchPath createPath() {
669170159Sariff            return new SearchPath()
670170159Sariff                    .expandJarClassPaths(true) // Only search user jars for Class-Paths
671170159Sariff                    .emptyPathDefault(getPath("."));  // Empty path elt ==> current directory
672170159Sariff        }
673170159Sariff
674170159Sariff        private void lazy() {
675170159Sariff            if (searchPath == null) {
676170159Sariff                setPaths(null);
677170159Sariff            }
678170159Sariff        }
679170159Sariff    }
680170159Sariff
681170159Sariff    /**
682170159Sariff     * Custom subtype of LocationHandler for PLATFORM_CLASS_PATH.
683170159Sariff     * Various options are supported for different components of the
684170159Sariff     * platform class path.
685170159Sariff     * Setting a value with setLocation overrides all existing option values.
686170159Sariff     * Setting any option overrides any value set with setLocation, and
687170159Sariff     * reverts to using default values for options that have not been set.
688170159Sariff     * Setting -bootclasspath or -Xbootclasspath overrides any existing
689170159Sariff     * value for -Xbootclasspath/p: and -Xbootclasspath/a:.
690170159Sariff     */
691170159Sariff    private class BootClassPathLocationHandler extends BasicLocationHandler {
692170159Sariff
693170159Sariff        private Collection<Path> searchPath;
694170159Sariff        final Map<Option, String> optionValues = new EnumMap<>(Option.class);
695170159Sariff
696170159Sariff        /**
697170159Sariff         * Is the bootclasspath the default?
698170159Sariff         */
699170159Sariff        private boolean isDefault;
700170159Sariff
701170159Sariff        BootClassPathLocationHandler() {
702170159Sariff            super(StandardLocation.PLATFORM_CLASS_PATH,
703170159Sariff                    Option.BOOT_CLASS_PATH, Option.XBOOTCLASSPATH,
704170159Sariff                    Option.XBOOTCLASSPATH_PREPEND,
705170159Sariff                    Option.XBOOTCLASSPATH_APPEND,
706170159Sariff                    Option.ENDORSEDDIRS, Option.DJAVA_ENDORSED_DIRS,
707170159Sariff                    Option.EXTDIRS, Option.DJAVA_EXT_DIRS);
708170159Sariff        }
709170159Sariff
710170159Sariff        boolean isDefault() {
711170159Sariff            lazy();
712170159Sariff            return isDefault;
713170159Sariff        }
714170159Sariff
715170159Sariff        @Override
716170159Sariff        boolean handleOption(Option option, String value) {
717170159Sariff            if (!options.contains(option)) {
718170159Sariff                return false;
719170159Sariff            }
720170159Sariff
721170159Sariff            option = canonicalize(option);
722170159Sariff            optionValues.put(option, value);
723170159Sariff            if (option == BOOT_CLASS_PATH) {
724170159Sariff                optionValues.remove(XBOOTCLASSPATH_PREPEND);
725170159Sariff                optionValues.remove(XBOOTCLASSPATH_APPEND);
726170159Sariff            }
727170159Sariff            searchPath = null;  // reset to "uninitialized"
728170159Sariff            return true;
729170159Sariff        }
730170159Sariff        // where
731170159Sariff        // TODO: would be better if option aliasing was handled at a higher
732170159Sariff        // level
733170159Sariff        private Option canonicalize(Option option) {
734170159Sariff            switch (option) {
735170719Sariff                case XBOOTCLASSPATH:
736170719Sariff                    return Option.BOOT_CLASS_PATH;
737170719Sariff                case DJAVA_ENDORSED_DIRS:
738170719Sariff                    return Option.ENDORSEDDIRS;
739170719Sariff                case DJAVA_EXT_DIRS:
740170719Sariff                    return Option.EXTDIRS;
741170719Sariff                default:
742170159Sariff                    return option;
743170719Sariff            }
744170719Sariff        }
745170159Sariff
746170159Sariff        @Override
747170159Sariff        Collection<Path> getPaths() {
748170159Sariff            lazy();
749170159Sariff            return searchPath;
750170159Sariff        }
751170159Sariff
752170159Sariff        @Override
753170159Sariff        void setPaths(Iterable<? extends Path> files) {
754170159Sariff            if (files == null) {
755170159Sariff                searchPath = null;  // reset to "uninitialized"
756170159Sariff            } else {
757170159Sariff                isDefault = false;
758170159Sariff                SearchPath p = new SearchPath().addFiles(files, false);
759170159Sariff                searchPath = Collections.unmodifiableCollection(p);
760170159Sariff                optionValues.clear();
761170159Sariff            }
762170159Sariff        }
763170159Sariff
764170159Sariff        SearchPath computePath() throws IOException {
765170159Sariff            SearchPath path = new SearchPath();
766170159Sariff
767170159Sariff            String bootclasspathOpt = optionValues.get(BOOT_CLASS_PATH);
768170159Sariff            String endorseddirsOpt = optionValues.get(ENDORSEDDIRS);
769170159Sariff            String extdirsOpt = optionValues.get(EXTDIRS);
770170159Sariff            String xbootclasspathPrependOpt = optionValues.get(XBOOTCLASSPATH_PREPEND);
771170159Sariff            String xbootclasspathAppendOpt = optionValues.get(XBOOTCLASSPATH_APPEND);
772170159Sariff            path.addFiles(xbootclasspathPrependOpt);
773170159Sariff
774170159Sariff            if (endorseddirsOpt != null) {
775170159Sariff                path.addDirectories(endorseddirsOpt);
776170159Sariff            } else {
777170159Sariff                path.addDirectories(System.getProperty("java.endorsed.dirs"), false);
778170159Sariff            }
779170159Sariff
780170159Sariff            if (bootclasspathOpt != null) {
781170159Sariff                path.addFiles(bootclasspathOpt);
782170159Sariff            } else {
783170159Sariff                // Standard system classes for this compiler's release.
784170159Sariff                Collection<Path> systemClasses = systemClasses();
785170159Sariff                if (systemClasses != null) {
786170159Sariff                    path.addFiles(systemClasses, false);
787170159Sariff                } else {
788170159Sariff                    // fallback to the value of sun.boot.class.path
789170159Sariff                    String files = System.getProperty("sun.boot.class.path");
790170159Sariff                    path.addFiles(files, false);
791170159Sariff                }
792170159Sariff            }
793170159Sariff
794170159Sariff            path.addFiles(xbootclasspathAppendOpt);
795170159Sariff
796170159Sariff            // Strictly speaking, standard extensions are not bootstrap
797170159Sariff            // classes, but we treat them identically, so we'll pretend
798170159Sariff            // that they are.
799170159Sariff            if (extdirsOpt != null) {
800                path.addDirectories(extdirsOpt);
801            } else {
802                // Add lib/jfxrt.jar to the search path
803               Path jfxrt = javaHome.resolve("lib/jfxrt.jar");
804                if (Files.exists(jfxrt)) {
805                    path.addFile(jfxrt, false);
806                }
807                path.addDirectories(System.getProperty("java.ext.dirs"), false);
808            }
809
810            isDefault =
811                       (xbootclasspathPrependOpt == null)
812                    && (bootclasspathOpt == null)
813                    && (xbootclasspathAppendOpt == null);
814
815            return path;
816        }
817
818        /**
819         * Return a collection of files containing system classes.
820         * Returns {@code null} if not running on a modular image.
821         *
822         * @throws UncheckedIOException if an I/O errors occurs
823         */
824        private Collection<Path> systemClasses() throws IOException {
825            // Return "modules" jimage file if available
826            if (Files.isRegularFile(thisSystemModules)) {
827                return Collections.singleton(thisSystemModules);
828            }
829
830            // Exploded module image
831            Path modules = javaHome.resolve("modules");
832            if (Files.isDirectory(modules.resolve("java.base"))) {
833                try (Stream<Path> listedModules = Files.list(modules)) {
834                    return listedModules.collect(Collectors.toList());
835                }
836            }
837
838            // not a modular image that we know about
839            return null;
840        }
841
842        private void lazy() {
843            if (searchPath == null) {
844                try {
845                searchPath = Collections.unmodifiableCollection(computePath());
846                } catch (IOException e) {
847                    // TODO: need better handling here, e.g. javac Abort?
848                    throw new UncheckedIOException(e);
849                }
850            }
851        }
852    }
853
854    /**
855     * A LocationHander to represent modules found from a module-oriented
856     * location such as MODULE_SOURCE_PATH, UPGRADE_MODULE_PATH,
857     * SYSTEM_MODULES and MODULE_PATH.
858     *
859     * The Location can be specified to accept overriding classes from the
860     * {@code --patch-module <module>=<path> } parameter.
861     */
862    private class ModuleLocationHandler extends LocationHandler implements Location {
863        protected final String name;
864        protected final String moduleName;
865        protected final Collection<Path> searchPath;
866        protected final Collection<Path> searchPathWithOverrides;
867        protected final boolean output;
868
869        ModuleLocationHandler(String name, String moduleName, Collection<Path> searchPath,
870                boolean output, boolean allowOverrides) {
871            this.name = name;
872            this.moduleName = moduleName;
873            this.searchPath = searchPath;
874            this.output = output;
875
876            if (allowOverrides && patchMap != null) {
877                SearchPath mPatch = patchMap.get(moduleName);
878                if (mPatch != null) {
879                    SearchPath sp = new SearchPath();
880                    sp.addAll(mPatch);
881                    sp.addAll(searchPath);
882                    searchPathWithOverrides = sp;
883                } else {
884                    searchPathWithOverrides = searchPath;
885                }
886            } else {
887                searchPathWithOverrides = searchPath;
888            }
889        }
890
891        @Override @DefinedBy(Api.COMPILER)
892        public String getName() {
893            return name;
894        }
895
896        @Override @DefinedBy(Api.COMPILER)
897        public boolean isOutputLocation() {
898            return output;
899        }
900
901        @Override // defined by LocationHandler
902        boolean handleOption(Option option, String value) {
903            throw new UnsupportedOperationException();
904        }
905
906        @Override // defined by LocationHandler
907        Collection<Path> getPaths() {
908            // For now, we always return searchPathWithOverrides. This may differ from the
909            // JVM behavior if there is a module-info.class to be found in the overriding
910            // classes.
911            return searchPathWithOverrides;
912        }
913
914        @Override // defined by LocationHandler
915        void setPaths(Iterable<? extends Path> files) throws IOException {
916            throw new UnsupportedOperationException();
917        }
918
919        @Override // defined by LocationHandler
920        String inferModuleName() {
921            return moduleName;
922        }
923    }
924
925    /**
926     * A LocationHandler for simple module-oriented search paths,
927     * like UPGRADE_MODULE_PATH and MODULE_PATH.
928     */
929    private class ModulePathLocationHandler extends SimpleLocationHandler {
930        private Map<String, ModuleLocationHandler> pathModules;
931
932        ModulePathLocationHandler(Location location, Option... options) {
933            super(location, options);
934        }
935
936        @Override
937        public boolean handleOption(Option option, String value) {
938            if (!options.contains(option)) {
939                return false;
940            }
941            setPaths(value == null ? null : getPathEntries(value));
942            return true;
943        }
944
945        @Override
946        public Location getLocationForModule(String moduleName) {
947            initPathModules();
948            return pathModules.get(moduleName);
949        }
950
951        @Override
952        Iterable<Set<Location>> listLocationsForModules() {
953            if (searchPath == null)
954                return Collections.emptyList();
955
956            return ModulePathIterator::new;
957        }
958
959        @Override
960        void setPaths(Iterable<? extends Path> paths) {
961            if (paths != null) {
962                for (Path p: paths) {
963                    checkValidModulePathEntry(p);
964                }
965            }
966            super.setPaths(paths);
967        }
968
969        private void initPathModules() {
970            if (pathModules != null) {
971                return;
972            }
973
974            pathModules = new LinkedHashMap<>();
975
976            for (Set<Location> set : listLocationsForModules()) {
977                for (Location locn : set) {
978                    if (locn instanceof ModuleLocationHandler) {
979                        ModuleLocationHandler h = (ModuleLocationHandler) locn;
980                        pathModules.put(h.moduleName, h);
981                    }
982                }
983            }
984        }
985
986        private void checkValidModulePathEntry(Path p) {
987            if (Files.isDirectory(p)) {
988                // either an exploded module or a directory of modules
989                return;
990            }
991
992            String name = p.getFileName().toString();
993            int lastDot = name.lastIndexOf(".");
994            if (lastDot > 0) {
995                switch (name.substring(lastDot)) {
996                    case ".jar":
997                    case ".jmod":
998                        return;
999                }
1000            }
1001            throw new IllegalArgumentException(p.toString());
1002        }
1003
1004        class ModulePathIterator implements Iterator<Set<Location>> {
1005            Iterator<Path> pathIter = searchPath.iterator();
1006            int pathIndex = 0;
1007            Set<Location> next = null;
1008
1009            @Override
1010            public boolean hasNext() {
1011                if (next != null)
1012                    return true;
1013
1014                while (next == null) {
1015                    if (pathIter.hasNext()) {
1016                        Path path = pathIter.next();
1017                        if (Files.isDirectory(path)) {
1018                            next = scanDirectory(path);
1019                        } else {
1020                            next = scanFile(path);
1021                        }
1022                        pathIndex++;
1023                    } else
1024                        return false;
1025                }
1026                return true;
1027            }
1028
1029            @Override
1030            public Set<Location> next() {
1031                hasNext();
1032                if (next != null) {
1033                    Set<Location> result = next;
1034                    next = null;
1035                    return result;
1036                }
1037                throw new NoSuchElementException();
1038            }
1039
1040            private Set<Location> scanDirectory(Path path) {
1041                Set<Path> paths = new LinkedHashSet<>();
1042                Path moduleInfoClass = null;
1043                try (DirectoryStream<Path> stream = Files.newDirectoryStream(path)) {
1044                    for (Path entry: stream) {
1045                        if (entry.endsWith("module-info.class")) {
1046                            moduleInfoClass = entry;
1047                            break;  // no need to continue scanning
1048                        }
1049                        paths.add(entry);
1050                    }
1051                } catch (DirectoryIteratorException | IOException ignore) {
1052                    log.error(Errors.LocnCantReadDirectory(path));
1053                    return Collections.emptySet();
1054                }
1055
1056                if (moduleInfoClass != null) {
1057                    // It's an exploded module directly on the module path.
1058                    // We can't infer module name from the directory name, so have to
1059                    // read module-info.class.
1060                    try {
1061                        String moduleName = readModuleName(moduleInfoClass);
1062                        String name = location.getName()
1063                                + "[" + pathIndex + ":" + moduleName + "]";
1064                        ModuleLocationHandler l = new ModuleLocationHandler(name, moduleName,
1065                                Collections.singleton(path), false, true);
1066                        return Collections.singleton(l);
1067                    } catch (ModuleNameReader.BadClassFile e) {
1068                        log.error(Errors.LocnBadModuleInfo(path));
1069                        return Collections.emptySet();
1070                    } catch (IOException e) {
1071                        log.error(Errors.LocnCantReadFile(path));
1072                        return Collections.emptySet();
1073                    }
1074                }
1075
1076                // A directory of modules
1077                Set<Location> result = new LinkedHashSet<>();
1078                int index = 0;
1079                for (Path entry : paths) {
1080                    Pair<String,Path> module = inferModuleName(entry);
1081                    if (module == null) {
1082                        // diagnostic reported if necessary; skip to next
1083                        continue;
1084                    }
1085                    String moduleName = module.fst;
1086                    Path modulePath = module.snd;
1087                    String name = location.getName()
1088                            + "[" + pathIndex + "." + (index++) + ":" + moduleName + "]";
1089                    ModuleLocationHandler l = new ModuleLocationHandler(name, moduleName,
1090                            Collections.singleton(modulePath), false, true);
1091                    result.add(l);
1092                }
1093                return result;
1094            }
1095
1096            private Set<Location> scanFile(Path path) {
1097                Pair<String,Path> module = inferModuleName(path);
1098                if (module == null) {
1099                    // diagnostic reported if necessary
1100                    return Collections.emptySet();
1101                }
1102                String moduleName = module.fst;
1103                Path modulePath = module.snd;
1104                String name = location.getName()
1105                        + "[" + pathIndex + ":" + moduleName + "]";
1106                ModuleLocationHandler l = new ModuleLocationHandler(name, moduleName,
1107                        Collections.singleton(modulePath), false, true);
1108                return Collections.singleton(l);
1109            }
1110
1111            private Pair<String,Path> inferModuleName(Path p) {
1112                if (Files.isDirectory(p)) {
1113                    if (Files.exists(p.resolve("module-info.class"))) {
1114                        String name = p.getFileName().toString();
1115                        if (SourceVersion.isName(name))
1116                            return new Pair<>(name, p);
1117                    }
1118                    return null;
1119                }
1120
1121                if (p.getFileName().toString().endsWith(".jar") && fsInfo.exists(p)) {
1122                    URI uri = URI.create("jar:" + p.toUri());
1123                    try (FileSystem fs = FileSystems.newFileSystem(uri, fsEnv, null)) {
1124                        Path moduleInfoClass = fs.getPath("module-info.class");
1125                        if (Files.exists(moduleInfoClass)) {
1126                            String moduleName = readModuleName(moduleInfoClass);
1127                            return new Pair<>(moduleName, p);
1128                        }
1129                    } catch (ModuleNameReader.BadClassFile e) {
1130                        log.error(Errors.LocnBadModuleInfo(p));
1131                        return null;
1132                    } catch (IOException e) {
1133                        log.error(Errors.LocnCantReadFile(p));
1134                        return null;
1135                    } catch (ProviderNotFoundException e) {
1136                        log.error(Errors.NoZipfsForArchive(p));
1137                        return null;
1138                    }
1139
1140                    //automatic module:
1141                    String fn = p.getFileName().toString();
1142                    //from ModulePath.deriveModuleDescriptor:
1143
1144                    // drop .jar
1145                    String mn = fn.substring(0, fn.length()-4);
1146
1147                    // find first occurrence of -${NUMBER}. or -${NUMBER}$
1148                    Matcher matcher = Pattern.compile("-(\\d+(\\.|$))").matcher(mn);
1149                    if (matcher.find()) {
1150                        int start = matcher.start();
1151
1152                        mn = mn.substring(0, start);
1153                    }
1154
1155                    // finally clean up the module name
1156                    mn =  mn.replaceAll("(\\.|\\d)*$", "")    // remove trailing version
1157                            .replaceAll("[^A-Za-z0-9]", ".")  // replace non-alphanumeric
1158                            .replaceAll("(\\.)(\\1)+", ".")   // collapse repeating dots
1159                            .replaceAll("^\\.", "")           // drop leading dots
1160                            .replaceAll("\\.$", "");          // drop trailing dots
1161
1162
1163                    if (!mn.isEmpty()) {
1164                        return new Pair<>(mn, p);
1165                    }
1166
1167                    log.error(Errors.LocnCantGetModuleNameForJar(p));
1168                    return null;
1169                }
1170
1171                if (p.getFileName().toString().endsWith(".jmod")) {
1172                    try {
1173                        // check if the JMOD file is valid
1174                        JDK9Wrappers.JmodFile.checkMagic(p);
1175
1176                        // No JMOD file system.  Use JarFileSystem to
1177                        // workaround for now
1178                        FileSystem fs = fileSystems.get(p);
1179                        if (fs == null) {
1180                            URI uri = URI.create("jar:" + p.toUri());
1181                            fs = FileSystems.newFileSystem(uri, Collections.emptyMap(), null);
1182                            try {
1183                                Path moduleInfoClass = fs.getPath("classes/module-info.class");
1184                                String moduleName = readModuleName(moduleInfoClass);
1185                                Path modulePath = fs.getPath("classes");
1186                                fileSystems.put(p, fs);
1187                                closeables.add(fs);
1188                                fs = null; // prevent fs being closed in the finally clause
1189                                return new Pair<>(moduleName, modulePath);
1190                            } finally {
1191                                if (fs != null)
1192                                    fs.close();
1193                            }
1194                        }
1195                    } catch (ModuleNameReader.BadClassFile e) {
1196                        log.error(Errors.LocnBadModuleInfo(p));
1197                    } catch (IOException | ProviderNotFoundException e) {
1198                        log.error(Errors.LocnCantReadFile(p));
1199                        return null;
1200                    }
1201                }
1202
1203                if (warn && false) {  // temp disable, when enabled, massage examples.not-yet.txt suitably.
1204                    log.warning(Warnings.LocnUnknownFileOnModulePath(p));
1205                }
1206                return null;
1207            }
1208
1209            private String readModuleName(Path path) throws IOException, ModuleNameReader.BadClassFile {
1210                if (moduleNameReader == null)
1211                    moduleNameReader = new ModuleNameReader();
1212                return moduleNameReader.readModuleName(path);
1213            }
1214        }
1215
1216    }
1217
1218    private class ModuleSourcePathLocationHandler extends BasicLocationHandler {
1219
1220        private Map<String, Location> moduleLocations;
1221        private Map<Path, Location> pathLocations;
1222
1223        ModuleSourcePathLocationHandler() {
1224            super(StandardLocation.MODULE_SOURCE_PATH,
1225                    Option.MODULE_SOURCE_PATH);
1226        }
1227
1228        @Override
1229        boolean handleOption(Option option, String value) {
1230            init(value);
1231            return true;
1232        }
1233
1234        void init(String value) {
1235            Collection<String> segments = new ArrayList<>();
1236            for (String s: value.split(File.pathSeparator)) {
1237                expandBraces(s, segments);
1238            }
1239
1240            Map<String, Collection<Path>> map = new LinkedHashMap<>();
1241            final String MARKER = "*";
1242            for (String seg: segments) {
1243                int markStart = seg.indexOf(MARKER);
1244                if (markStart == -1) {
1245                    add(map, getPath(seg), null);
1246                } else {
1247                    if (markStart == 0 || !isSeparator(seg.charAt(markStart - 1))) {
1248                        throw new IllegalArgumentException("illegal use of " + MARKER + " in " + seg);
1249                    }
1250                    Path prefix = getPath(seg.substring(0, markStart - 1));
1251                    Path suffix;
1252                    int markEnd = markStart + MARKER.length();
1253                    if (markEnd == seg.length()) {
1254                        suffix = null;
1255                    } else if (!isSeparator(seg.charAt(markEnd))
1256                            || seg.indexOf(MARKER, markEnd) != -1) {
1257                        throw new IllegalArgumentException("illegal use of " + MARKER + " in " + seg);
1258                    } else {
1259                        suffix = getPath(seg.substring(markEnd + 1));
1260                    }
1261                    add(map, prefix, suffix);
1262                }
1263            }
1264
1265            moduleLocations = new LinkedHashMap<>();
1266            pathLocations = new LinkedHashMap<>();
1267            map.forEach((k, v) -> {
1268                String name = location.getName() + "[" + k + "]";
1269                ModuleLocationHandler h = new ModuleLocationHandler(name, k, v, false, false);
1270                moduleLocations.put(k, h);
1271                v.forEach(p -> pathLocations.put(normalize(p), h));
1272            });
1273        }
1274
1275        private boolean isSeparator(char ch) {
1276            // allow both separators on Windows
1277            return (ch == File.separatorChar) || (ch == '/');
1278        }
1279
1280        void add(Map<String, Collection<Path>> map, Path prefix, Path suffix) {
1281            if (!Files.isDirectory(prefix)) {
1282                if (warn) {
1283                    String key = Files.exists(prefix)
1284                            ? "dir.path.element.not.directory"
1285                            : "dir.path.element.not.found";
1286                    log.warning(Lint.LintCategory.PATH, key, prefix);
1287                }
1288                return;
1289            }
1290            try (DirectoryStream<Path> stream = Files.newDirectoryStream(prefix, path -> Files.isDirectory(path))) {
1291                for (Path entry: stream) {
1292                    Path path = (suffix == null) ? entry : entry.resolve(suffix);
1293                    if (Files.isDirectory(path)) {
1294                        String name = entry.getFileName().toString();
1295                        Collection<Path> paths = map.get(name);
1296                        if (paths == null)
1297                            map.put(name, paths = new ArrayList<>());
1298                        paths.add(path);
1299                    }
1300                }
1301            } catch (IOException e) {
1302                // TODO? What to do?
1303                System.err.println(e);
1304            }
1305        }
1306
1307        private void expandBraces(String value, Collection<String> results) {
1308            int depth = 0;
1309            int start = -1;
1310            String prefix = null;
1311            String suffix = null;
1312            for (int i = 0; i < value.length(); i++) {
1313                switch (value.charAt(i)) {
1314                    case '{':
1315                        depth++;
1316                        if (depth == 1) {
1317                            prefix = value.substring(0, i);
1318                            suffix = value.substring(getMatchingBrace(value, i) + 1);
1319                            start = i + 1;
1320                        }
1321                        break;
1322
1323                    case ',':
1324                        if (depth == 1) {
1325                            String elem = value.substring(start, i);
1326                            expandBraces(prefix + elem + suffix, results);
1327                            start = i + 1;
1328                        }
1329                        break;
1330
1331                    case '}':
1332                        switch (depth) {
1333                            case 0:
1334                                throw new IllegalArgumentException("mismatched braces");
1335
1336                            case 1:
1337                                String elem = value.substring(start, i);
1338                                expandBraces(prefix + elem + suffix, results);
1339                                return;
1340
1341                            default:
1342                                depth--;
1343                        }
1344                        break;
1345                }
1346            }
1347            if (depth > 0)
1348                throw new IllegalArgumentException("mismatched braces");
1349            results.add(value);
1350        }
1351
1352        int getMatchingBrace(String value, int offset) {
1353            int depth = 1;
1354            for (int i = offset + 1; i < value.length(); i++) {
1355                switch (value.charAt(i)) {
1356                    case '{':
1357                        depth++;
1358                        break;
1359
1360                    case '}':
1361                        if (--depth == 0)
1362                            return i;
1363                        break;
1364                }
1365            }
1366            throw new IllegalArgumentException("mismatched braces");
1367        }
1368
1369        @Override
1370        boolean isSet() {
1371            return (moduleLocations != null);
1372        }
1373
1374        @Override
1375        Collection<Path> getPaths() {
1376            throw new UnsupportedOperationException();
1377        }
1378
1379        @Override
1380        void setPaths(Iterable<? extends Path> files) throws IOException {
1381            throw new UnsupportedOperationException();
1382        }
1383
1384        @Override
1385        Location getLocationForModule(String name) {
1386            return (moduleLocations == null) ? null : moduleLocations.get(name);
1387        }
1388
1389        @Override
1390        Location getLocationForModule(Path dir) {
1391            return (pathLocations == null) ? null : pathLocations.get(dir);
1392        }
1393
1394        @Override
1395        Iterable<Set<Location>> listLocationsForModules() {
1396            if (moduleLocations == null)
1397                return Collections.emptySet();
1398            Set<Location> locns = new LinkedHashSet<>();
1399            moduleLocations.forEach((k, v) -> locns.add(v));
1400            return Collections.singleton(locns);
1401        }
1402
1403    }
1404
1405    private class SystemModulesLocationHandler extends BasicLocationHandler {
1406        private Path systemJavaHome;
1407        private Path modules;
1408        private Map<String, ModuleLocationHandler> systemModules;
1409
1410        SystemModulesLocationHandler() {
1411            super(StandardLocation.SYSTEM_MODULES, Option.SYSTEM);
1412            systemJavaHome = Locations.javaHome;
1413        }
1414
1415        @Override
1416        boolean handleOption(Option option, String value) {
1417            if (!options.contains(option)) {
1418                return false;
1419            }
1420
1421            if (value == null) {
1422                systemJavaHome = Locations.javaHome;
1423            } else if (value.equals("none")) {
1424                systemJavaHome = null;
1425            } else {
1426                update(getPath(value));
1427            }
1428
1429            modules = null;
1430            return true;
1431        }
1432
1433        @Override
1434        Collection<Path> getPaths() {
1435            return (systemJavaHome == null) ? null : Collections.singleton(systemJavaHome);
1436        }
1437
1438        @Override
1439        void setPaths(Iterable<? extends Path> files) throws IOException {
1440            if (files == null) {
1441                systemJavaHome = null;
1442            } else {
1443                Iterator<? extends Path> pathIter = files.iterator();
1444                if (!pathIter.hasNext()) {
1445                    throw new IllegalArgumentException("empty path for directory"); // TODO: FIXME
1446                }
1447                Path dir = pathIter.next();
1448                if (pathIter.hasNext()) {
1449                    throw new IllegalArgumentException("path too long for directory"); // TODO: FIXME
1450                }
1451                if (!Files.exists(dir)) {
1452                    throw new FileNotFoundException(dir + ": does not exist");
1453                } else if (!Files.isDirectory(dir)) {
1454                    throw new IOException(dir + ": not a directory");
1455                }
1456                update(dir);
1457            }
1458        }
1459
1460        private void update(Path p) {
1461            if (!isCurrentPlatform(p) && !Files.exists(p.resolve("lib").resolve("jrt-fs.jar")) &&
1462                    !Files.exists(systemJavaHome.resolve("modules")))
1463                throw new IllegalArgumentException(p.toString());
1464            systemJavaHome = p;
1465            modules = null;
1466        }
1467
1468        private boolean isCurrentPlatform(Path p) {
1469            try {
1470                return Files.isSameFile(p, Locations.javaHome);
1471            } catch (IOException ex) {
1472                throw new IllegalArgumentException(p.toString(), ex);
1473            }
1474        }
1475
1476        @Override
1477        Location getLocationForModule(String name) throws IOException {
1478            initSystemModules();
1479            return systemModules.get(name);
1480        }
1481
1482        @Override
1483        Iterable<Set<Location>> listLocationsForModules() throws IOException {
1484            initSystemModules();
1485            Set<Location> locns = new LinkedHashSet<>();
1486            for (Location l: systemModules.values())
1487                locns.add(l);
1488            return Collections.singleton(locns);
1489        }
1490
1491        private void initSystemModules() throws IOException {
1492            if (systemModules != null) {
1493                return;
1494            }
1495
1496            if (systemJavaHome == null) {
1497                systemModules = Collections.emptyMap();
1498                return;
1499            }
1500
1501            if (modules == null) {
1502                try {
1503                    URI jrtURI = URI.create("jrt:/");
1504                    FileSystem jrtfs;
1505
1506                    if (isCurrentPlatform(systemJavaHome)) {
1507                        jrtfs = FileSystems.getFileSystem(jrtURI);
1508                    } else {
1509                        try {
1510                            Map<String, String> attrMap =
1511                                    Collections.singletonMap("java.home", systemJavaHome.toString());
1512                            jrtfs = FileSystems.newFileSystem(jrtURI, attrMap);
1513                        } catch (ProviderNotFoundException ex) {
1514                            URL javaHomeURL = systemJavaHome.resolve("jrt-fs.jar").toUri().toURL();
1515                            ClassLoader currentLoader = Locations.class.getClassLoader();
1516                            URLClassLoader fsLoader =
1517                                    new URLClassLoader(new URL[] {javaHomeURL}, currentLoader);
1518
1519                            jrtfs = FileSystems.newFileSystem(jrtURI, Collections.emptyMap(), fsLoader);
1520
1521                            closeables.add(fsLoader);
1522                        }
1523
1524                        closeables.add(jrtfs);
1525                    }
1526
1527                    modules = jrtfs.getPath("/modules");
1528                } catch (FileSystemNotFoundException | ProviderNotFoundException e) {
1529                    modules = systemJavaHome.resolve("modules");
1530                    if (!Files.exists(modules))
1531                        throw new IOException("can't find system classes", e);
1532                }
1533            }
1534
1535            systemModules = new LinkedHashMap<>();
1536            try (DirectoryStream<Path> stream = Files.newDirectoryStream(modules, Files::isDirectory)) {
1537                for (Path entry : stream) {
1538                    String moduleName = entry.getFileName().toString();
1539                    String name = location.getName() + "[" + moduleName + "]";
1540                    ModuleLocationHandler h = new ModuleLocationHandler(name, moduleName,
1541                            Collections.singleton(entry), false, true);
1542                    systemModules.put(moduleName, h);
1543                }
1544            }
1545        }
1546    }
1547
1548    Map<Location, LocationHandler> handlersForLocation;
1549    Map<Option, LocationHandler> handlersForOption;
1550
1551    void initHandlers() {
1552        handlersForLocation = new HashMap<>();
1553        handlersForOption = new EnumMap<>(Option.class);
1554
1555        BasicLocationHandler[] handlers = {
1556            new BootClassPathLocationHandler(),
1557            new ClassPathLocationHandler(),
1558            new SimpleLocationHandler(StandardLocation.SOURCE_PATH, Option.SOURCE_PATH),
1559            new SimpleLocationHandler(StandardLocation.ANNOTATION_PROCESSOR_PATH, Option.PROCESSOR_PATH),
1560            new SimpleLocationHandler(StandardLocation.ANNOTATION_PROCESSOR_MODULE_PATH, Option.PROCESSOR_MODULE_PATH),
1561            new OutputLocationHandler(StandardLocation.CLASS_OUTPUT, Option.D),
1562            new OutputLocationHandler(StandardLocation.SOURCE_OUTPUT, Option.S),
1563            new OutputLocationHandler(StandardLocation.NATIVE_HEADER_OUTPUT, Option.H),
1564            new ModuleSourcePathLocationHandler(),
1565            // TODO: should UPGRADE_MODULE_PATH be merged with SYSTEM_MODULES?
1566            new ModulePathLocationHandler(StandardLocation.UPGRADE_MODULE_PATH, Option.UPGRADE_MODULE_PATH),
1567            new ModulePathLocationHandler(StandardLocation.MODULE_PATH, Option.MODULE_PATH),
1568            new SystemModulesLocationHandler(),
1569        };
1570
1571        for (BasicLocationHandler h : handlers) {
1572            handlersForLocation.put(h.location, h);
1573            for (Option o : h.options) {
1574                handlersForOption.put(o, h);
1575            }
1576        }
1577    }
1578
1579    private Map<String, SearchPath> patchMap;
1580
1581    boolean handleOption(Option option, String value) {
1582        switch (option) {
1583            case PATCH_MODULE:
1584                if (value == null) {
1585                    patchMap = null;
1586                } else {
1587                    // Allow an extended syntax for --patch-module consisting of a series
1588                    // of values separated by NULL characters. This is to facilitate
1589                    // supporting deferred file manager options on the command line.
1590                    // See Option.PATCH_MODULE for the code that composes these multiple
1591                    // values.
1592                    for (String v : value.split("\0")) {
1593                        int eq = v.indexOf('=');
1594                        if (eq > 0) {
1595                            String mName = v.substring(0, eq);
1596                            SearchPath mPatchPath = new SearchPath()
1597                                    .addFiles(v.substring(eq + 1));
1598                            boolean ok = true;
1599                            for (Path p : mPatchPath) {
1600                                Path mi = p.resolve("module-info.class");
1601                                if (Files.exists(mi)) {
1602                                    log.error(Errors.LocnModuleInfoNotAllowedOnPatchPath(mi));
1603                                    ok = false;
1604                                }
1605                            }
1606                            if (ok) {
1607                                if (patchMap == null) {
1608                                    patchMap = new LinkedHashMap<>();
1609                                }
1610                                patchMap.put(mName, mPatchPath);
1611                            }
1612                        } else {
1613                            // Should not be able to get here;
1614                            // this should be caught and handled in Option.PATCH_MODULE
1615                            log.error(Errors.LocnInvalidArgForXpatch(value));
1616                        }
1617                    }
1618                }
1619                return true;
1620            default:
1621                LocationHandler h = handlersForOption.get(option);
1622                return (h == null ? false : h.handleOption(option, value));
1623        }
1624    }
1625
1626    boolean hasLocation(Location location) {
1627        LocationHandler h = getHandler(location);
1628        return (h == null ? false : h.isSet());
1629    }
1630
1631    Collection<Path> getLocation(Location location) {
1632        LocationHandler h = getHandler(location);
1633        return (h == null ? null : h.getPaths());
1634    }
1635
1636    Path getOutputLocation(Location location) {
1637        if (!location.isOutputLocation()) {
1638            throw new IllegalArgumentException();
1639        }
1640        LocationHandler h = getHandler(location);
1641        return ((OutputLocationHandler) h).outputDir;
1642    }
1643
1644    void setLocation(Location location, Iterable<? extends Path> files) throws IOException {
1645        LocationHandler h = getHandler(location);
1646        if (h == null) {
1647            if (location.isOutputLocation()) {
1648                h = new OutputLocationHandler(location);
1649            } else {
1650                h = new SimpleLocationHandler(location);
1651            }
1652            handlersForLocation.put(location, h);
1653        }
1654        h.setPaths(files);
1655    }
1656
1657    Location getLocationForModule(Location location, String name) throws IOException {
1658        LocationHandler h = getHandler(location);
1659        return (h == null ? null : h.getLocationForModule(name));
1660    }
1661
1662    Location getLocationForModule(Location location, Path dir) {
1663        LocationHandler h = getHandler(location);
1664        return (h == null ? null : h.getLocationForModule(dir));
1665    }
1666
1667    String inferModuleName(Location location) {
1668        LocationHandler h = getHandler(location);
1669        return (h == null ? null : h.inferModuleName());
1670    }
1671
1672    Iterable<Set<Location>> listLocationsForModules(Location location) throws IOException {
1673        LocationHandler h = getHandler(location);
1674        return (h == null ? null : h.listLocationsForModules());
1675    }
1676
1677    protected LocationHandler getHandler(Location location) {
1678        Objects.requireNonNull(location);
1679        return (location instanceof LocationHandler)
1680                ? (LocationHandler) location
1681                : handlersForLocation.get(location);
1682    }
1683
1684    /**
1685     * Is this the name of an archive file?
1686     */
1687    private boolean isArchive(Path file) {
1688        String n = StringUtils.toLowerCase(file.getFileName().toString());
1689        return fsInfo.isFile(file)
1690                && (n.endsWith(".jar") || n.endsWith(".zip"));
1691    }
1692
1693    static Path normalize(Path p) {
1694        try {
1695            return p.toRealPath();
1696        } catch (IOException e) {
1697            return p.toAbsolutePath().normalize();
1698        }
1699    }
1700
1701}
1702