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