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