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