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