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