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