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