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