Locations.java revision 3680:a35a1d86165c
1/*
2 * Copyright (c) 2003, 2016, Oracle and/or its affiliates. All rights reserved.
3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4 *
5 * This code is free software; you can redistribute it and/or modify it
6 * under the terms of the GNU General Public License version 2 only, as
7 * published by the Free Software Foundation.  Oracle designates this
8 * particular file as subject to the "Classpath" exception as provided
9 * by Oracle in the LICENSE file that accompanied this code.
10 *
11 * This code is distributed in the hope that it will be useful, but WITHOUT
12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
14 * version 2 for more details (a copy is included in the LICENSE file that
15 * accompanied this code).
16 *
17 * You should have received a copy of the GNU General Public License version
18 * 2 along with this work; if not, write to the Free Software Foundation,
19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
20 *
21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
22 * or visit www.oracle.com if you need additional information or have any
23 * questions.
24 */
25
26package com.sun.tools.javac.file;
27
28import java.io.Closeable;
29import java.io.File;
30import java.io.FileNotFoundException;
31import java.io.IOException;
32import java.io.UncheckedIOException;
33import java.net.URI;
34import java.net.URL;
35import java.net.URLClassLoader;
36import java.nio.file.DirectoryIteratorException;
37import java.nio.file.DirectoryStream;
38import java.nio.file.FileSystem;
39import java.nio.file.FileSystemNotFoundException;
40import java.nio.file.FileSystems;
41import java.nio.file.Files;
42import java.nio.file.Path;
43import java.nio.file.Paths;
44import java.nio.file.ProviderNotFoundException;
45import java.nio.file.spi.FileSystemProvider;
46import java.util.ArrayList;
47import java.util.Arrays;
48import java.util.Collection;
49import java.util.Collections;
50import java.util.EnumMap;
51import java.util.EnumSet;
52import java.util.HashMap;
53import java.util.HashSet;
54import java.util.Iterator;
55import java.util.LinkedHashMap;
56import java.util.LinkedHashSet;
57import java.util.List;
58import java.util.Map;
59import java.util.Objects;
60import java.util.NoSuchElementException;
61import java.util.Set;
62import java.util.regex.Matcher;
63import java.util.regex.Pattern;
64import java.util.stream.Collectors;
65import java.util.stream.Stream;
66import java.util.zip.ZipFile;
67
68import javax.lang.model.SourceVersion;
69import javax.tools.JavaFileManager;
70import javax.tools.JavaFileManager.Location;
71import javax.tools.StandardJavaFileManager;
72import javax.tools.StandardJavaFileManager.PathFactory;
73import javax.tools.StandardLocation;
74
75import com.sun.tools.javac.code.Lint;
76import com.sun.tools.javac.main.Option;
77import com.sun.tools.javac.resources.CompilerProperties.Errors;
78import com.sun.tools.javac.resources.CompilerProperties.Warnings;
79import com.sun.tools.javac.util.DefinedBy;
80import com.sun.tools.javac.util.DefinedBy.Api;
81import com.sun.tools.javac.util.JDK9Wrappers;
82import com.sun.tools.javac.util.ListBuffer;
83import com.sun.tools.javac.util.Log;
84import com.sun.tools.javac.jvm.ModuleNameReader;
85import com.sun.tools.javac.util.Pair;
86import com.sun.tools.javac.util.StringUtils;
87
88import static javax.tools.StandardLocation.PLATFORM_CLASS_PATH;
89
90import static com.sun.tools.javac.main.Option.BOOT_CLASS_PATH;
91import static com.sun.tools.javac.main.Option.DJAVA_ENDORSED_DIRS;
92import static com.sun.tools.javac.main.Option.DJAVA_EXT_DIRS;
93import static com.sun.tools.javac.main.Option.ENDORSEDDIRS;
94import static com.sun.tools.javac.main.Option.EXTDIRS;
95import static com.sun.tools.javac.main.Option.XBOOTCLASSPATH;
96import static com.sun.tools.javac.main.Option.XBOOTCLASSPATH_APPEND;
97import static com.sun.tools.javac.main.Option.XBOOTCLASSPATH_PREPEND;
98
99/**
100 * This class converts command line arguments, environment variables and system properties (in
101 * File.pathSeparator-separated String form) into a boot class path, user class path, and source
102 * path (in {@code Collection<String>} form).
103 *
104 * <p>
105 * <b>This is NOT part of any supported API. If you write code that depends on this, you do so at
106 * your own risk. This code and its internal interfaces are subject to change or deletion without
107 * notice.</b>
108 */
109public class Locations {
110
111    /**
112     * The log to use for warning output
113     */
114    private Log log;
115
116    /**
117     * Access to (possibly cached) file info
118     */
119    private FSInfo fsInfo;
120
121    /**
122     * Whether to warn about non-existent path elements
123     */
124    private boolean warn;
125
126    private ModuleNameReader moduleNameReader;
127
128    private PathFactory pathFactory = Paths::get;
129
130    static final Path javaHome = FileSystems.getDefault().getPath(System.getProperty("java.home"));
131    static final Path thisSystemModules = javaHome.resolve("lib").resolve("modules");
132
133    Map<Path, FileSystem> fileSystems = new LinkedHashMap<>();
134    List<Closeable> closeables = new ArrayList<>();
135    private Map<String,String> fsEnv = Collections.emptyMap();
136
137    Locations() {
138        initHandlers();
139    }
140
141    Path getPath(String first, String... more) {
142        return pathFactory.getPath(first, more);
143    }
144
145    public void close() throws IOException {
146        ListBuffer<IOException> list = new ListBuffer<>();
147        closeables.forEach(closeable -> {
148            try {
149                closeable.close();
150            } catch (IOException ex) {
151                list.add(ex);
152            }
153        });
154        if (list.nonEmpty()) {
155            IOException ex = new IOException();
156            for (IOException e: list)
157                ex.addSuppressed(e);
158            throw ex;
159        }
160    }
161
162    // could replace Lint by "boolean warn"
163    void update(Log log, Lint lint, FSInfo fsInfo) {
164        this.log = log;
165        warn = lint.isEnabled(Lint.LintCategory.PATH);
166        this.fsInfo = fsInfo;
167    }
168
169    void setPathFactory(PathFactory f) {
170        pathFactory = f;
171    }
172
173    boolean isDefaultBootClassPath() {
174        BootClassPathLocationHandler h
175                = (BootClassPathLocationHandler) getHandler(PLATFORM_CLASS_PATH);
176        return h.isDefault();
177    }
178
179    /**
180     * Split a search path into its elements. Empty path elements will be ignored.
181     *
182     * @param searchPath The search path to be split
183     * @return The elements of the path
184     */
185    private Iterable<Path> getPathEntries(String searchPath) {
186        return getPathEntries(searchPath, null);
187    }
188
189    /**
190     * Split a search path into its elements. If emptyPathDefault is not null, all empty elements in the
191     * path, including empty elements at either end of the path, will be replaced with the value of
192     * emptyPathDefault.
193     *
194     * @param searchPath The search path to be split
195     * @param emptyPathDefault The value to substitute for empty path elements, or null, to ignore
196     * empty path elements
197     * @return The elements of the path
198     */
199    private Iterable<Path> getPathEntries(String searchPath, Path emptyPathDefault) {
200        ListBuffer<Path> entries = new ListBuffer<>();
201        for (String s: searchPath.split(Pattern.quote(File.pathSeparator), -1)) {
202            if (s.isEmpty()) {
203                if (emptyPathDefault != null) {
204                    entries.add(emptyPathDefault);
205                }
206            } else {
207                entries.add(getPath(s));
208            }
209        }
210        return entries;
211    }
212
213    public void setMultiReleaseValue(String multiReleaseValue) {
214        fsEnv = Collections.singletonMap("multi-release", multiReleaseValue);
215    }
216
217    /**
218     * Utility class to help evaluate a path option. Duplicate entries are ignored, jar class paths
219     * can be expanded.
220     */
221    private class SearchPath extends LinkedHashSet<Path> {
222
223        private static final long serialVersionUID = 0;
224
225        private boolean expandJarClassPaths = false;
226        private final Set<Path> canonicalValues = new HashSet<>();
227
228        public SearchPath expandJarClassPaths(boolean x) {
229            expandJarClassPaths = x;
230            return this;
231        }
232
233        /**
234         * What to use when path element is the empty string
235         */
236        private Path emptyPathDefault = null;
237
238        public SearchPath emptyPathDefault(Path x) {
239            emptyPathDefault = x;
240            return this;
241        }
242
243        public SearchPath addDirectories(String dirs, boolean warn) {
244            boolean prev = expandJarClassPaths;
245            expandJarClassPaths = true;
246            try {
247                if (dirs != null) {
248                    for (Path dir : getPathEntries(dirs)) {
249                        addDirectory(dir, warn);
250                    }
251                }
252                return this;
253            } finally {
254                expandJarClassPaths = prev;
255            }
256        }
257
258        public SearchPath addDirectories(String dirs) {
259            return addDirectories(dirs, warn);
260        }
261
262        private void addDirectory(Path dir, boolean warn) {
263            if (!Files.isDirectory(dir)) {
264                if (warn) {
265                    log.warning(Lint.LintCategory.PATH,
266                            "dir.path.element.not.found", dir);
267                }
268                return;
269            }
270
271            try (Stream<Path> s = Files.list(dir)) {
272                s.filter(dirEntry -> isArchive(dirEntry))
273                        .forEach(dirEntry -> addFile(dirEntry, warn));
274            } catch (IOException ignore) {
275            }
276        }
277
278        public SearchPath addFiles(String files, boolean warn) {
279            if (files != null) {
280                addFiles(getPathEntries(files, emptyPathDefault), warn);
281            }
282            return this;
283        }
284
285        public SearchPath addFiles(String files) {
286            return addFiles(files, warn);
287        }
288
289        public SearchPath addFiles(Iterable<? extends Path> files, boolean warn) {
290            if (files != null) {
291                for (Path file : files) {
292                    addFile(file, warn);
293                }
294            }
295            return this;
296        }
297
298        public SearchPath addFiles(Iterable<? extends Path> files) {
299            return addFiles(files, warn);
300        }
301
302        public void addFile(Path file, boolean warn) {
303            if (contains(file)) {
304                // discard duplicates
305                return;
306            }
307
308            if (!fsInfo.exists(file)) {
309                /* No such file or directory exists */
310                if (warn) {
311                    log.warning(Lint.LintCategory.PATH,
312                            "path.element.not.found", file);
313                }
314                super.add(file);
315                return;
316            }
317
318            Path canonFile = fsInfo.getCanonicalFile(file);
319            if (canonicalValues.contains(canonFile)) {
320                /* Discard duplicates and avoid infinite recursion */
321                return;
322            }
323
324            if (fsInfo.isFile(file)) {
325                /* File is an ordinary file. */
326                if (   !file.getFileName().toString().endsWith(".jmod")
327                    && !file.endsWith("modules")) {
328                    if (!isArchive(file)) {
329                        /* Not a recognized extension; open it to see if
330                         it looks like a valid zip file. */
331                        try {
332                            FileSystems.newFileSystem(file, null).close();
333                            if (warn) {
334                                log.warning(Lint.LintCategory.PATH,
335                                        "unexpected.archive.file", file);
336                            }
337                        } catch (IOException | ProviderNotFoundException e) {
338                            // FIXME: include e.getLocalizedMessage in warning
339                            if (warn) {
340                                log.warning(Lint.LintCategory.PATH,
341                                        "invalid.archive.file", file);
342                            }
343                            return;
344                        }
345                    } else {
346                        if (fsInfo.getJarFSProvider() == null) {
347                            log.error(Errors.NoZipfsForArchive(file));
348                            return ;
349                        }
350                    }
351                }
352            }
353
354            /* Now what we have left is either a directory or a file name
355             conforming to archive naming convention */
356            super.add(file);
357            canonicalValues.add(canonFile);
358
359            if (expandJarClassPaths && fsInfo.isFile(file) && !file.endsWith("modules")) {
360                addJarClassPath(file, warn);
361            }
362        }
363
364        // Adds referenced classpath elements from a jar's Class-Path
365        // Manifest entry.  In some future release, we may want to
366        // update this code to recognize URLs rather than simple
367        // filenames, but if we do, we should redo all path-related code.
368        private void addJarClassPath(Path jarFile, boolean warn) {
369            try {
370                for (Path f : fsInfo.getJarClassPath(jarFile)) {
371                    addFile(f, warn);
372                }
373            } catch (IOException e) {
374                log.error("error.reading.file", jarFile, JavacFileManager.getMessage(e));
375            }
376        }
377    }
378
379    /**
380     * Base class for handling support for the representation of Locations.
381     *
382     * Locations are (by design) opaque handles that can easily be implemented
383     * by enums like StandardLocation. Within JavacFileManager, each Location
384     * has an associated LocationHandler, which provides much of the appropriate
385     * functionality for the corresponding Location.
386     *
387     * @see #initHandlers
388     * @see #getHandler
389     */
390    protected abstract class LocationHandler {
391
392        /**
393         * @see JavaFileManager#handleOption
394         */
395        abstract boolean handleOption(Option option, String value);
396
397        /**
398         * @see StandardJavaFileManager#hasLocation
399         */
400        boolean isSet() {
401            return (getPaths() != null);
402        }
403
404        /**
405         * @see StandardJavaFileManager#getLocation
406         */
407        abstract Collection<Path> getPaths();
408
409        /**
410         * @see StandardJavaFileManager#setLocation
411         */
412        abstract void setPaths(Iterable<? extends Path> files) throws IOException;
413
414        /**
415         * @see JavaFileManager#getModuleLocation(Location, String)
416         */
417        Location getModuleLocation(String moduleName) throws IOException {
418            return null;
419        }
420
421        /**
422         * @see JavaFileManager#getModuleLocation(Location, JavaFileObject, String)
423         */
424        Location getModuleLocation(Path dir) {
425            return null;
426        }
427
428        /**
429         * @see JavaFileManager#inferModuleName
430         */
431        String inferModuleName() {
432            return null;
433        }
434
435        /**
436         * @see JavaFileManager#listModuleLocations
437         */
438        Iterable<Set<Location>> listModuleLocations() throws IOException {
439            return null;
440        }
441    }
442
443    /**
444     * A LocationHandler for a given Location, and associated set of options.
445     */
446    private abstract class BasicLocationHandler extends LocationHandler {
447
448        final Location location;
449        final Set<Option> options;
450
451        /**
452         * Create a handler. The location and options provide a way to map from a location or an
453         * option to the corresponding handler.
454         *
455         * @param location the location for which this is the handler
456         * @param options the options affecting this location
457         * @see #initHandlers
458         */
459        protected BasicLocationHandler(Location location, Option... options) {
460            this.location = location;
461            this.options = options.length == 0
462                    ? EnumSet.noneOf(Option.class)
463                    : EnumSet.copyOf(Arrays.asList(options));
464        }
465    }
466
467    /**
468     * General purpose implementation for output locations, such as -d/CLASS_OUTPUT and
469     * -s/SOURCE_OUTPUT. All options are treated as equivalent (i.e. aliases.)
470     * The value is a single file, possibly null.
471     */
472    private class OutputLocationHandler extends BasicLocationHandler {
473
474        private Path outputDir;
475        private Map<String, Location> moduleLocations;
476
477        OutputLocationHandler(Location location, Option... options) {
478            super(location, options);
479        }
480
481        @Override
482        boolean handleOption(Option option, String value) {
483            if (!options.contains(option)) {
484                return false;
485            }
486
487            // TODO: could/should validate outputDir exists and is a directory
488            // need to decide how best to report issue for benefit of
489            // direct API call on JavaFileManager.handleOption(specifies IAE)
490            // vs. command line decoding.
491            outputDir = (value == null) ? null : getPath(value);
492            return true;
493        }
494
495        @Override
496        Collection<Path> getPaths() {
497            return (outputDir == null) ? null : Collections.singleton(outputDir);
498        }
499
500        @Override
501        void setPaths(Iterable<? extends Path> files) throws IOException {
502            if (files == null) {
503                outputDir = null;
504            } else {
505                Iterator<? extends Path> pathIter = files.iterator();
506                if (!pathIter.hasNext()) {
507                    throw new IllegalArgumentException("empty path for directory");
508                }
509                Path dir = pathIter.next();
510                if (pathIter.hasNext()) {
511                    throw new IllegalArgumentException("path too long for directory");
512                }
513                if (!Files.exists(dir)) {
514                    throw new FileNotFoundException(dir + ": does not exist");
515                } else if (!Files.isDirectory(dir)) {
516                    throw new IOException(dir + ": not a directory");
517                }
518                outputDir = dir;
519            }
520            moduleLocations = null;
521        }
522
523        @Override
524        Location getModuleLocation(String name) {
525            if (moduleLocations == null)
526                moduleLocations = new HashMap<>();
527            Location l = moduleLocations.get(name);
528            if (l == null) {
529                l = new ModuleLocationHandler(location.getName() + "[" + name + "]",
530                        name,
531                        Collections.singleton(outputDir.resolve(name)),
532                        true, false);
533                moduleLocations.put(name, l);
534            }
535            return l;
536        }
537    }
538
539    /**
540     * General purpose implementation for search path locations,
541     * such as -sourcepath/SOURCE_PATH and -processorPath/ANNOTATION_PROCESSOR_PATH.
542     * All options are treated as equivalent (i.e. aliases.)
543     * The value is an ordered set of files and/or directories.
544     */
545    private class SimpleLocationHandler extends BasicLocationHandler {
546
547        protected Collection<Path> searchPath;
548
549        SimpleLocationHandler(Location location, Option... options) {
550            super(location, options);
551        }
552
553        @Override
554        boolean handleOption(Option option, String value) {
555            if (!options.contains(option)) {
556                return false;
557            }
558            searchPath = value == null ? null
559                    : Collections.unmodifiableCollection(createPath().addFiles(value));
560            return true;
561        }
562
563        @Override
564        Collection<Path> getPaths() {
565            return searchPath;
566        }
567
568        @Override
569        void setPaths(Iterable<? extends Path> files) {
570            SearchPath p;
571            if (files == null) {
572                p = computePath(null);
573            } else {
574                p = createPath().addFiles(files);
575            }
576            searchPath = Collections.unmodifiableCollection(p);
577        }
578
579        protected SearchPath computePath(String value) {
580            return createPath().addFiles(value);
581        }
582
583        protected SearchPath createPath() {
584            return new SearchPath();
585        }
586    }
587
588    /**
589     * Subtype of SimpleLocationHandler for -classpath/CLASS_PATH.
590     * If no value is given, a default is provided, based on system properties and other values.
591     */
592    private class ClassPathLocationHandler extends SimpleLocationHandler {
593
594        ClassPathLocationHandler() {
595            super(StandardLocation.CLASS_PATH, Option.CLASS_PATH);
596        }
597
598        @Override
599        Collection<Path> getPaths() {
600            lazy();
601            return searchPath;
602        }
603
604        @Override
605        protected SearchPath computePath(String value) {
606            String cp = value;
607
608            // CLASSPATH environment variable when run from `javac'.
609            if (cp == null) {
610                cp = System.getProperty("env.class.path");
611            }
612
613            // If invoked via a java VM (not the javac launcher), use the
614            // platform class path
615            if (cp == null && System.getProperty("application.home") == null) {
616                cp = System.getProperty("java.class.path");
617            }
618
619            // Default to current working directory.
620            if (cp == null) {
621                cp = ".";
622            }
623
624            return createPath().addFiles(cp);
625        }
626
627        @Override
628        protected SearchPath createPath() {
629            return new SearchPath()
630                    .expandJarClassPaths(true) // Only search user jars for Class-Paths
631                    .emptyPathDefault(getPath("."));  // Empty path elt ==> current directory
632        }
633
634        private void lazy() {
635            if (searchPath == null) {
636                setPaths(null);
637            }
638        }
639    }
640
641    /**
642     * Custom subtype of LocationHandler for PLATFORM_CLASS_PATH.
643     * Various options are supported for different components of the
644     * platform class path.
645     * Setting a value with setLocation overrides all existing option values.
646     * Setting any option overrides any value set with setLocation, and
647     * reverts to using default values for options that have not been set.
648     * Setting -bootclasspath or -Xbootclasspath overrides any existing
649     * value for -Xbootclasspath/p: and -Xbootclasspath/a:.
650     */
651    private class BootClassPathLocationHandler extends BasicLocationHandler {
652
653        private Collection<Path> searchPath;
654        final Map<Option, String> optionValues = new EnumMap<>(Option.class);
655
656        /**
657         * Is the bootclasspath the default?
658         */
659        private boolean isDefault;
660
661        BootClassPathLocationHandler() {
662            super(StandardLocation.PLATFORM_CLASS_PATH,
663                    Option.BOOT_CLASS_PATH, Option.XBOOTCLASSPATH,
664                    Option.XBOOTCLASSPATH_PREPEND,
665                    Option.XBOOTCLASSPATH_APPEND,
666                    Option.ENDORSEDDIRS, Option.DJAVA_ENDORSED_DIRS,
667                    Option.EXTDIRS, Option.DJAVA_EXT_DIRS);
668        }
669
670        boolean isDefault() {
671            lazy();
672            return isDefault;
673        }
674
675        @Override
676        boolean handleOption(Option option, String value) {
677            if (!options.contains(option)) {
678                return false;
679            }
680
681            option = canonicalize(option);
682            optionValues.put(option, value);
683            if (option == BOOT_CLASS_PATH) {
684                optionValues.remove(XBOOTCLASSPATH_PREPEND);
685                optionValues.remove(XBOOTCLASSPATH_APPEND);
686            }
687            searchPath = null;  // reset to "uninitialized"
688            return true;
689        }
690        // where
691        // TODO: would be better if option aliasing was handled at a higher
692        // level
693        private Option canonicalize(Option option) {
694            switch (option) {
695                case XBOOTCLASSPATH:
696                    return Option.BOOT_CLASS_PATH;
697                case DJAVA_ENDORSED_DIRS:
698                    return Option.ENDORSEDDIRS;
699                case DJAVA_EXT_DIRS:
700                    return Option.EXTDIRS;
701                default:
702                    return option;
703            }
704        }
705
706        @Override
707        Collection<Path> getPaths() {
708            lazy();
709            return searchPath;
710        }
711
712        @Override
713        void setPaths(Iterable<? extends Path> files) {
714            if (files == null) {
715                searchPath = null;  // reset to "uninitialized"
716            } else {
717                isDefault = false;
718                SearchPath p = new SearchPath().addFiles(files, false);
719                searchPath = Collections.unmodifiableCollection(p);
720                optionValues.clear();
721            }
722        }
723
724        SearchPath computePath() throws IOException {
725            SearchPath path = new SearchPath();
726
727            String bootclasspathOpt = optionValues.get(BOOT_CLASS_PATH);
728            String endorseddirsOpt = optionValues.get(ENDORSEDDIRS);
729            String extdirsOpt = optionValues.get(EXTDIRS);
730            String xbootclasspathPrependOpt = optionValues.get(XBOOTCLASSPATH_PREPEND);
731            String xbootclasspathAppendOpt = optionValues.get(XBOOTCLASSPATH_APPEND);
732            path.addFiles(xbootclasspathPrependOpt);
733
734            if (endorseddirsOpt != null) {
735                path.addDirectories(endorseddirsOpt);
736            } else {
737                path.addDirectories(System.getProperty("java.endorsed.dirs"), false);
738            }
739
740            if (bootclasspathOpt != null) {
741                path.addFiles(bootclasspathOpt);
742            } else {
743                // Standard system classes for this compiler's release.
744                Collection<Path> systemClasses = systemClasses();
745                if (systemClasses != null) {
746                    path.addFiles(systemClasses, false);
747                } else {
748                    // fallback to the value of sun.boot.class.path
749                    String files = System.getProperty("sun.boot.class.path");
750                    path.addFiles(files, false);
751                }
752            }
753
754            path.addFiles(xbootclasspathAppendOpt);
755
756            // Strictly speaking, standard extensions are not bootstrap
757            // classes, but we treat them identically, so we'll pretend
758            // that they are.
759            if (extdirsOpt != null) {
760                path.addDirectories(extdirsOpt);
761            } else {
762                // Add lib/jfxrt.jar to the search path
763               Path jfxrt = javaHome.resolve("lib/jfxrt.jar");
764                if (Files.exists(jfxrt)) {
765                    path.addFile(jfxrt, false);
766                }
767                path.addDirectories(System.getProperty("java.ext.dirs"), false);
768            }
769
770            isDefault =
771                       (xbootclasspathPrependOpt == null)
772                    && (bootclasspathOpt == null)
773                    && (xbootclasspathAppendOpt == null);
774
775            return path;
776        }
777
778        /**
779         * Return a collection of files containing system classes.
780         * Returns {@code null} if not running on a modular image.
781         *
782         * @throws UncheckedIOException if an I/O errors occurs
783         */
784        private Collection<Path> systemClasses() throws IOException {
785            // Return "modules" jimage file if available
786            if (Files.isRegularFile(thisSystemModules)) {
787                return Collections.singleton(thisSystemModules);
788            }
789
790            // Exploded module image
791            Path modules = javaHome.resolve("modules");
792            if (Files.isDirectory(modules.resolve("java.base"))) {
793                try (Stream<Path> listedModules = Files.list(modules)) {
794                    return listedModules.collect(Collectors.toList());
795                }
796            }
797
798            // not a modular image that we know about
799            return null;
800        }
801
802        private void lazy() {
803            if (searchPath == null) {
804                try {
805                searchPath = Collections.unmodifiableCollection(computePath());
806                } catch (IOException e) {
807                    // TODO: need better handling here, e.g. javac Abort?
808                    throw new UncheckedIOException(e);
809                }
810            }
811        }
812    }
813
814    /**
815     * A LocationHander to represent modules found from a module-oriented
816     * location such as MODULE_SOURCE_PATH, UPGRADE_MODULE_PATH,
817     * SYSTEM_MODULES and MODULE_PATH.
818     *
819     * The Location can be specified to accept overriding classes from the
820     * {@code --patch-module <module>=<path> } parameter.
821     */
822    private class ModuleLocationHandler extends LocationHandler implements Location {
823        protected final String name;
824        protected final String moduleName;
825        protected final Collection<Path> searchPath;
826        protected final Collection<Path> searchPathWithOverrides;
827        protected final boolean output;
828
829        ModuleLocationHandler(String name, String moduleName, Collection<Path> searchPath,
830                boolean output, boolean allowOverrides) {
831            this.name = name;
832            this.moduleName = moduleName;
833            this.searchPath = searchPath;
834            this.output = output;
835
836            if (allowOverrides && patchMap != null) {
837                SearchPath mPatch = patchMap.get(moduleName);
838                if (mPatch != null) {
839                    SearchPath sp = new SearchPath();
840                    sp.addAll(mPatch);
841                    sp.addAll(searchPath);
842                    searchPathWithOverrides = sp;
843                } else {
844                    searchPathWithOverrides = searchPath;
845                }
846            } else {
847                searchPathWithOverrides = searchPath;
848            }
849        }
850
851        @Override @DefinedBy(Api.COMPILER)
852        public String getName() {
853            return name;
854        }
855
856        @Override @DefinedBy(Api.COMPILER)
857        public boolean isOutputLocation() {
858            return output;
859        }
860
861        @Override // defined by LocationHandler
862        boolean handleOption(Option option, String value) {
863            throw new UnsupportedOperationException();
864        }
865
866        @Override // defined by LocationHandler
867        Collection<Path> getPaths() {
868            // For now, we always return searchPathWithOverrides. This may differ from the
869            // JVM behavior if there is a module-info.class to be found in the overriding
870            // classes.
871            return searchPathWithOverrides;
872        }
873
874        @Override // defined by LocationHandler
875        void setPaths(Iterable<? extends Path> files) throws IOException {
876            throw new UnsupportedOperationException();
877        }
878
879        @Override // defined by LocationHandler
880        String inferModuleName() {
881            return moduleName;
882        }
883    }
884
885    /**
886     * A LocationHandler for simple module-oriented search paths,
887     * like UPGRADE_MODULE_PATH and MODULE_PATH.
888     */
889    private class ModulePathLocationHandler extends SimpleLocationHandler {
890        ModulePathLocationHandler(Location location, Option... options) {
891            super(location, options);
892        }
893
894        @Override
895        public boolean handleOption(Option option, String value) {
896            if (!options.contains(option)) {
897                return false;
898            }
899            setPaths(value == null ? null : getPathEntries(value));
900            return true;
901        }
902
903        @Override
904        Iterable<Set<Location>> listModuleLocations() {
905            if (searchPath == null)
906                return Collections.emptyList();
907
908            return () -> new ModulePathIterator();
909        }
910
911        @Override
912        void setPaths(Iterable<? extends Path> paths) {
913            if (paths != null) {
914                for (Path p: paths) {
915                    checkValidModulePathEntry(p);
916                }
917            }
918            super.setPaths(paths);
919        }
920
921        private void checkValidModulePathEntry(Path p) {
922            if (Files.isDirectory(p)) {
923                // either an exploded module or a directory of modules
924                return;
925            }
926
927            String name = p.getFileName().toString();
928            int lastDot = name.lastIndexOf(".");
929            if (lastDot > 0) {
930                switch (name.substring(lastDot)) {
931                    case ".jar":
932                    case ".jmod":
933                        return;
934                }
935            }
936            throw new IllegalArgumentException(p.toString());
937        }
938
939        class ModulePathIterator implements Iterator<Set<Location>> {
940            Iterator<Path> pathIter = searchPath.iterator();
941            int pathIndex = 0;
942            Set<Location> next = null;
943
944            @Override
945            public boolean hasNext() {
946                if (next != null)
947                    return true;
948
949                while (next == null) {
950                    if (pathIter.hasNext()) {
951                        Path path = pathIter.next();
952                        if (Files.isDirectory(path)) {
953                            next = scanDirectory(path);
954                        } else {
955                            next = scanFile(path);
956                        }
957                        pathIndex++;
958                    } else
959                        return false;
960                }
961                return true;
962            }
963
964            @Override
965            public Set<Location> next() {
966                hasNext();
967                if (next != null) {
968                    Set<Location> result = next;
969                    next = null;
970                    return result;
971                }
972                throw new NoSuchElementException();
973            }
974
975            private Set<Location> scanDirectory(Path path) {
976                Set<Path> paths = new LinkedHashSet<>();
977                Path moduleInfoClass = null;
978                try (DirectoryStream<Path> stream = Files.newDirectoryStream(path)) {
979                    for (Path entry: stream) {
980                        if (entry.endsWith("module-info.class")) {
981                            moduleInfoClass = entry;
982                            break;  // no need to continue scanning
983                        }
984                        paths.add(entry);
985                    }
986                } catch (DirectoryIteratorException | IOException ignore) {
987                    log.error(Errors.LocnCantReadDirectory(path));
988                    return Collections.emptySet();
989                }
990
991                if (moduleInfoClass != null) {
992                    // It's an exploded module directly on the module path.
993                    // We can't infer module name from the directory name, so have to
994                    // read module-info.class.
995                    try {
996                        String moduleName = readModuleName(moduleInfoClass);
997                        String name = location.getName()
998                                + "[" + pathIndex + ":" + moduleName + "]";
999                        ModuleLocationHandler l = new ModuleLocationHandler(name, moduleName,
1000                                Collections.singleton(path), false, true);
1001                        return Collections.singleton(l);
1002                    } catch (ModuleNameReader.BadClassFile e) {
1003                        log.error(Errors.LocnBadModuleInfo(path));
1004                        return Collections.emptySet();
1005                    } catch (IOException e) {
1006                        log.error(Errors.LocnCantReadFile(path));
1007                        return Collections.emptySet();
1008                    }
1009                }
1010
1011                // A directory of modules
1012                Set<Location> result = new LinkedHashSet<>();
1013                int index = 0;
1014                for (Path entry : paths) {
1015                    Pair<String,Path> module = inferModuleName(entry);
1016                    if (module == null) {
1017                        // diagnostic reported if necessary; skip to next
1018                        continue;
1019                    }
1020                    String moduleName = module.fst;
1021                    Path modulePath = module.snd;
1022                    String name = location.getName()
1023                            + "[" + pathIndex + "." + (index++) + ":" + moduleName + "]";
1024                    ModuleLocationHandler l = new ModuleLocationHandler(name, moduleName,
1025                            Collections.singleton(modulePath), false, true);
1026                    result.add(l);
1027                }
1028                return result;
1029            }
1030
1031            private Set<Location> scanFile(Path path) {
1032                Pair<String,Path> module = inferModuleName(path);
1033                if (module == null) {
1034                    // diagnostic reported if necessary
1035                    return Collections.emptySet();
1036                }
1037                String moduleName = module.fst;
1038                Path modulePath = module.snd;
1039                String name = location.getName()
1040                        + "[" + pathIndex + ":" + moduleName + "]";
1041                ModuleLocationHandler l = new ModuleLocationHandler(name, moduleName,
1042                        Collections.singleton(modulePath), false, true);
1043                return Collections.singleton(l);
1044            }
1045
1046            private Pair<String,Path> inferModuleName(Path p) {
1047                if (Files.isDirectory(p)) {
1048                    if (Files.exists(p.resolve("module-info.class"))) {
1049                        String name = p.getFileName().toString();
1050                        if (SourceVersion.isName(name))
1051                            return new Pair<>(name, p);
1052                    }
1053                    return null;
1054                }
1055
1056                if (p.getFileName().toString().endsWith(".jar") && fsInfo.exists(p)) {
1057                    URI uri = URI.create("jar:" + p.toUri());
1058                    try (FileSystem fs = FileSystems.newFileSystem(uri, fsEnv, null)) {
1059                        Path moduleInfoClass = fs.getPath("module-info.class");
1060                        if (Files.exists(moduleInfoClass)) {
1061                            String moduleName = readModuleName(moduleInfoClass);
1062                            return new Pair<>(moduleName, p);
1063                        }
1064                    } catch (ModuleNameReader.BadClassFile e) {
1065                        log.error(Errors.LocnBadModuleInfo(p));
1066                        return null;
1067                    } catch (IOException e) {
1068                        log.error(Errors.LocnCantReadFile(p));
1069                        return null;
1070                    } catch (ProviderNotFoundException e) {
1071                        log.error(Errors.NoZipfsForArchive(p));
1072                        return null;
1073                    }
1074
1075                    //automatic module:
1076                    String fn = p.getFileName().toString();
1077                    //from ModulePath.deriveModuleDescriptor:
1078
1079                    // drop .jar
1080                    String mn = fn.substring(0, fn.length()-4);
1081
1082                    // find first occurrence of -${NUMBER}. or -${NUMBER}$
1083                    Matcher matcher = Pattern.compile("-(\\d+(\\.|$))").matcher(mn);
1084                    if (matcher.find()) {
1085                        int start = matcher.start();
1086
1087                        mn = mn.substring(0, start);
1088                    }
1089
1090                    // finally clean up the module name
1091                    mn =  mn.replaceAll("[^A-Za-z0-9]", ".")  // replace non-alphanumeric
1092                            .replaceAll("(\\.)(\\1)+", ".")   // collapse repeating dots
1093                            .replaceAll("^\\.", "")           // drop leading dots
1094                            .replaceAll("\\.$", "");          // drop trailing dots
1095
1096
1097                    if (!mn.isEmpty()) {
1098                        return new Pair<>(mn, p);
1099                    }
1100
1101                    log.error(Errors.LocnCantGetModuleNameForJar(p));
1102                    return null;
1103                }
1104
1105                if (p.getFileName().toString().endsWith(".jmod")) {
1106                    try {
1107                        // check if the JMOD file is valid
1108                        JDK9Wrappers.JmodFile.checkMagic(p);
1109
1110                        // No JMOD file system.  Use JarFileSystem to
1111                        // workaround for now
1112                        FileSystem fs = fileSystems.get(p);
1113                        if (fs == null) {
1114                            URI uri = URI.create("jar:" + p.toUri());
1115                            fs = FileSystems.newFileSystem(uri, Collections.emptyMap(), null);
1116                            try {
1117                                Path moduleInfoClass = fs.getPath("classes/module-info.class");
1118                                String moduleName = readModuleName(moduleInfoClass);
1119                                Path modulePath = fs.getPath("classes");
1120                                fileSystems.put(p, fs);
1121                                closeables.add(fs);
1122                                fs = null; // prevent fs being closed in the finally clause
1123                                return new Pair<>(moduleName, modulePath);
1124                            } finally {
1125                                if (fs != null)
1126                                    fs.close();
1127                            }
1128                        }
1129                    } catch (ModuleNameReader.BadClassFile e) {
1130                        log.error(Errors.LocnBadModuleInfo(p));
1131                    } catch (IOException | ProviderNotFoundException e) {
1132                        log.error(Errors.LocnCantReadFile(p));
1133                        return null;
1134                    }
1135                }
1136
1137                if (warn && false) {  // temp disable, when enabled, massage examples.not-yet.txt suitably.
1138                    log.warning(Warnings.LocnUnknownFileOnModulePath(p));
1139                }
1140                return null;
1141            }
1142
1143            private String readModuleName(Path path) throws IOException, ModuleNameReader.BadClassFile {
1144                if (moduleNameReader == null)
1145                    moduleNameReader = new ModuleNameReader();
1146                return moduleNameReader.readModuleName(path);
1147            }
1148        }
1149
1150    }
1151
1152    private class ModuleSourcePathLocationHandler extends BasicLocationHandler {
1153
1154        private Map<String, Location> moduleLocations;
1155        private Map<Path, Location> pathLocations;
1156
1157
1158        ModuleSourcePathLocationHandler() {
1159            super(StandardLocation.MODULE_SOURCE_PATH,
1160                    Option.MODULE_SOURCE_PATH);
1161        }
1162
1163        @Override
1164        boolean handleOption(Option option, String value) {
1165            init(value);
1166            return true;
1167        }
1168
1169        void init(String value) {
1170            Collection<String> segments = new ArrayList<>();
1171            for (String s: value.split(File.pathSeparator)) {
1172                expandBraces(s, segments);
1173            }
1174
1175            Map<String, Collection<Path>> map = new LinkedHashMap<>();
1176            final String MARKER = "*";
1177            for (String seg: segments) {
1178                int markStart = seg.indexOf(MARKER);
1179                if (markStart == -1) {
1180                    add(map, getPath(seg), null);
1181                } else {
1182                    if (markStart == 0 || !isSeparator(seg.charAt(markStart - 1))) {
1183                        throw new IllegalArgumentException("illegal use of " + MARKER + " in " + seg);
1184                    }
1185                    Path prefix = getPath(seg.substring(0, markStart - 1));
1186                    Path suffix;
1187                    int markEnd = markStart + MARKER.length();
1188                    if (markEnd == seg.length()) {
1189                        suffix = null;
1190                    } else if (!isSeparator(seg.charAt(markEnd))
1191                            || seg.indexOf(MARKER, markEnd) != -1) {
1192                        throw new IllegalArgumentException("illegal use of " + MARKER + " in " + seg);
1193                    } else {
1194                        suffix = getPath(seg.substring(markEnd + 1));
1195                    }
1196                    add(map, prefix, suffix);
1197                }
1198            }
1199
1200            moduleLocations = new LinkedHashMap<>();
1201            pathLocations = new LinkedHashMap<>();
1202            map.forEach((k, v) -> {
1203                String name = location.getName() + "[" + k + "]";
1204                ModuleLocationHandler h = new ModuleLocationHandler(name, k, v, false, false);
1205                moduleLocations.put(k, h);
1206                v.forEach(p -> pathLocations.put(normalize(p), h));
1207            });
1208        }
1209
1210        private boolean isSeparator(char ch) {
1211            // allow both separators on Windows
1212            return (ch == File.separatorChar) || (ch == '/');
1213        }
1214
1215        void add(Map<String, Collection<Path>> map, Path prefix, Path suffix) {
1216            if (!Files.isDirectory(prefix)) {
1217                if (warn) {
1218                    String key = Files.exists(prefix)
1219                            ? "dir.path.element.not.directory"
1220                            : "dir.path.element.not.found";
1221                    log.warning(Lint.LintCategory.PATH, key, prefix);
1222                }
1223                return;
1224            }
1225            try (DirectoryStream<Path> stream = Files.newDirectoryStream(prefix, path -> Files.isDirectory(path))) {
1226                for (Path entry: stream) {
1227                    Path path = (suffix == null) ? entry : entry.resolve(suffix);
1228                    if (Files.isDirectory(path)) {
1229                        String name = entry.getFileName().toString();
1230                        Collection<Path> paths = map.get(name);
1231                        if (paths == null)
1232                            map.put(name, paths = new ArrayList<>());
1233                        paths.add(path);
1234                    }
1235                }
1236            } catch (IOException e) {
1237                // TODO? What to do?
1238                System.err.println(e);
1239            }
1240        }
1241
1242        private void expandBraces(String value, Collection<String> results) {
1243            int depth = 0;
1244            int start = -1;
1245            String prefix = null;
1246            String suffix = null;
1247            for (int i = 0; i < value.length(); i++) {
1248                switch (value.charAt(i)) {
1249                    case '{':
1250                        depth++;
1251                        if (depth == 1) {
1252                            prefix = value.substring(0, i);
1253                            suffix = value.substring(getMatchingBrace(value, i) + 1);
1254                            start = i + 1;
1255                        }
1256                        break;
1257
1258                    case ',':
1259                        if (depth == 1) {
1260                            String elem = value.substring(start, i);
1261                            expandBraces(prefix + elem + suffix, results);
1262                            start = i + 1;
1263                        }
1264                        break;
1265
1266                    case '}':
1267                        switch (depth) {
1268                            case 0:
1269                                throw new IllegalArgumentException("mismatched braces");
1270
1271                            case 1:
1272                                String elem = value.substring(start, i);
1273                                expandBraces(prefix + elem + suffix, results);
1274                                return;
1275
1276                            default:
1277                                depth--;
1278                        }
1279                        break;
1280                }
1281            }
1282            if (depth > 0)
1283                throw new IllegalArgumentException("mismatched braces");
1284            results.add(value);
1285        }
1286
1287        int getMatchingBrace(String value, int offset) {
1288            int depth = 1;
1289            for (int i = offset + 1; i < value.length(); i++) {
1290                switch (value.charAt(i)) {
1291                    case '{':
1292                        depth++;
1293                        break;
1294
1295                    case '}':
1296                        if (--depth == 0)
1297                            return i;
1298                        break;
1299                }
1300            }
1301            throw new IllegalArgumentException("mismatched braces");
1302        }
1303
1304        @Override
1305        boolean isSet() {
1306            return (moduleLocations != null);
1307        }
1308
1309        @Override
1310        Collection<Path> getPaths() {
1311            throw new UnsupportedOperationException();
1312        }
1313
1314        @Override
1315        void setPaths(Iterable<? extends Path> files) throws IOException {
1316            throw new UnsupportedOperationException();
1317        }
1318
1319        @Override
1320        Location getModuleLocation(String name) {
1321            return (moduleLocations == null) ? null : moduleLocations.get(name);
1322        }
1323
1324        @Override
1325        Location getModuleLocation(Path dir) {
1326            return (pathLocations == null) ? null : pathLocations.get(dir);
1327        }
1328
1329        @Override
1330        Iterable<Set<Location>> listModuleLocations() {
1331            if (moduleLocations == null)
1332                return Collections.emptySet();
1333            Set<Location> locns = new LinkedHashSet<>();
1334            moduleLocations.forEach((k, v) -> locns.add(v));
1335            return Collections.singleton(locns);
1336        }
1337
1338    }
1339
1340    private class SystemModulesLocationHandler extends BasicLocationHandler {
1341        private Path systemJavaHome;
1342        private Path modules;
1343        private Map<String, ModuleLocationHandler> systemModules;
1344
1345        SystemModulesLocationHandler() {
1346            super(StandardLocation.SYSTEM_MODULES, Option.SYSTEM);
1347            systemJavaHome = Locations.javaHome;
1348        }
1349
1350        @Override
1351        boolean handleOption(Option option, String value) {
1352            if (!options.contains(option)) {
1353                return false;
1354            }
1355
1356            if (value == null) {
1357                systemJavaHome = Locations.javaHome;
1358            } else if (value.equals("none")) {
1359                systemJavaHome = null;
1360            } else {
1361                update(getPath(value));
1362            }
1363
1364            modules = null;
1365            return true;
1366        }
1367
1368        @Override
1369        Collection<Path> getPaths() {
1370            return (systemJavaHome == null) ? null : Collections.singleton(systemJavaHome);
1371        }
1372
1373        @Override
1374        void setPaths(Iterable<? extends Path> files) throws IOException {
1375            if (files == null) {
1376                systemJavaHome = null;
1377            } else {
1378                Iterator<? extends Path> pathIter = files.iterator();
1379                if (!pathIter.hasNext()) {
1380                    throw new IllegalArgumentException("empty path for directory"); // TODO: FIXME
1381                }
1382                Path dir = pathIter.next();
1383                if (pathIter.hasNext()) {
1384                    throw new IllegalArgumentException("path too long for directory"); // TODO: FIXME
1385                }
1386                if (!Files.exists(dir)) {
1387                    throw new FileNotFoundException(dir + ": does not exist");
1388                } else if (!Files.isDirectory(dir)) {
1389                    throw new IOException(dir + ": not a directory");
1390                }
1391                update(dir);
1392            }
1393        }
1394
1395        private void update(Path p) {
1396            if (!isCurrentPlatform(p) && !Files.exists(p.resolve("jrt-fs.jar")) && !Files.exists(systemJavaHome.resolve("modules")))
1397                throw new IllegalArgumentException(p.toString());
1398            systemJavaHome = p;
1399            modules = null;
1400        }
1401
1402        private boolean isCurrentPlatform(Path p) {
1403            try {
1404                return Files.isSameFile(p, Locations.javaHome);
1405            } catch (IOException ex) {
1406                throw new IllegalArgumentException(p.toString(), ex);
1407            }
1408        }
1409
1410        @Override
1411        Location getModuleLocation(String name) throws IOException {
1412            initSystemModules();
1413            return systemModules.get(name);
1414        }
1415
1416        @Override
1417        Iterable<Set<Location>> listModuleLocations() throws IOException {
1418            initSystemModules();
1419            Set<Location> locns = new LinkedHashSet<>();
1420            for (Location l: systemModules.values())
1421                locns.add(l);
1422            return Collections.singleton(locns);
1423        }
1424
1425        private void initSystemModules() throws IOException {
1426            if (systemModules != null) {
1427                return;
1428            }
1429
1430            if (systemJavaHome == null) {
1431                systemModules = Collections.emptyMap();
1432                return;
1433            }
1434
1435            if (modules == null) {
1436                try {
1437                    URI jrtURI = URI.create("jrt:/");
1438                    FileSystem jrtfs;
1439
1440                    if (isCurrentPlatform(systemJavaHome)) {
1441                        jrtfs = FileSystems.getFileSystem(jrtURI);
1442                    } else {
1443                        try {
1444                            Map<String, String> attrMap =
1445                                    Collections.singletonMap("java.home", systemJavaHome.toString());
1446                            jrtfs = FileSystems.newFileSystem(jrtURI, attrMap);
1447                        } catch (ProviderNotFoundException ex) {
1448                            URL javaHomeURL = systemJavaHome.resolve("jrt-fs.jar").toUri().toURL();
1449                            ClassLoader currentLoader = Locations.class.getClassLoader();
1450                            URLClassLoader fsLoader =
1451                                    new URLClassLoader(new URL[] {javaHomeURL}, currentLoader);
1452
1453                            jrtfs = FileSystems.newFileSystem(jrtURI, Collections.emptyMap(), fsLoader);
1454
1455                            closeables.add(fsLoader);
1456                        }
1457
1458                        closeables.add(jrtfs);
1459                    }
1460
1461                    modules = jrtfs.getPath("/modules");
1462                } catch (FileSystemNotFoundException | ProviderNotFoundException e) {
1463                    modules = systemJavaHome.resolve("modules");
1464                    if (!Files.exists(modules))
1465                        throw new IOException("can't find system classes", e);
1466                }
1467            }
1468
1469            systemModules = new LinkedHashMap<>();
1470            try (DirectoryStream<Path> stream = Files.newDirectoryStream(modules, Files::isDirectory)) {
1471                for (Path entry : stream) {
1472                    String moduleName = entry.getFileName().toString();
1473                    String name = location.getName() + "[" + moduleName + "]";
1474                    ModuleLocationHandler h = new ModuleLocationHandler(name, moduleName,
1475                            Collections.singleton(entry), false, true);
1476                    systemModules.put(moduleName, h);
1477                }
1478            }
1479        }
1480    }
1481
1482    Map<Location, LocationHandler> handlersForLocation;
1483    Map<Option, LocationHandler> handlersForOption;
1484
1485    void initHandlers() {
1486        handlersForLocation = new HashMap<>();
1487        handlersForOption = new EnumMap<>(Option.class);
1488
1489        BasicLocationHandler[] handlers = {
1490            new BootClassPathLocationHandler(),
1491            new ClassPathLocationHandler(),
1492            new SimpleLocationHandler(StandardLocation.SOURCE_PATH, Option.SOURCE_PATH),
1493            new SimpleLocationHandler(StandardLocation.ANNOTATION_PROCESSOR_PATH, Option.PROCESSOR_PATH),
1494            new SimpleLocationHandler(StandardLocation.ANNOTATION_PROCESSOR_MODULE_PATH, Option.PROCESSOR_MODULE_PATH),
1495            new OutputLocationHandler(StandardLocation.CLASS_OUTPUT, Option.D),
1496            new OutputLocationHandler(StandardLocation.SOURCE_OUTPUT, Option.S),
1497            new OutputLocationHandler(StandardLocation.NATIVE_HEADER_OUTPUT, Option.H),
1498            new ModuleSourcePathLocationHandler(),
1499            // TODO: should UPGRADE_MODULE_PATH be merged with SYSTEM_MODULES?
1500            new ModulePathLocationHandler(StandardLocation.UPGRADE_MODULE_PATH, Option.UPGRADE_MODULE_PATH),
1501            new ModulePathLocationHandler(StandardLocation.MODULE_PATH, Option.MODULE_PATH),
1502            new SystemModulesLocationHandler(),
1503        };
1504
1505        for (BasicLocationHandler h : handlers) {
1506            handlersForLocation.put(h.location, h);
1507            for (Option o : h.options) {
1508                handlersForOption.put(o, h);
1509            }
1510        }
1511    }
1512
1513    private Map<String, SearchPath> patchMap;
1514
1515    boolean handleOption(Option option, String value) {
1516        switch (option) {
1517            case PATCH_MODULE:
1518                if (value == null) {
1519                    patchMap = null;
1520                } else {
1521                    // Allow an extended syntax for --patch-module consisting of a series
1522                    // of values separated by NULL characters. This is to facilitate
1523                    // supporting deferred file manager options on the command line.
1524                    // See Option.PATCH_MODULE for the code that composes these multiple
1525                    // values.
1526                    for (String v : value.split("\0")) {
1527                        int eq = v.indexOf('=');
1528                        if (eq > 0) {
1529                            String mName = v.substring(0, eq);
1530                            SearchPath mPatchPath = new SearchPath()
1531                                    .addFiles(v.substring(eq + 1));
1532                            boolean ok = true;
1533                            for (Path p : mPatchPath) {
1534                                Path mi = p.resolve("module-info.class");
1535                                if (Files.exists(mi)) {
1536                                    log.error(Errors.LocnModuleInfoNotAllowedOnPatchPath(mi));
1537                                    ok = false;
1538                                }
1539                            }
1540                            if (ok) {
1541                                if (patchMap == null) {
1542                                    patchMap = new LinkedHashMap<>();
1543                                }
1544                                patchMap.put(mName, mPatchPath);
1545                            }
1546                        } else {
1547                            // Should not be able to get here;
1548                            // this should be caught and handled in Option.PATCH_MODULE
1549                            log.error(Errors.LocnInvalidArgForXpatch(value));
1550                        }
1551                    }
1552                }
1553                return true;
1554            default:
1555                LocationHandler h = handlersForOption.get(option);
1556                return (h == null ? false : h.handleOption(option, value));
1557        }
1558    }
1559
1560    boolean hasLocation(Location location) {
1561        LocationHandler h = getHandler(location);
1562        return (h == null ? false : h.isSet());
1563    }
1564
1565    Collection<Path> getLocation(Location location) {
1566        LocationHandler h = getHandler(location);
1567        return (h == null ? null : h.getPaths());
1568    }
1569
1570    Path getOutputLocation(Location location) {
1571        if (!location.isOutputLocation()) {
1572            throw new IllegalArgumentException();
1573        }
1574        LocationHandler h = getHandler(location);
1575        return ((OutputLocationHandler) h).outputDir;
1576    }
1577
1578    void setLocation(Location location, Iterable<? extends Path> files) throws IOException {
1579        LocationHandler h = getHandler(location);
1580        if (h == null) {
1581            if (location.isOutputLocation()) {
1582                h = new OutputLocationHandler(location);
1583            } else {
1584                h = new SimpleLocationHandler(location);
1585            }
1586            handlersForLocation.put(location, h);
1587        }
1588        h.setPaths(files);
1589    }
1590
1591    Location getModuleLocation(Location location, String name) throws IOException {
1592        LocationHandler h = getHandler(location);
1593        return (h == null ? null : h.getModuleLocation(name));
1594    }
1595
1596    Location getModuleLocation(Location location, Path dir) {
1597        LocationHandler h = getHandler(location);
1598        return (h == null ? null : h.getModuleLocation(dir));
1599    }
1600
1601    String inferModuleName(Location location) {
1602        LocationHandler h = getHandler(location);
1603        return (h == null ? null : h.inferModuleName());
1604    }
1605
1606    Iterable<Set<Location>> listModuleLocations(Location location) throws IOException {
1607        LocationHandler h = getHandler(location);
1608        return (h == null ? null : h.listModuleLocations());
1609    }
1610
1611    protected LocationHandler getHandler(Location location) {
1612        Objects.requireNonNull(location);
1613        return (location instanceof LocationHandler)
1614                ? (LocationHandler) location
1615                : handlersForLocation.get(location);
1616    }
1617
1618    /**
1619     * Is this the name of an archive file?
1620     */
1621    private boolean isArchive(Path file) {
1622        String n = StringUtils.toLowerCase(file.getFileName().toString());
1623        return fsInfo.isFile(file)
1624                && (n.endsWith(".jar") || n.endsWith(".zip"));
1625    }
1626
1627    static Path normalize(Path p) {
1628        try {
1629            return p.toRealPath();
1630        } catch (IOException e) {
1631            return p.toAbsolutePath().normalize();
1632        }
1633    }
1634
1635}
1636