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