Locations.java revision 3294:9adfb22ff08f
1199482Srdivacky/*
2199482Srdivacky * Copyright (c) 2003, 2016, Oracle and/or its affiliates. All rights reserved.
3199482Srdivacky * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4199482Srdivacky *
5199482Srdivacky * This code is free software; you can redistribute it and/or modify it
6199482Srdivacky * under the terms of the GNU General Public License version 2 only, as
7199482Srdivacky * published by the Free Software Foundation.  Oracle designates this
8199482Srdivacky * particular file as subject to the "Classpath" exception as provided
9199482Srdivacky * by Oracle in the LICENSE file that accompanied this code.
10199482Srdivacky *
11199482Srdivacky * This code is distributed in the hope that it will be useful, but WITHOUT
12199482Srdivacky * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13199482Srdivacky * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
14203955Srdivacky * version 2 for more details (a copy is included in the LICENSE file that
15203955Srdivacky * accompanied this code).
16199482Srdivacky *
17199482Srdivacky * You should have received a copy of the GNU General Public License version
18199482Srdivacky * 2 along with this work; if not, write to the Free Software Foundation,
19234353Sdim * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
20234353Sdim *
21199482Srdivacky * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
22205408Srdivacky * or visit www.oracle.com if you need additional information or have any
23205408Srdivacky * questions.
24205408Srdivacky */
25205408Srdivacky
26205408Srdivackypackage com.sun.tools.javac.file;
27205408Srdivacky
28205408Srdivackyimport java.io.Closeable;
29226633Sdimimport java.io.File;
30205408Srdivackyimport java.io.FileNotFoundException;
31205408Srdivackyimport java.io.IOException;
32205408Srdivackyimport java.io.UncheckedIOException;
33205408Srdivackyimport java.net.URI;
34205408Srdivackyimport java.net.URL;
35205408Srdivackyimport java.net.URLClassLoader;
36205408Srdivackyimport java.nio.file.DirectoryIteratorException;
37205408Srdivackyimport java.nio.file.DirectoryStream;
38199482Srdivackyimport java.nio.file.FileSystem;
39199482Srdivackyimport java.nio.file.FileSystemNotFoundException;
40199482Srdivackyimport java.nio.file.FileSystems;
41199482Srdivackyimport java.nio.file.Files;
42199482Srdivackyimport java.nio.file.Path;
43199482Srdivackyimport java.nio.file.Paths;
44226633Sdimimport java.nio.file.ProviderNotFoundException;
45199482Srdivackyimport java.util.ArrayList;
46199482Srdivackyimport java.util.Arrays;
47199482Srdivackyimport java.util.Collection;
48199482Srdivackyimport java.util.Collections;
49199482Srdivackyimport java.util.EnumMap;
50226633Sdimimport java.util.EnumSet;
51199482Srdivackyimport java.util.HashMap;
52199482Srdivackyimport java.util.HashSet;
53239462Sdimimport java.util.Iterator;
54239462Sdimimport java.util.LinkedHashMap;
55239462Sdimimport java.util.LinkedHashSet;
56239462Sdimimport java.util.List;
57239462Sdimimport java.util.Map;
58239462Sdimimport java.util.Objects;
59199482Srdivackyimport java.util.NoSuchElementException;
60199482Srdivackyimport java.util.Set;
61199482Srdivackyimport java.util.regex.Matcher;
62226633Sdimimport java.util.regex.Pattern;
63199482Srdivackyimport java.util.stream.Collectors;
64199482Srdivackyimport java.util.stream.Stream;
65199482Srdivackyimport java.util.zip.ZipFile;
66199482Srdivacky
67199482Srdivackyimport javax.lang.model.SourceVersion;
68226633Sdimimport javax.tools.JavaFileManager;
69199482Srdivackyimport javax.tools.JavaFileManager.Location;
70199482Srdivackyimport javax.tools.StandardJavaFileManager;
71199482Srdivackyimport javax.tools.StandardLocation;
72199482Srdivacky
73199482Srdivackyimport com.sun.tools.javac.code.Lint;
74226633Sdimimport com.sun.tools.javac.main.Option;
75199482Srdivackyimport com.sun.tools.javac.resources.CompilerProperties.Errors;
76234353Sdimimport com.sun.tools.javac.resources.CompilerProperties.Warnings;
77234353Sdimimport com.sun.tools.javac.util.ListBuffer;
78226633Sdimimport com.sun.tools.javac.util.Log;
79199482Srdivackyimport com.sun.tools.javac.util.Pair;
80210299Sedimport com.sun.tools.javac.util.StringUtils;
81212904Sdim
82212904Sdimimport static javax.tools.StandardLocation.PLATFORM_CLASS_PATH;
83234353Sdim
84234353Sdimimport static com.sun.tools.javac.main.Option.BOOTCLASSPATH;
85234353Sdimimport static com.sun.tools.javac.main.Option.DJAVA_ENDORSED_DIRS;
86234353Sdimimport static com.sun.tools.javac.main.Option.DJAVA_EXT_DIRS;
87234353Sdimimport static com.sun.tools.javac.main.Option.ENDORSEDDIRS;
88234353Sdimimport static com.sun.tools.javac.main.Option.EXTDIRS;
89234353Sdimimport static com.sun.tools.javac.main.Option.XBOOTCLASSPATH;
90234353Sdimimport static com.sun.tools.javac.main.Option.XBOOTCLASSPATH_APPEND;
91234353Sdimimport static com.sun.tools.javac.main.Option.XBOOTCLASSPATH_PREPEND;
92234353Sdim
93234353Sdim/**
94234353Sdim * This class converts command line arguments, environment variables and system properties (in
95234353Sdim * File.pathSeparator-separated String form) into a boot class path, user class path, and source
96263508Sdim * path (in {@code Collection<String>} form).
97226633Sdim *
98234353Sdim * <p>
99234353Sdim * <b>This is NOT part of any supported API. If you write code that depends on this, you do so at
100234353Sdim * your own risk. This code and its internal interfaces are subject to change or deletion without
101234353Sdim * notice.</b>
102234353Sdim */
103234353Sdimpublic class Locations {
104234353Sdim
105234353Sdim    /**
106234353Sdim     * The log to use for warning output
107234353Sdim     */
108234353Sdim    private Log log;
109263508Sdim
110263508Sdim    /**
111263508Sdim     * Access to (possibly cached) file info
112234353Sdim     */
113234353Sdim    private FSInfo fsInfo;
114212904Sdim
115212904Sdim    /**
116212904Sdim     * Whether to warn about non-existent path elements
117212904Sdim     */
118212904Sdim    private boolean warn;
119226633Sdim
120212904Sdim    private ModuleNameReader moduleNameReader;
121218893Sdim
122226633Sdim    static final Path javaHome = Paths.get(System.getProperty("java.home"));
123199482Srdivacky    static final Path thisSystemModules = javaHome.resolve("lib").resolve("modules");
124249423Sdim
125199482Srdivacky    Map<Path, FileSystem> fileSystems = new LinkedHashMap<>();
126199482Srdivacky    List<Closeable> closeables = new ArrayList<>();
127199482Srdivacky
128226633Sdim    Locations() {
129199482Srdivacky        initHandlers();
130199482Srdivacky    }
131199482Srdivacky
132199482Srdivacky    public void close() throws IOException {
133199482Srdivacky        ListBuffer<IOException> list = new ListBuffer<>();
134249423Sdim        closeables.forEach(closeable -> {
135249423Sdim            try {
136249423Sdim                closeable.close();
137249423Sdim            } catch (IOException ex) {
138249423Sdim                list.add(ex);
139249423Sdim            }
140249423Sdim        });
141249423Sdim        if (list.nonEmpty()) {
142249423Sdim            IOException ex = new IOException();
143249423Sdim            for (IOException e: list)
144249423Sdim                ex.addSuppressed(e);
145249423Sdim            throw ex;
146249423Sdim        }
147249423Sdim    }
148249423Sdim
149203955Srdivacky    // could replace Lint by "boolean warn"
150203955Srdivacky    void update(Log log, Lint lint, FSInfo fsInfo) {
151203955Srdivacky        this.log = log;
152203955Srdivacky        warn = lint.isEnabled(Lint.LintCategory.PATH);
153203955Srdivacky        this.fsInfo = fsInfo;
154203955Srdivacky    }
155203955Srdivacky
156203955Srdivacky    boolean isDefaultBootClassPath() {
157203955Srdivacky        BootClassPathLocationHandler h
158203955Srdivacky                = (BootClassPathLocationHandler) getHandler(PLATFORM_CLASS_PATH);
159203955Srdivacky        return h.isDefault();
160203955Srdivacky    }
161203955Srdivacky
162203955Srdivacky    /**
163203955Srdivacky     * Split a search path into its elements. Empty path elements will be ignored.
164203955Srdivacky     *
165203955Srdivacky     * @param searchPath The search path to be split
166226633Sdim     * @return The elements of the path
167203955Srdivacky     */
168203955Srdivacky    private static Iterable<Path> getPathEntries(String searchPath) {
169226633Sdim        return getPathEntries(searchPath, null);
170203955Srdivacky    }
171203955Srdivacky
172203955Srdivacky    /**
173203955Srdivacky     * Split a search path into its elements. If emptyPathDefault is not null, all empty elements in the
174203955Srdivacky     * path, including empty elements at either end of the path, will be replaced with the value of
175234353Sdim     * emptyPathDefault.
176203955Srdivacky     *
177203955Srdivacky     * @param searchPath The search path to be split
178203955Srdivacky     * @param emptyPathDefault The value to substitute for empty path elements, or null, to ignore
179226633Sdim     * empty path elements
180203955Srdivacky     * @return The elements of the path
181210299Sed     */
182203955Srdivacky    private static Iterable<Path> getPathEntries(String searchPath, Path emptyPathDefault) {
183203955Srdivacky        ListBuffer<Path> entries = new ListBuffer<>();
184203955Srdivacky        for (String s: searchPath.split(Pattern.quote(File.pathSeparator), -1)) {
185212904Sdim            if (s.isEmpty()) {
186212904Sdim                if (emptyPathDefault != null) {
187212904Sdim                    entries.add(emptyPathDefault);
188226633Sdim                }
189212904Sdim            } else {
190212904Sdim                entries.add(Paths.get(s));
191212904Sdim            }
192212904Sdim        }
193212904Sdim        return entries;
194212904Sdim    }
195199482Srdivacky
196199482Srdivacky    /**
197199482Srdivacky     * Utility class to help evaluate a path option. Duplicate entries are ignored, jar class paths
198199482Srdivacky     * can be expanded.
199199482Srdivacky     */
200199482Srdivacky    private class SearchPath extends LinkedHashSet<Path> {
201199482Srdivacky
202199482Srdivacky        private static final long serialVersionUID = 0;
203199482Srdivacky
204199482Srdivacky        private boolean expandJarClassPaths = false;
205199482Srdivacky        private final Set<Path> canonicalValues = new HashSet<>();
206199482Srdivacky
207199482Srdivacky        public SearchPath expandJarClassPaths(boolean x) {
208199482Srdivacky            expandJarClassPaths = x;
209199482Srdivacky            return this;
210199482Srdivacky        }
211199482Srdivacky
212199482Srdivacky        /**
213199482Srdivacky         * What to use when path element is the empty string
214199482Srdivacky         */
215199482Srdivacky        private Path emptyPathDefault = null;
216199482Srdivacky
217199482Srdivacky        public SearchPath emptyPathDefault(Path x) {
218199482Srdivacky            emptyPathDefault = x;
219199482Srdivacky            return this;
220199482Srdivacky        }
221199482Srdivacky
222199482Srdivacky        public SearchPath addDirectories(String dirs, boolean warn) {
223199482Srdivacky            boolean prev = expandJarClassPaths;
224199482Srdivacky            expandJarClassPaths = true;
225212904Sdim            try {
226199482Srdivacky                if (dirs != null) {
227199482Srdivacky                    for (Path dir : getPathEntries(dirs)) {
228199482Srdivacky                        addDirectory(dir, warn);
229                    }
230                }
231                return this;
232            } finally {
233                expandJarClassPaths = prev;
234            }
235        }
236
237        public SearchPath addDirectories(String dirs) {
238            return addDirectories(dirs, warn);
239        }
240
241        private void addDirectory(Path dir, boolean warn) {
242            if (!Files.isDirectory(dir)) {
243                if (warn) {
244                    log.warning(Lint.LintCategory.PATH,
245                            "dir.path.element.not.found", dir);
246                }
247                return;
248            }
249
250            try (Stream<Path> s = Files.list(dir)) {
251                s.filter(dirEntry -> isArchive(dirEntry))
252                        .forEach(dirEntry -> addFile(dirEntry, warn));
253            } catch (IOException ignore) {
254            }
255        }
256
257        public SearchPath addFiles(String files, boolean warn) {
258            if (files != null) {
259                addFiles(getPathEntries(files, emptyPathDefault), warn);
260            }
261            return this;
262        }
263
264        public SearchPath addFiles(String files) {
265            return addFiles(files, warn);
266        }
267
268        public SearchPath addFiles(Iterable<? extends Path> files, boolean warn) {
269            if (files != null) {
270                for (Path file : files) {
271                    addFile(file, warn);
272                }
273            }
274            return this;
275        }
276
277        public SearchPath addFiles(Iterable<? extends Path> files) {
278            return addFiles(files, warn);
279        }
280
281        public void addFile(Path file, boolean warn) {
282            if (contains(file)) {
283                // discard duplicates
284                return;
285            }
286
287            if (!fsInfo.exists(file)) {
288                /* No such file or directory exists */
289                if (warn) {
290                    log.warning(Lint.LintCategory.PATH,
291                            "path.element.not.found", file);
292                }
293                super.add(file);
294                return;
295            }
296
297            Path canonFile = fsInfo.getCanonicalFile(file);
298            if (canonicalValues.contains(canonFile)) {
299                /* Discard duplicates and avoid infinite recursion */
300                return;
301            }
302
303            if (fsInfo.isFile(file)) {
304                /* File is an ordinary file. */
305                if (!isArchive(file)
306                        && !file.getFileName().toString().endsWith(".jmod")
307                        && !file.endsWith("modules")) {
308                    /* Not a recognized extension; open it to see if
309                     it looks like a valid zip file. */
310                    try {
311                        // TODO: use of ZipFile should be updated
312                        ZipFile z = new ZipFile(file.toFile());
313                        z.close();
314                        if (warn) {
315                            log.warning(Lint.LintCategory.PATH,
316                                    "unexpected.archive.file", file);
317                        }
318                    } catch (IOException e) {
319                        // FIXME: include e.getLocalizedMessage in warning
320                        if (warn) {
321                            log.warning(Lint.LintCategory.PATH,
322                                    "invalid.archive.file", file);
323                        }
324                        return;
325                    }
326                }
327            }
328
329            /* Now what we have left is either a directory or a file name
330             conforming to archive naming convention */
331            super.add(file);
332            canonicalValues.add(canonFile);
333
334            if (expandJarClassPaths && fsInfo.isFile(file) && !file.endsWith("modules")) {
335                addJarClassPath(file, warn);
336            }
337        }
338
339        // Adds referenced classpath elements from a jar's Class-Path
340        // Manifest entry.  In some future release, we may want to
341        // update this code to recognize URLs rather than simple
342        // filenames, but if we do, we should redo all path-related code.
343        private void addJarClassPath(Path jarFile, boolean warn) {
344            try {
345                for (Path f : fsInfo.getJarClassPath(jarFile)) {
346                    addFile(f, warn);
347                }
348            } catch (IOException e) {
349                log.error("error.reading.file", jarFile, JavacFileManager.getMessage(e));
350            }
351        }
352    }
353
354    /**
355     * Base class for handling support for the representation of Locations.
356     *
357     * Locations are (by design) opaque handles that can easily be implemented
358     * by enums like StandardLocation. Within JavacFileManager, each Location
359     * has an associated LocationHandler, which provides much of the appropriate
360     * functionality for the corresponding Location.
361     *
362     * @see #initHandlers
363     * @see #getHandler
364     */
365    protected abstract class LocationHandler {
366
367        /**
368         * @see JavaFileManager#handleOption
369         */
370        abstract boolean handleOption(Option option, String value);
371
372        /**
373         * @see StandardJavaFileManager#hasLocation
374         */
375        boolean isSet() {
376            return (getPaths() != null);
377        }
378
379        /**
380         * @see StandardJavaFileManager#getLocation
381         */
382        abstract Collection<Path> getPaths();
383
384        /**
385         * @see StandardJavaFileManager#setLocation
386         */
387        abstract void setPaths(Iterable<? extends Path> files) throws IOException;
388
389        /**
390         * @see JavaFileManager#getModuleLocation(Location, String)
391         */
392        Location getModuleLocation(String moduleName) throws IOException {
393            return null;
394        }
395
396        /**
397         * @see JavaFileManager#getModuleLocation(Location, JavaFileObject, String)
398         */
399        Location getModuleLocation(Path dir) {
400            return null;
401        }
402
403        /**
404         * @see JavaFileManager#inferModuleName
405         */
406        String inferModuleName() {
407            return null;
408        }
409
410        /**
411         * @see JavaFileManager#listModuleLocations
412         */
413        Iterable<Set<Location>> listModuleLocations() throws IOException {
414            return null;
415        }
416    }
417
418    /**
419     * A LocationHandler for a given Location, and associated set of options.
420     */
421    private abstract class BasicLocationHandler extends LocationHandler {
422
423        final Location location;
424        final Set<Option> options;
425
426        /**
427         * Create a handler. The location and options provide a way to map from a location or an
428         * option to the corresponding handler.
429         *
430         * @param location the location for which this is the handler
431         * @param options the options affecting this location
432         * @see #initHandlers
433         */
434        protected BasicLocationHandler(Location location, Option... options) {
435            this.location = location;
436            this.options = options.length == 0
437                    ? EnumSet.noneOf(Option.class)
438                    : EnumSet.copyOf(Arrays.asList(options));
439        }
440    }
441
442    /**
443     * General purpose implementation for output locations, such as -d/CLASS_OUTPUT and
444     * -s/SOURCE_OUTPUT. All options are treated as equivalent (i.e. aliases.)
445     * The value is a single file, possibly null.
446     */
447    private class OutputLocationHandler extends BasicLocationHandler {
448
449        private Path outputDir;
450        private Map<String, Location> moduleLocations;
451
452        OutputLocationHandler(Location location, Option... options) {
453            super(location, options);
454        }
455
456        @Override
457        boolean handleOption(Option option, String value) {
458            if (!options.contains(option)) {
459                return false;
460            }
461
462            // TODO: could/should validate outputDir exists and is a directory
463            // need to decide how best to report issue for benefit of
464            // direct API call on JavaFileManager.handleOption(specifies IAE)
465            // vs. command line decoding.
466            outputDir = (value == null) ? null : Paths.get(value);
467            return true;
468        }
469
470        @Override
471        Collection<Path> getPaths() {
472            return (outputDir == null) ? null : Collections.singleton(outputDir);
473        }
474
475        @Override
476        void setPaths(Iterable<? extends Path> files) throws IOException {
477            if (files == null) {
478                outputDir = null;
479            } else {
480                Iterator<? extends Path> pathIter = files.iterator();
481                if (!pathIter.hasNext()) {
482                    throw new IllegalArgumentException("empty path for directory");
483                }
484                Path dir = pathIter.next();
485                if (pathIter.hasNext()) {
486                    throw new IllegalArgumentException("path too long for directory");
487                }
488                if (!Files.exists(dir)) {
489                    throw new FileNotFoundException(dir + ": does not exist");
490                } else if (!Files.isDirectory(dir)) {
491                    throw new IOException(dir + ": not a directory");
492                }
493                outputDir = dir;
494            }
495            moduleLocations = null;
496        }
497
498        @Override
499        Location getModuleLocation(String name) {
500            if (moduleLocations == null)
501                moduleLocations = new HashMap<>();
502            Location l = moduleLocations.get(name);
503            if (l == null) {
504                l = new ModuleLocationHandler(location.getName() + "[" + name + "]",
505                        name,
506                        Collections.singleton(outputDir.resolve(name)),
507                        true, false);
508                moduleLocations.put(name, l);
509            }
510            return l;
511        }
512    }
513
514    /**
515     * General purpose implementation for search path locations,
516     * such as -sourcepath/SOURCE_PATH and -processorPath/ANNOTATION_PROCESSOR_PATH.
517     * All options are treated as equivalent (i.e. aliases.)
518     * The value is an ordered set of files and/or directories.
519     */
520    private class SimpleLocationHandler extends BasicLocationHandler {
521
522        protected Collection<Path> searchPath;
523
524        SimpleLocationHandler(Location location, Option... options) {
525            super(location, options);
526        }
527
528        @Override
529        boolean handleOption(Option option, String value) {
530            if (!options.contains(option)) {
531                return false;
532            }
533            searchPath = value == null ? null
534                    : Collections.unmodifiableCollection(createPath().addFiles(value));
535            return true;
536        }
537
538        @Override
539        Collection<Path> getPaths() {
540            return searchPath;
541        }
542
543        @Override
544        void setPaths(Iterable<? extends Path> files) {
545            SearchPath p;
546            if (files == null) {
547                p = computePath(null);
548            } else {
549                p = createPath().addFiles(files);
550            }
551            searchPath = Collections.unmodifiableCollection(p);
552        }
553
554        protected SearchPath computePath(String value) {
555            return createPath().addFiles(value);
556        }
557
558        protected SearchPath createPath() {
559            return new SearchPath();
560        }
561    }
562
563    /**
564     * Subtype of SimpleLocationHandler for -classpath/CLASS_PATH.
565     * If no value is given, a default is provided, based on system properties and other values.
566     */
567    private class ClassPathLocationHandler extends SimpleLocationHandler {
568
569        ClassPathLocationHandler() {
570            super(StandardLocation.CLASS_PATH,
571                    Option.CLASSPATH, Option.CP);
572        }
573
574        @Override
575        Collection<Path> getPaths() {
576            lazy();
577            return searchPath;
578        }
579
580        @Override
581        protected SearchPath computePath(String value) {
582            String cp = value;
583
584            // CLASSPATH environment variable when run from `javac'.
585            if (cp == null) {
586                cp = System.getProperty("env.class.path");
587            }
588
589            // If invoked via a java VM (not the javac launcher), use the
590            // platform class path
591            if (cp == null && System.getProperty("application.home") == null) {
592                cp = System.getProperty("java.class.path");
593            }
594
595            // Default to current working directory.
596            if (cp == null) {
597                cp = ".";
598            }
599
600            return createPath().addFiles(cp);
601        }
602
603        @Override
604        protected SearchPath createPath() {
605            return new SearchPath()
606                    .expandJarClassPaths(true) // Only search user jars for Class-Paths
607                    .emptyPathDefault(Paths.get("."));  // Empty path elt ==> current directory
608        }
609
610        private void lazy() {
611            if (searchPath == null) {
612                setPaths(null);
613            }
614        }
615    }
616
617    /**
618     * Custom subtype of LocationHandler for PLATFORM_CLASS_PATH.
619     * Various options are supported for different components of the
620     * platform class path.
621     * Setting a value with setLocation overrides all existing option values.
622     * Setting any option overrides any value set with setLocation, and
623     * reverts to using default values for options that have not been set.
624     * Setting -bootclasspath or -Xbootclasspath overrides any existing
625     * value for -Xbootclasspath/p: and -Xbootclasspath/a:.
626     */
627    private class BootClassPathLocationHandler extends BasicLocationHandler {
628
629        private Collection<Path> searchPath;
630        final Map<Option, String> optionValues = new EnumMap<>(Option.class);
631
632        /**
633         * Is the bootclasspath the default?
634         */
635        private boolean isDefault;
636
637        BootClassPathLocationHandler() {
638            super(StandardLocation.PLATFORM_CLASS_PATH,
639                    Option.BOOTCLASSPATH, Option.XBOOTCLASSPATH,
640                    Option.XBOOTCLASSPATH_PREPEND,
641                    Option.XBOOTCLASSPATH_APPEND,
642                    Option.ENDORSEDDIRS, Option.DJAVA_ENDORSED_DIRS,
643                    Option.EXTDIRS, Option.DJAVA_EXT_DIRS);
644        }
645
646        boolean isDefault() {
647            lazy();
648            return isDefault;
649        }
650
651        @Override
652        boolean handleOption(Option option, String value) {
653            if (!options.contains(option)) {
654                return false;
655            }
656
657            option = canonicalize(option);
658            optionValues.put(option, value);
659            if (option == BOOTCLASSPATH) {
660                optionValues.remove(XBOOTCLASSPATH_PREPEND);
661                optionValues.remove(XBOOTCLASSPATH_APPEND);
662            }
663            searchPath = null;  // reset to "uninitialized"
664            return true;
665        }
666        // where
667        // TODO: would be better if option aliasing was handled at a higher
668        // level
669        private Option canonicalize(Option option) {
670            switch (option) {
671                case XBOOTCLASSPATH:
672                    return Option.BOOTCLASSPATH;
673                case DJAVA_ENDORSED_DIRS:
674                    return Option.ENDORSEDDIRS;
675                case DJAVA_EXT_DIRS:
676                    return Option.EXTDIRS;
677                default:
678                    return option;
679            }
680        }
681
682        @Override
683        Collection<Path> getPaths() {
684            lazy();
685            return searchPath;
686        }
687
688        @Override
689        void setPaths(Iterable<? extends Path> files) {
690            if (files == null) {
691                searchPath = null;  // reset to "uninitialized"
692            } else {
693                isDefault = false;
694                SearchPath p = new SearchPath().addFiles(files, false);
695                searchPath = Collections.unmodifiableCollection(p);
696                optionValues.clear();
697            }
698        }
699
700        SearchPath computePath() throws IOException {
701            SearchPath path = new SearchPath();
702
703            String bootclasspathOpt = optionValues.get(BOOTCLASSPATH);
704            String endorseddirsOpt = optionValues.get(ENDORSEDDIRS);
705            String extdirsOpt = optionValues.get(EXTDIRS);
706            String xbootclasspathPrependOpt = optionValues.get(XBOOTCLASSPATH_PREPEND);
707            String xbootclasspathAppendOpt = optionValues.get(XBOOTCLASSPATH_APPEND);
708            path.addFiles(xbootclasspathPrependOpt);
709
710            if (endorseddirsOpt != null) {
711                path.addDirectories(endorseddirsOpt);
712            } else {
713                path.addDirectories(System.getProperty("java.endorsed.dirs"), false);
714            }
715
716            if (bootclasspathOpt != null) {
717                path.addFiles(bootclasspathOpt);
718            } else {
719                // Standard system classes for this compiler's release.
720                Collection<Path> systemClasses = systemClasses();
721                if (systemClasses != null) {
722                    path.addFiles(systemClasses, false);
723                } else {
724                    // fallback to the value of sun.boot.class.path
725                    String files = System.getProperty("sun.boot.class.path");
726                    path.addFiles(files, false);
727                }
728            }
729
730            path.addFiles(xbootclasspathAppendOpt);
731
732            // Strictly speaking, standard extensions are not bootstrap
733            // classes, but we treat them identically, so we'll pretend
734            // that they are.
735            if (extdirsOpt != null) {
736                path.addDirectories(extdirsOpt);
737            } else {
738                // Add lib/jfxrt.jar to the search path
739               Path jfxrt = javaHome.resolve("lib/jfxrt.jar");
740                if (Files.exists(jfxrt)) {
741                    path.addFile(jfxrt, false);
742                }
743                path.addDirectories(System.getProperty("java.ext.dirs"), false);
744            }
745
746            isDefault =
747                       (xbootclasspathPrependOpt == null)
748                    && (bootclasspathOpt == null)
749                    && (xbootclasspathAppendOpt == null);
750
751            return path;
752        }
753
754        /**
755         * Return a collection of files containing system classes.
756         * Returns {@code null} if not running on a modular image.
757         *
758         * @throws UncheckedIOException if an I/O errors occurs
759         */
760        private Collection<Path> systemClasses() throws IOException {
761            // Return "modules" jimage file if available
762            if (Files.isRegularFile(thisSystemModules)) {
763                return addAdditionalBootEntries(Collections.singleton(thisSystemModules));
764            }
765
766            // Exploded module image
767            Path modules = javaHome.resolve("modules");
768            if (Files.isDirectory(modules.resolve("java.base"))) {
769                try (Stream<Path> listedModules = Files.list(modules)) {
770                    return addAdditionalBootEntries(listedModules.collect(Collectors.toList()));
771                }
772            }
773
774            // not a modular image that we know about
775            return null;
776        }
777
778        //ensure bootclasspath prepends/appends are reflected in the systemClasses
779        private Collection<Path> addAdditionalBootEntries(Collection<Path> modules) throws IOException {
780            String files = System.getProperty("sun.boot.class.path");
781            if (files == null)
782                return modules;
783
784            Set<Path> paths = new LinkedHashSet<>();
785
786            // The JVM no longer supports -Xbootclasspath/p:, so any interesting
787            // entries should be appended to the set of modules.
788
789            paths.addAll(modules);
790
791            for (String s : files.split(Pattern.quote(File.pathSeparator))) {
792                paths.add(Paths.get(s));
793            }
794
795            return paths;
796        }
797
798        private void lazy() {
799            if (searchPath == null) {
800                try {
801                searchPath = Collections.unmodifiableCollection(computePath());
802                } catch (IOException e) {
803                    // TODO: need better handling here, e.g. javac Abort?
804                    throw new UncheckedIOException(e);
805                }
806            }
807        }
808    }
809
810    /**
811     * A LocationHander to represent modules found from a module-oriented
812     * location such as MODULE_SOURCE_PATH, UPGRADE_MODULE_PATH,
813     * SYSTEM_MODULES and MODULE_PATH.
814     *
815     * The Location can be specified to accept overriding classes from the
816     * -Xpatch:dir parameter.
817     */
818    private class ModuleLocationHandler extends LocationHandler implements Location {
819        protected final String name;
820        protected final String moduleName;
821        protected final Collection<Path> searchPath;
822        protected final Collection<Path> searchPathWithOverrides;
823        protected final boolean output;
824
825        ModuleLocationHandler(String name, String moduleName, Collection<Path> searchPath,
826                boolean output, boolean allowOverrides) {
827            this.name = name;
828            this.moduleName = moduleName;
829            this.searchPath = searchPath;
830            this.output = output;
831
832            if (allowOverrides) {
833                if (patchMap != null) {
834                    SearchPath mPatch = patchMap.get(moduleName);
835                    if (mPatch != null) {
836                        SearchPath sp = new SearchPath();
837                        sp.addAll(mPatch);
838                        sp.addAll(searchPath);
839                        searchPathWithOverrides = sp;
840                    } else {
841                        searchPathWithOverrides = searchPath;
842                    }
843                } else {
844                     // for old style patch option; retained for transition
845                    Set<Path> overrides = new LinkedHashSet<>();
846                    if (moduleOverrideSearchPath != null) {
847                       for (Path p: moduleOverrideSearchPath) {
848                           Path o = p.resolve(moduleName);
849                           if (Files.isDirectory(o)) {
850                               overrides.add(o);
851                           }
852                       }
853                    }
854
855                    if (!overrides.isEmpty()) {
856                        overrides.addAll(searchPath);
857                        searchPathWithOverrides = overrides;
858                    } else {
859                        searchPathWithOverrides = searchPath;
860                    }
861                }
862            } else {
863                searchPathWithOverrides = searchPath;
864            }
865        }
866
867        @Override // defined by Location
868        public String getName() {
869            return name;
870        }
871
872        @Override // defined by Location
873        public boolean isOutputLocation() {
874            return output;
875        }
876
877        @Override // defined by LocationHandler
878        boolean handleOption(Option option, String value) {
879            throw new UnsupportedOperationException();
880        }
881
882        @Override // defined by LocationHandler
883        Collection<Path> getPaths() {
884            // For now, we always return searchPathWithOverrides. This may differ from the
885            // JVM behavior if there is a module-info.class to be found in the overriding
886            // classes.
887            return searchPathWithOverrides;
888        }
889
890        @Override // defined by LocationHandler
891        void setPaths(Iterable<? extends Path> files) throws IOException {
892            throw new UnsupportedOperationException();
893        }
894
895        @Override // defined by LocationHandler
896        String inferModuleName() {
897            return moduleName;
898        }
899    }
900
901    /**
902     * A LocationHandler for simple module-oriented search paths,
903     * like UPGRADE_MODULE_PATH and MODULE_PATH.
904     */
905    private class ModulePathLocationHandler extends SimpleLocationHandler {
906        ModulePathLocationHandler(Location location, Option... options) {
907            super(location, options);
908        }
909
910        @Override
911        public boolean handleOption(Option option, String value) {
912            if (!options.contains(option)) {
913                return false;
914            }
915            setPaths(value == null ? null : getPathEntries(value));
916            return true;
917        }
918
919        @Override
920        Iterable<Set<Location>> listModuleLocations() {
921            if (searchPath == null)
922                return Collections.emptyList();
923
924            return () -> new ModulePathIterator();
925        }
926
927        @Override
928        void setPaths(Iterable<? extends Path> paths) {
929            if (paths != null) {
930                for (Path p: paths) {
931                    checkValidModulePathEntry(p);
932                }
933            }
934            super.setPaths(paths);
935        }
936
937        private void checkValidModulePathEntry(Path p) {
938            if (Files.isDirectory(p)) {
939                // either an exploded module or a directory of modules
940                return;
941            }
942
943            String name = p.getFileName().toString();
944            int lastDot = name.lastIndexOf(".");
945            if (lastDot > 0) {
946                switch (name.substring(lastDot)) {
947                    case ".jar":
948                    case ".jmod":
949                        return;
950                }
951            }
952            throw new IllegalArgumentException(p.toString());
953        }
954
955        class ModulePathIterator implements Iterator<Set<Location>> {
956            Iterator<Path> pathIter = searchPath.iterator();
957            int pathIndex = 0;
958            Set<Location> next = null;
959
960            @Override
961            public boolean hasNext() {
962                if (next != null)
963                    return true;
964
965                while (next == null) {
966                    if (pathIter.hasNext()) {
967                        Path path = pathIter.next();
968                        if (Files.isDirectory(path)) {
969                            next = scanDirectory(path);
970                        } else {
971                            next = scanFile(path);
972                        }
973                        pathIndex++;
974                    } else
975                        return false;
976                }
977                return true;
978            }
979
980            @Override
981            public Set<Location> next() {
982                hasNext();
983                if (next != null) {
984                    Set<Location> result = next;
985                    next = null;
986                    return result;
987                }
988                throw new NoSuchElementException();
989            }
990
991            private Set<Location> scanDirectory(Path path) {
992                Set<Path> paths = new LinkedHashSet<>();
993                Path moduleInfoClass = null;
994                try (DirectoryStream<Path> stream = Files.newDirectoryStream(path)) {
995                    for (Path entry: stream) {
996                        if (entry.endsWith("module-info.class")) {
997                            moduleInfoClass = entry;
998                            break;  // no need to continue scanning
999                        }
1000                        paths.add(entry);
1001                    }
1002                } catch (DirectoryIteratorException | IOException ignore) {
1003                    log.error(Errors.LocnCantReadDirectory(path));
1004                    return Collections.emptySet();
1005                }
1006
1007                if (moduleInfoClass != null) {
1008                    // It's an exploded module directly on the module path.
1009                    // We can't infer module name from the directory name, so have to
1010                    // read module-info.class.
1011                    try {
1012                        String moduleName = readModuleName(moduleInfoClass);
1013                        String name = location.getName()
1014                                + "[" + pathIndex + ":" + moduleName + "]";
1015                        ModuleLocationHandler l = new ModuleLocationHandler(name, moduleName,
1016                                Collections.singleton(path), false, true);
1017                        return Collections.singleton(l);
1018                    } catch (ModuleNameReader.BadClassFile e) {
1019                        log.error(Errors.LocnBadModuleInfo(path));
1020                        return Collections.emptySet();
1021                    } catch (IOException e) {
1022                        log.error(Errors.LocnCantReadFile(path));
1023                        return Collections.emptySet();
1024                    }
1025                }
1026
1027                // A directory of modules
1028                Set<Location> result = new LinkedHashSet<>();
1029                int index = 0;
1030                for (Path entry : paths) {
1031                    Pair<String,Path> module = inferModuleName(entry);
1032                    if (module == null) {
1033                        // diagnostic reported if necessary; skip to next
1034                        continue;
1035                    }
1036                    String moduleName = module.fst;
1037                    Path modulePath = module.snd;
1038                    String name = location.getName()
1039                            + "[" + pathIndex + "." + (index++) + ":" + moduleName + "]";
1040                    ModuleLocationHandler l = new ModuleLocationHandler(name, moduleName,
1041                            Collections.singleton(modulePath), false, true);
1042                    result.add(l);
1043                }
1044                return result;
1045            }
1046
1047            private Set<Location> scanFile(Path path) {
1048                Pair<String,Path> module = inferModuleName(path);
1049                if (module == null) {
1050                    // diagnostic reported if necessary
1051                    return Collections.emptySet();
1052                }
1053                String moduleName = module.fst;
1054                Path modulePath = module.snd;
1055                String name = location.getName()
1056                        + "[" + pathIndex + ":" + moduleName + "]";
1057                ModuleLocationHandler l = new ModuleLocationHandler(name, moduleName,
1058                        Collections.singleton(modulePath), false, true);
1059                return Collections.singleton(l);
1060            }
1061
1062            private Pair<String,Path> inferModuleName(Path p) {
1063                if (Files.isDirectory(p)) {
1064                    if (Files.exists(p.resolve("module-info.class"))) {
1065                        String name = p.getFileName().toString();
1066                        if (SourceVersion.isName(name))
1067                            return new Pair<>(name, p);
1068                    }
1069                    return null;
1070                }
1071
1072                if (p.getFileName().toString().endsWith(".jar")) {
1073                    try (FileSystem fs = FileSystems.newFileSystem(p, null)) {
1074                        Path moduleInfoClass = fs.getPath("module-info.class");
1075                        if (Files.exists(moduleInfoClass)) {
1076                            String moduleName = readModuleName(moduleInfoClass);
1077                            return new Pair<>(moduleName, p);
1078                        }
1079                    } catch (ModuleNameReader.BadClassFile e) {
1080                        log.error(Errors.LocnBadModuleInfo(p));
1081                        return null;
1082                    } catch (IOException e) {
1083                        log.error(Errors.LocnCantReadFile(p));
1084                        return null;
1085                    }
1086
1087                    //automatic module:
1088                    String fn = p.getFileName().toString();
1089                    //from ModulePath.deriveModuleDescriptor:
1090
1091                    // drop .jar
1092                    String mn = fn.substring(0, fn.length()-4);
1093
1094                    // find first occurrence of -${NUMBER}. or -${NUMBER}$
1095                    Matcher matcher = Pattern.compile("-(\\d+(\\.|$))").matcher(mn);
1096                    if (matcher.find()) {
1097                        int start = matcher.start();
1098
1099                        mn = mn.substring(0, start);
1100                    }
1101
1102                    // finally clean up the module name
1103                    mn =  mn.replaceAll("[^A-Za-z0-9]", ".")  // replace non-alphanumeric
1104                            .replaceAll("(\\.)(\\1)+", ".")   // collapse repeating dots
1105                            .replaceAll("^\\.", "")           // drop leading dots
1106                            .replaceAll("\\.$", "");          // drop trailing dots
1107
1108
1109                    if (!mn.isEmpty()) {
1110                        return new Pair<>(mn, p);
1111                    }
1112
1113                    log.error(Errors.LocnCantGetModuleNameForJar(p));
1114                    return null;
1115                }
1116
1117                if (p.getFileName().toString().endsWith(".jmod")) {
1118                    try {
1119                        FileSystem fs = fileSystems.get(p);
1120                        if (fs == null) {
1121                            URI uri = URI.create("jar:" + p.toUri());
1122                            fs = FileSystems.newFileSystem(uri, Collections.emptyMap(), null);
1123                            try {
1124                                Path moduleInfoClass = fs.getPath("classes/module-info.class");
1125                                String moduleName = readModuleName(moduleInfoClass);
1126                                Path modulePath = fs.getPath("classes");
1127                                fileSystems.put(p, fs);
1128                                closeables.add(fs);
1129                                fs = null; // prevent fs being closed in the finally clause
1130                                return new Pair<>(moduleName, modulePath);
1131                            } finally {
1132                                if (fs != null)
1133                                    fs.close();
1134                            }
1135                        }
1136                    } catch (ProviderNotFoundException e) {
1137                        // will be thrown if the file is not a valid zip file
1138                        log.error(Errors.LocnCantReadFile(p));
1139                        return null;
1140                    } catch (ModuleNameReader.BadClassFile e) {
1141                        log.error(Errors.LocnBadModuleInfo(p));
1142                    } catch (IOException e) {
1143                        log.error(Errors.LocnCantReadFile(p));
1144                        return null;
1145                    }
1146                }
1147
1148                if (warn && false) {  // temp disable
1149                    log.warning(Warnings.LocnUnknownFileOnModulePath(p));
1150                }
1151                return null;
1152            }
1153
1154            private String readModuleName(Path path) throws IOException, ModuleNameReader.BadClassFile {
1155                if (moduleNameReader == null)
1156                    moduleNameReader = new ModuleNameReader();
1157                return moduleNameReader.readModuleName(path);
1158            }
1159        }
1160
1161    }
1162
1163    private class ModuleSourcePathLocationHandler extends BasicLocationHandler {
1164
1165        private Map<String, Location> moduleLocations;
1166        private Map<Path, Location> pathLocations;
1167
1168
1169        ModuleSourcePathLocationHandler() {
1170            super(StandardLocation.MODULE_SOURCE_PATH,
1171                    Option.MODULESOURCEPATH);
1172        }
1173
1174        @Override
1175        boolean handleOption(Option option, String value) {
1176            init(value);
1177            return true;
1178        }
1179
1180        void init(String value) {
1181            Collection<String> segments = new ArrayList<>();
1182            for (String s: value.split(File.pathSeparator)) {
1183                expandBraces(s, segments);
1184            }
1185
1186            Map<String, Collection<Path>> map = new LinkedHashMap<>();
1187            final String MARKER = "*";
1188            for (String seg: segments) {
1189                int markStart = seg.indexOf(MARKER);
1190                if (markStart == -1) {
1191                    add(map, Paths.get(seg), null);
1192                } else {
1193                    if (markStart == 0 || !isSeparator(seg.charAt(markStart - 1))) {
1194                        throw new IllegalArgumentException("illegal use of " + MARKER + " in " + seg);
1195                    }
1196                    Path prefix = Paths.get(seg.substring(0, markStart - 1));
1197                    Path suffix;
1198                    int markEnd = markStart + MARKER.length();
1199                    if (markEnd == seg.length()) {
1200                        suffix = null;
1201                    } else if (!isSeparator(seg.charAt(markEnd))
1202                            || seg.indexOf(MARKER, markEnd) != -1) {
1203                        throw new IllegalArgumentException("illegal use of " + MARKER + " in " + seg);
1204                    } else {
1205                        suffix = Paths.get(seg.substring(markEnd + 1));
1206                    }
1207                    add(map, prefix, suffix);
1208                }
1209            }
1210
1211            moduleLocations = new LinkedHashMap<>();
1212            pathLocations = new LinkedHashMap<>();
1213            map.forEach((k, v) -> {
1214                String name = location.getName() + "[" + k + "]";
1215                ModuleLocationHandler h = new ModuleLocationHandler(name, k, v, false, false);
1216                moduleLocations.put(k, h);
1217                v.forEach(p -> pathLocations.put(normalize(p), h));
1218            });
1219        }
1220
1221        private boolean isSeparator(char ch) {
1222            // allow both separators on Windows
1223            return (ch == File.separatorChar) || (ch == '/');
1224        }
1225
1226        void add(Map<String, Collection<Path>> map, Path prefix, Path suffix) {
1227            if (!Files.isDirectory(prefix)) {
1228                if (warn) {
1229                    String key = Files.exists(prefix)
1230                            ? "dir.path.element.not.directory"
1231                            : "dir.path.element.not.found";
1232                    log.warning(Lint.LintCategory.PATH, key, prefix);
1233                }
1234                return;
1235            }
1236            try (DirectoryStream<Path> stream = Files.newDirectoryStream(prefix, path -> Files.isDirectory(path))) {
1237                for (Path entry: stream) {
1238                    Path path = (suffix == null) ? entry : entry.resolve(suffix);
1239                    if (Files.isDirectory(path)) {
1240                        String name = entry.getFileName().toString();
1241                        Collection<Path> paths = map.get(name);
1242                        if (paths == null)
1243                            map.put(name, paths = new ArrayList<>());
1244                        paths.add(path);
1245                    }
1246                }
1247            } catch (IOException e) {
1248                // TODO? What to do?
1249                System.err.println(e);
1250            }
1251        }
1252
1253        private void expandBraces(String value, Collection<String> results) {
1254            int depth = 0;
1255            int start = -1;
1256            String prefix = null;
1257            String suffix = null;
1258            for (int i = 0; i < value.length(); i++) {
1259                switch (value.charAt(i)) {
1260                    case '{':
1261                        depth++;
1262                        if (depth == 1) {
1263                            prefix = value.substring(0, i);
1264                            suffix = value.substring(getMatchingBrace(value, i) + 1);
1265                            start = i + 1;
1266                        }
1267                        break;
1268
1269                    case ',':
1270                        if (depth == 1) {
1271                            String elem = value.substring(start, i);
1272                            expandBraces(prefix + elem + suffix, results);
1273                            start = i + 1;
1274                        }
1275                        break;
1276
1277                    case '}':
1278                        switch (depth) {
1279                            case 0:
1280                                throw new IllegalArgumentException("mismatched braces");
1281
1282                            case 1:
1283                                String elem = value.substring(start, i);
1284                                expandBraces(prefix + elem + suffix, results);
1285                                return;
1286
1287                            default:
1288                                depth--;
1289                        }
1290                        break;
1291                }
1292            }
1293            if (depth > 0)
1294                throw new IllegalArgumentException("mismatched braces");
1295            results.add(value);
1296        }
1297
1298        int getMatchingBrace(String value, int offset) {
1299            int depth = 1;
1300            for (int i = offset + 1; i < value.length(); i++) {
1301                switch (value.charAt(i)) {
1302                    case '{':
1303                        depth++;
1304                        break;
1305
1306                    case '}':
1307                        if (--depth == 0)
1308                            return i;
1309                        break;
1310                }
1311            }
1312            throw new IllegalArgumentException("mismatched braces");
1313        }
1314
1315        @Override
1316        boolean isSet() {
1317            return (moduleLocations != null);
1318        }
1319
1320        @Override
1321        Collection<Path> getPaths() {
1322            throw new UnsupportedOperationException();
1323        }
1324
1325        @Override
1326        void setPaths(Iterable<? extends Path> files) throws IOException {
1327            throw new UnsupportedOperationException();
1328        }
1329
1330        @Override
1331        Location getModuleLocation(String name) {
1332            return (moduleLocations == null) ? null : moduleLocations.get(name);
1333        }
1334
1335        @Override
1336        Location getModuleLocation(Path dir) {
1337            return (pathLocations == null) ? null : pathLocations.get(dir);
1338        }
1339
1340        @Override
1341        Iterable<Set<Location>> listModuleLocations() {
1342            if (moduleLocations == null)
1343                return Collections.emptySet();
1344            Set<Location> locns = new LinkedHashSet<>();
1345            moduleLocations.forEach((k, v) -> locns.add(v));
1346            return Collections.singleton(locns);
1347        }
1348
1349    }
1350
1351    private class SystemModulesLocationHandler extends BasicLocationHandler {
1352        private Path javaHome;
1353        private Path modules;
1354        private Map<String, ModuleLocationHandler> systemModules;
1355
1356        SystemModulesLocationHandler() {
1357            super(StandardLocation.SYSTEM_MODULES, Option.SYSTEM);
1358            javaHome = Paths.get(System.getProperty("java.home"));
1359        }
1360
1361        @Override
1362        boolean handleOption(Option option, String value) {
1363            if (!options.contains(option)) {
1364                return false;
1365            }
1366
1367            if (value == null) {
1368                javaHome = Paths.get(System.getProperty("java.home"));
1369            } else if (value.equals("none")) {
1370                javaHome = null;
1371            } else {
1372                update(Paths.get(value));
1373            }
1374
1375            modules = null;
1376            return true;
1377        }
1378
1379        @Override
1380        Collection<Path> getPaths() {
1381            return (javaHome == null) ? null : Collections.singleton(javaHome);
1382        }
1383
1384        @Override
1385        void setPaths(Iterable<? extends Path> files) throws IOException {
1386            if (files == null) {
1387                javaHome = null;
1388            } else {
1389                Iterator<? extends Path> pathIter = files.iterator();
1390                if (!pathIter.hasNext()) {
1391                    throw new IllegalArgumentException("empty path for directory"); // TODO: FIXME
1392                }
1393                Path dir = pathIter.next();
1394                if (pathIter.hasNext()) {
1395                    throw new IllegalArgumentException("path too long for directory"); // TODO: FIXME
1396                }
1397                if (!Files.exists(dir)) {
1398                    throw new FileNotFoundException(dir + ": does not exist");
1399                } else if (!Files.isDirectory(dir)) {
1400                    throw new IOException(dir + ": not a directory");
1401                }
1402                update(dir);
1403            }
1404        }
1405
1406        private void update(Path p) {
1407            if (!isCurrentPlatform(p) && !Files.exists(p.resolve("jrt-fs.jar")) && !Files.exists(javaHome.resolve("modules")))
1408                throw new IllegalArgumentException(p.toString());
1409            javaHome = p;
1410            modules = null;
1411        }
1412
1413        private boolean isCurrentPlatform(Path p) {
1414            Path jh = Paths.get(System.getProperty("java.home"));
1415            try {
1416                return Files.isSameFile(p, jh);
1417            } catch (IOException ex) {
1418                throw new IllegalArgumentException(p.toString(), ex);
1419            }
1420        }
1421
1422        @Override
1423        Location getModuleLocation(String name) throws IOException {
1424            initSystemModules();
1425            return systemModules.get(name);
1426        }
1427
1428        @Override
1429        Iterable<Set<Location>> listModuleLocations() throws IOException {
1430            initSystemModules();
1431            Set<Location> locns = new LinkedHashSet<>();
1432            for (Location l: systemModules.values())
1433                locns.add(l);
1434            return Collections.singleton(locns);
1435        }
1436
1437        private void initSystemModules() throws IOException {
1438            if (systemModules != null) {
1439                return;
1440            }
1441
1442            if (javaHome == null) {
1443                systemModules = Collections.emptyMap();
1444                return;
1445            }
1446
1447            if (modules == null) {
1448                try {
1449                    URI jrtURI = URI.create("jrt:/");
1450                    FileSystem jrtfs;
1451
1452                    if (isCurrentPlatform(javaHome)) {
1453                        jrtfs = FileSystems.getFileSystem(jrtURI);
1454                    } else {
1455                        try {
1456                            Map<String, String> attrMap =
1457                                    Collections.singletonMap("java.home", javaHome.toString());
1458                            jrtfs = FileSystems.newFileSystem(jrtURI, attrMap);
1459                        } catch (ProviderNotFoundException ex) {
1460                            URL javaHomeURL = javaHome.resolve("jrt-fs.jar").toUri().toURL();
1461                            ClassLoader currentLoader = Locations.class.getClassLoader();
1462                            URLClassLoader fsLoader =
1463                                    new URLClassLoader(new URL[] {javaHomeURL}, currentLoader);
1464
1465                            jrtfs = FileSystems.newFileSystem(jrtURI, Collections.emptyMap(), fsLoader);
1466
1467                            closeables.add(fsLoader);
1468                        }
1469
1470                        closeables.add(jrtfs);
1471                    }
1472
1473                    modules = jrtfs.getPath("/modules");
1474                } catch (FileSystemNotFoundException | ProviderNotFoundException e) {
1475                    modules = javaHome.resolve("modules");
1476                    if (!Files.exists(modules))
1477                        throw new IOException("can't find system classes", e);
1478                }
1479            }
1480
1481            systemModules = new LinkedHashMap<>();
1482            try (DirectoryStream<Path> stream = Files.newDirectoryStream(modules, Files::isDirectory)) {
1483                for (Path entry : stream) {
1484                    String moduleName = entry.getFileName().toString();
1485                    String name = location.getName() + "[" + moduleName + "]";
1486                    ModuleLocationHandler h = new ModuleLocationHandler(name, moduleName,
1487                            Collections.singleton(entry), false, true);
1488                    systemModules.put(moduleName, h);
1489                }
1490            }
1491        }
1492    }
1493
1494    Map<Location, LocationHandler> handlersForLocation;
1495    Map<Option, LocationHandler> handlersForOption;
1496
1497    void initHandlers() {
1498        handlersForLocation = new HashMap<>();
1499        handlersForOption = new EnumMap<>(Option.class);
1500
1501        BasicLocationHandler[] handlers = {
1502            new BootClassPathLocationHandler(),
1503            new ClassPathLocationHandler(),
1504            new SimpleLocationHandler(StandardLocation.SOURCE_PATH, Option.SOURCEPATH),
1505            new SimpleLocationHandler(StandardLocation.ANNOTATION_PROCESSOR_PATH, Option.PROCESSORPATH),
1506            new SimpleLocationHandler(StandardLocation.ANNOTATION_PROCESSOR_MODULE_PATH, Option.PROCESSORMODULEPATH),
1507            new OutputLocationHandler(StandardLocation.CLASS_OUTPUT, Option.D),
1508            new OutputLocationHandler(StandardLocation.SOURCE_OUTPUT, Option.S),
1509            new OutputLocationHandler(StandardLocation.NATIVE_HEADER_OUTPUT, Option.H),
1510            new ModuleSourcePathLocationHandler(),
1511            // TODO: should UPGRADE_MODULE_PATH be merged with SYSTEM_MODULES?
1512            new ModulePathLocationHandler(StandardLocation.UPGRADE_MODULE_PATH, Option.UPGRADEMODULEPATH),
1513            new ModulePathLocationHandler(StandardLocation.MODULE_PATH, Option.MODULEPATH, Option.MP),
1514            new SystemModulesLocationHandler(),
1515        };
1516
1517        for (BasicLocationHandler h : handlers) {
1518            handlersForLocation.put(h.location, h);
1519            for (Option o : h.options) {
1520                handlersForOption.put(o, h);
1521            }
1522        }
1523    }
1524
1525    private SearchPath moduleOverrideSearchPath; // for old style patch option; retained for transition
1526    private Map<String, SearchPath> patchMap;
1527
1528    boolean handleOption(Option option, String value) {
1529        switch (option) {
1530            case XPATCH:
1531                if (value.contains("=")) {
1532                    Map<String, SearchPath> map = new LinkedHashMap<>();
1533                    for (String entry: value.split(",")) {
1534                        int eq = entry.indexOf('=');
1535                        if (eq > 0) {
1536                            String mName = entry.substring(0, eq);
1537                            SearchPath mPatchPath = new SearchPath()
1538                                    .addFiles(entry.substring(eq + 1));
1539                            boolean ok = true;
1540                            for (Path p: mPatchPath) {
1541                                Path mi = p.resolve("module-info.class");
1542                                if (Files.exists(mi)) {
1543                                    log.error(Errors.LocnModuleInfoNotAllowedOnPatchPath(mi));
1544                                    ok = false;
1545                                }
1546                            }
1547                            if (ok && !mPatchPath.isEmpty()) {
1548                                map.computeIfAbsent(mName, (_x) -> new SearchPath())
1549                                        .addAll(mPatchPath);
1550                            }
1551                        } else {
1552                            log.error(Errors.LocnInvalidArgForXpatch(entry));
1553                        }
1554                    }
1555                    patchMap = map;
1556                } else {
1557                     // for old style patch option; retained for transition
1558                    moduleOverrideSearchPath = new SearchPath().addFiles(value);
1559                }
1560                return true;
1561            default:
1562                LocationHandler h = handlersForOption.get(option);
1563                return (h == null ? false : h.handleOption(option, value));
1564        }
1565    }
1566
1567    boolean hasLocation(Location location) {
1568        LocationHandler h = getHandler(location);
1569        return (h == null ? false : h.isSet());
1570    }
1571
1572    Collection<Path> getLocation(Location location) {
1573        LocationHandler h = getHandler(location);
1574        return (h == null ? null : h.getPaths());
1575    }
1576
1577    Path getOutputLocation(Location location) {
1578        if (!location.isOutputLocation()) {
1579            throw new IllegalArgumentException();
1580        }
1581        LocationHandler h = getHandler(location);
1582        return ((OutputLocationHandler) h).outputDir;
1583    }
1584
1585    void setLocation(Location location, Iterable<? extends Path> files) throws IOException {
1586        LocationHandler h = getHandler(location);
1587        if (h == null) {
1588            if (location.isOutputLocation()) {
1589                h = new OutputLocationHandler(location);
1590            } else {
1591                h = new SimpleLocationHandler(location);
1592            }
1593            handlersForLocation.put(location, h);
1594        }
1595        h.setPaths(files);
1596    }
1597
1598    Location getModuleLocation(Location location, String name) throws IOException {
1599        LocationHandler h = getHandler(location);
1600        return (h == null ? null : h.getModuleLocation(name));
1601    }
1602
1603    Location getModuleLocation(Location location, Path dir) {
1604        LocationHandler h = getHandler(location);
1605        return (h == null ? null : h.getModuleLocation(dir));
1606    }
1607
1608    String inferModuleName(Location location) {
1609        LocationHandler h = getHandler(location);
1610        return (h == null ? null : h.inferModuleName());
1611    }
1612
1613    Iterable<Set<Location>> listModuleLocations(Location location) throws IOException {
1614        LocationHandler h = getHandler(location);
1615        return (h == null ? null : h.listModuleLocations());
1616    }
1617
1618    protected LocationHandler getHandler(Location location) {
1619        Objects.requireNonNull(location);
1620        return (location instanceof LocationHandler)
1621                ? (LocationHandler) location
1622                : handlersForLocation.get(location);
1623    }
1624
1625    /**
1626     * Is this the name of an archive file?
1627     */
1628    private boolean isArchive(Path file) {
1629        String n = StringUtils.toLowerCase(file.getFileName().toString());
1630        return fsInfo.isFile(file)
1631                && (n.endsWith(".jar") || n.endsWith(".zip"));
1632    }
1633
1634    static Path normalize(Path p) {
1635        try {
1636            return p.toRealPath();
1637        } catch (IOException e) {
1638            return p.toAbsolutePath().normalize();
1639        }
1640    }
1641
1642}
1643