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