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