1 /*
2 * Copyright (c) 2005, 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.File;
29 import java.io.IOException;
30 import java.io.UncheckedIOException;
31 import java.lang.module.Configuration;
32 import java.lang.module.ModuleFinder;
33 import java.net.MalformedURLException;
34 import java.net.URI;
35 import java.net.URISyntaxException;
36 import java.net.URL;
37 import java.nio.CharBuffer;
38 import java.nio.charset.Charset;
39 import java.nio.file.FileSystem;
40 import java.nio.file.FileSystems;
41 import java.nio.file.FileVisitOption;
42 import java.nio.file.FileVisitResult;
43 import java.nio.file.Files;
44 import java.nio.file.InvalidPathException;
45 import java.nio.file.LinkOption;
46 import java.nio.file.Path;
47 import java.nio.file.Paths;
48 import java.nio.file.ProviderNotFoundException;
49 import java.nio.file.SimpleFileVisitor;
50 import java.nio.file.attribute.BasicFileAttributes;
51 import java.nio.file.spi.FileSystemProvider;
52 import java.util.ArrayList;
53 import java.util.Arrays;
54 import java.util.Collection;
55 import java.util.Collections;
56 import java.util.Comparator;
57 import java.util.HashMap;
58 import java.util.Iterator;
59 import java.util.Map;
60 import java.util.Objects;
61 import java.util.ServiceLoader;
62 import java.util.Set;
63 import java.util.stream.Stream;
64 import java.util.zip.ZipException;
65
66 import javax.lang.model.SourceVersion;
67 import javax.tools.FileObject;
68 import javax.tools.JavaFileManager;
69 import javax.tools.JavaFileObject;
70 import javax.tools.StandardJavaFileManager;
71
72 import com.sun.tools.javac.file.RelativePath.RelativeDirectory;
73 import com.sun.tools.javac.file.RelativePath.RelativeFile;
74 import com.sun.tools.javac.main.Option;
75 import com.sun.tools.javac.resources.CompilerProperties.Errors;
76 import com.sun.tools.javac.util.Assert;
77 import com.sun.tools.javac.util.Context;
78 import com.sun.tools.javac.util.Context.Factory;
79 import com.sun.tools.javac.util.DefinedBy;
80 import com.sun.tools.javac.util.DefinedBy.Api;
81 import com.sun.tools.javac.util.List;
82 import com.sun.tools.javac.util.ListBuffer;
83 import com.sun.tools.javac.util.Options;
84
85 import static java.nio.charset.StandardCharsets.US_ASCII;
86 import static java.nio.file.FileVisitOption.FOLLOW_LINKS;
87
88 import static javax.tools.StandardLocation.*;
89
90 /**
91 * This class provides access to the source, class and other files
92 * used by the compiler and related tools.
93 *
94 * <p><b>This is NOT part of any supported API.
95 * If you write code that depends on this, you do so at your own risk.
96 * This code and its internal interfaces are subject to change or
97 * deletion without notice.</b>
98 */
99 public class JavacFileManager extends BaseFileManager implements StandardJavaFileManager {
100
101 public static char[] toArray(CharBuffer buffer) {
102 if (buffer.hasArray())
103 return buffer.compact().flip().array();
104 else
105 return buffer.toString().toCharArray();
106 }
107
108 private FSInfo fsInfo;
109
110 private static final Set<JavaFileObject.Kind> SOURCE_OR_CLASS =
111 Set.of(JavaFileObject.Kind.SOURCE, JavaFileObject.Kind.CLASS);
112
113 protected boolean symbolFileEnabled = true;
114
115 private PathFactory pathFactory = Paths::get;
116
117 protected enum SortFiles implements Comparator<Path> {
118 FORWARD {
119 @Override
120 public int compare(Path f1, Path f2) {
121 return f1.getFileName().compareTo(f2.getFileName());
122 }
123 },
124 REVERSE {
125 @Override
126 public int compare(Path f1, Path f2) {
127 return f2.getFileName().compareTo(f1.getFileName());
128 }
129 }
130 }
131
132 protected SortFiles sortFiles;
133
134 /**
135 * We use a two-layered map instead of a map with a complex key because we don't want to reindex
136 * the values for every Location+RelativeDirectory pair. Once the PathsAndContainers are needed
137 * for a single Location, we should know all valid RelativeDirectory mappings. Because the
138 * indexing is costly for very large classpaths, this can result in a significant savings.
139 */
140 private Map<Location, Map<RelativeDirectory, java.util.List<PathAndContainer>>>
141 pathsAndContainersByLocationAndRelativeDirectory = new HashMap<>();
142
143 /** Containers that have no indexing by {@link RelativeDirectory}, keyed by {@link Location}. */
144 private Map<Location, java.util.List<PathAndContainer>> nonIndexingContainersByLocation =
145 new HashMap<>();
146
147 /**
148 * Register a Context.Factory to create a JavacFileManager.
149 */
150 public static void preRegister(Context context) {
151 context.put(JavaFileManager.class,
152 (Factory<JavaFileManager>)c -> new JavacFileManager(c, true, null));
153 }
154
155 /**
156 * Create a JavacFileManager using a given context, optionally registering
157 * it as the JavaFileManager for that context.
158 */
159 @SuppressWarnings("this-escape")
160 public JavacFileManager(Context context, boolean register, Charset charset) {
161 super(charset);
162 if (register)
163 context.put(JavaFileManager.class, this);
164 setContext(context);
165 }
166
167 /**
168 * Set the context for JavacFileManager.
169 */
170 @Override
171 public void setContext(Context context) {
172 super.setContext(context);
173 fsInfo = FSInfo.instance(context);
174 }
175
176 @Override
177 protected void applyOptions(Options options) {
178 super.applyOptions(options);
179
180 symbolFileEnabled = !options.isSet("ignore.symbol.file");
181
182 String sf = options.get("sortFiles");
183 if (sf != null) {
184 sortFiles = (sf.equals("reverse") ? SortFiles.REVERSE : SortFiles.FORWARD);
185 }
186 }
187
188 @Override @DefinedBy(DefinedBy.Api.COMPILER)
189 public void setPathFactory(PathFactory f) {
190 pathFactory = Objects.requireNonNull(f);
191 locations.setPathFactory(f);
192 }
193
194 private Path getPath(String first, String... more) {
195 return pathFactory.getPath(first, more);
196 }
197
198 /**
199 * Set whether or not to use ct.sym as an alternate to the current runtime.
200 */
201 public void setSymbolFileEnabled(boolean b) {
202 symbolFileEnabled = b;
203 }
204
205 public boolean isSymbolFileEnabled() {
206 return symbolFileEnabled;
207 }
208
209 // used by tests
210 public JavaFileObject getJavaFileObject(String name) {
211 return getJavaFileObjects(name).iterator().next();
212 }
213
214 // used by tests
215 public JavaFileObject getJavaFileObject(Path file) {
216 return getJavaFileObjects(file).iterator().next();
217 }
218
219 public JavaFileObject getFileForOutput(String classname,
220 JavaFileObject.Kind kind,
221 JavaFileObject sibling)
222 throws IOException
223 {
224 return getJavaFileForOutput(CLASS_OUTPUT, classname, kind, sibling);
225 }
226
227 @Override @DefinedBy(Api.COMPILER)
228 public Iterable<? extends JavaFileObject> getJavaFileObjectsFromStrings(Iterable<String> names) {
229 ListBuffer<Path> paths = new ListBuffer<>();
230 for (String name : names)
231 paths.append(getPath(nullCheck(name)));
232 return getJavaFileObjectsFromPaths(paths.toList());
233 }
234
235 @Override @DefinedBy(Api.COMPILER)
236 public Iterable<? extends JavaFileObject> getJavaFileObjects(String... names) {
237 return getJavaFileObjectsFromStrings(Arrays.asList(nullCheck(names)));
238 }
239
240 private static boolean isValidName(String name) {
241 // Arguably, isValidName should reject keywords (such as in SourceVersion.isName() ),
242 // but the set of keywords depends on the source level, and we don't want
243 // impls of JavaFileManager to have to be dependent on the source level.
244 // Therefore we simply check that the argument is a sequence of identifiers
245 // separated by ".".
246 for (String s : name.split("\\.", -1)) {
247 if (!SourceVersion.isIdentifier(s))
248 return false;
249 }
250 return true;
251 }
252
253 private static void validateClassName(String className) {
254 if (!isValidName(className))
255 throw new IllegalArgumentException("Invalid class name: " + className);
256 }
257
258 private static void validatePackageName(String packageName) {
259 if (packageName.length() > 0 && !isValidName(packageName))
260 throw new IllegalArgumentException("Invalid packageName name: " + packageName);
261 }
262
263 public static void testName(String name,
264 boolean isValidPackageName,
265 boolean isValidClassName)
266 {
267 try {
268 validatePackageName(name);
269 if (!isValidPackageName)
270 throw new AssertionError("Invalid package name accepted: " + name);
271 printAscii("Valid package name: \"%s\"", name);
272 } catch (IllegalArgumentException e) {
273 if (isValidPackageName)
274 throw new AssertionError("Valid package name rejected: " + name);
275 printAscii("Invalid package name: \"%s\"", name);
276 }
277 try {
278 validateClassName(name);
279 if (!isValidClassName)
280 throw new AssertionError("Invalid class name accepted: " + name);
281 printAscii("Valid class name: \"%s\"", name);
282 } catch (IllegalArgumentException e) {
283 if (isValidClassName)
284 throw new AssertionError("Valid class name rejected: " + name);
285 printAscii("Invalid class name: \"%s\"", name);
286 }
287 }
288
289 private static void printAscii(String format, Object... args) {
290 String message = new String(
291 String.format(null, format, args).getBytes(US_ASCII), US_ASCII);
292 System.out.println(message);
293 }
294
295 private final Map<Path, Container> containers = new HashMap<>();
296
297 synchronized Container getContainer(Path path) throws IOException {
298 Container fs = containers.get(path);
299
300 if (fs != null) {
301 return fs;
302 }
303
304 if (fsInfo.isFile(path) && path.equals(Locations.thisSystemModules)) {
305 containers.put(path, fs = new JRTImageContainer());
306 return fs;
307 }
308
309 Path realPath = fsInfo.getCanonicalFile(path);
310
311 fs = containers.get(realPath);
312
313 if (fs != null) {
314 containers.put(path, fs);
315 return fs;
316 }
317
318 BasicFileAttributes attr = null;
319
320 try {
321 attr = Files.readAttributes(realPath, BasicFileAttributes.class);
322 } catch (IOException ex) {
323 //non-existing
324 fs = MISSING_CONTAINER;
325 }
326
327 if (attr != null) {
328 if (attr.isDirectory()) {
329 fs = new DirectoryContainer(realPath);
330 } else {
331 try {
332 fs = new ArchiveContainer(path);
333 } catch (ProviderNotFoundException ex) {
334 throw new IOException(ex);
335 }
336 }
337 }
338
339 containers.put(realPath, fs);
340 containers.put(path, fs);
341
342 return fs;
343 }
344
345 private interface Container {
346 /**
347 * Insert all files in subdirectory subdirectory of container which
348 * match fileKinds into resultList
349 */
350 public abstract void list(Path userPath,
351 RelativeDirectory subdirectory,
352 Set<JavaFileObject.Kind> fileKinds,
353 boolean recurse,
354 ListBuffer<JavaFileObject> resultList) throws IOException;
355 public abstract JavaFileObject getFileObject(Path userPath, RelativeFile name) throws IOException;
356 public abstract void close() throws IOException;
357 public abstract boolean maintainsDirectoryIndex();
358
359 /**
360 * The directories this container indexes if {@link #maintainsDirectoryIndex()}, otherwise
361 * an empty iterable.
362 */
363 public abstract Iterable<RelativeDirectory> indexedDirectories();
364 }
365
366 private static final Container MISSING_CONTAINER = new Container() {
367 @Override
368 public void list(Path userPath,
369 RelativeDirectory subdirectory,
370 Set<JavaFileObject.Kind> fileKinds,
371 boolean recurse,
372 ListBuffer<JavaFileObject> resultList) throws IOException {
373 }
374 @Override
375 public JavaFileObject getFileObject(Path userPath, RelativeFile name) throws IOException {
376 return null;
377 }
378 @Override
379 public void close() throws IOException {}
380 @Override
381 public boolean maintainsDirectoryIndex() {
382 return false;
383 }
384 @Override
385 public Iterable<RelativeDirectory> indexedDirectories() {
386 return List.nil();
387 }
388 };
389
390 private final class JRTImageContainer implements Container {
391
392 /**
393 * Insert all files in a subdirectory of the platform image
394 * which match fileKinds into resultList.
395 */
396 @Override
397 public void list(Path userPath,
398 RelativeDirectory subdirectory,
399 Set<JavaFileObject.Kind> fileKinds,
400 boolean recurse,
401 ListBuffer<JavaFileObject> resultList) throws IOException {
402 try {
403 JRTIndex.Entry e = getJRTIndex().getEntry(subdirectory);
404 if (symbolFileEnabled && e.ctSym.hidden)
405 return;
406 for (Path file: e.files.values()) {
407 if (fileKinds.contains(getKind(file))) {
408 JavaFileObject fe
409 = PathFileObject.forJRTPath(JavacFileManager.this, file);
410 resultList.append(fe);
411 }
412 }
413
414 if (recurse) {
415 for (RelativeDirectory rd: e.subdirs) {
416 list(userPath, rd, fileKinds, recurse, resultList);
417 }
418 }
419 } catch (IOException ex) {
420 ex.printStackTrace(System.err);
421 log.error(Errors.ErrorReadingFile(userPath, getMessage(ex)));
422 }
423 }
424
425 @Override
426 public JavaFileObject getFileObject(Path userPath, RelativeFile name) throws IOException {
427 JRTIndex.Entry e = getJRTIndex().getEntry(name.dirname());
428 if (symbolFileEnabled && e.ctSym.hidden)
429 return null;
430 Path p = e.files.get(name.basename());
431 if (p != null) {
432 return PathFileObject.forJRTPath(JavacFileManager.this, p);
433 } else {
434 return null;
435 }
436 }
437
438 @Override
439 public void close() throws IOException {
440 }
441
442 @Override
443 public boolean maintainsDirectoryIndex() {
444 return false;
445 }
446
447 @Override
448 public Iterable<RelativeDirectory> indexedDirectories() {
449 return List.nil();
450 }
451 }
452
453 private synchronized JRTIndex getJRTIndex() {
454 if (jrtIndex == null)
455 jrtIndex = JRTIndex.getSharedInstance();
456 return jrtIndex;
457 }
458
459 private JRTIndex jrtIndex;
460
461 private final class DirectoryContainer implements Container {
462 private final Path directory;
463
464 public DirectoryContainer(Path directory) {
465 this.directory = directory;
466 }
467
468 /**
469 * Insert all files in subdirectory subdirectory of directory userPath
470 * which match fileKinds into resultList
471 */
472 @Override
473 public void list(Path userPath,
474 RelativeDirectory subdirectory,
475 Set<JavaFileObject.Kind> fileKinds,
476 boolean recurse,
477 ListBuffer<JavaFileObject> resultList) throws IOException {
478 Path d;
479 try {
480 d = subdirectory.resolveAgainst(userPath);
481 } catch (InvalidPathException ignore) {
482 return ;
483 }
484
485 if (!Files.exists(d)) {
486 return;
487 }
488
489 if (!caseMapCheck(d, subdirectory)) {
490 return;
491 }
492
493 java.util.List<Path> files;
494 try (Stream<Path> s = Files.list(d)) {
495 files = (sortFiles == null ? s : s.sorted(sortFiles)).toList();
496 } catch (IOException ignore) {
497 return;
498 }
499
500 for (Path f: files) {
501 String fname = f.getFileName().toString();
502 if (fname.endsWith("/"))
503 fname = fname.substring(0, fname.length() - 1);
504 if (Files.isDirectory(f)) {
505 if (recurse && SourceVersion.isIdentifier(fname)) {
506 list(userPath,
507 new RelativeDirectory(subdirectory, fname),
508 fileKinds,
509 recurse,
510 resultList);
511 }
512 } else {
513 if (isValidFile(fname, fileKinds)) {
514 try {
515 RelativeFile file = new RelativeFile(subdirectory, fname);
516 JavaFileObject fe = PathFileObject.forDirectoryPath(JavacFileManager.this,
517 file.resolveAgainst(directory), userPath, file);
518 resultList.append(fe);
519 } catch (InvalidPathException e) {
520 throw new IOException("error accessing directory " + directory + e);
521 }
522 }
523 }
524 }
525 }
526
527 @Override
528 public JavaFileObject getFileObject(Path userPath, RelativeFile name) throws IOException {
529 try {
530 Path f = name.resolveAgainst(userPath);
531 if (Files.exists(f))
532 return PathFileObject.forSimplePath(JavacFileManager.this,
533 fsInfo.getCanonicalFile(f), f);
534 } catch (InvalidPathException ignore) {
535 }
536 return null;
537 }
538
539 @Override
540 public void close() throws IOException {
541 }
542
543 @Override
544 public boolean maintainsDirectoryIndex() {
545 return false;
546 }
547
548 @Override
549 public Iterable<RelativeDirectory> indexedDirectories() {
550 return List.nil();
551 }
552 }
553
554 private static final Set<FileVisitOption> NO_FILE_VISIT_OPTIONS = Set.of();
555 private static final Set<FileVisitOption> FOLLOW_LINKS_OPTIONS = Set.of(FOLLOW_LINKS);
556
557 private final class ArchiveContainer implements Container {
558 private final Path archivePath;
559 private final FileSystem fileSystem;
560 private final Map<RelativeDirectory, Path> packages;
561
562 public ArchiveContainer(Path archivePath) throws IOException, ProviderNotFoundException {
563 this.archivePath = archivePath;
564 if (multiReleaseValue != null && archivePath.toString().endsWith(".jar")) {
565 FileSystemProvider jarFSProvider = fsInfo.getJarFSProvider();
566 Assert.checkNonNull(jarFSProvider, "should have been caught before!");
567 Map<String, ?> env = fsInfo.readOnlyJarFSEnv(multiReleaseValue);
568 try {
569 this.fileSystem = jarFSProvider.newFileSystem(archivePath, env);
570 } catch (ZipException ze) {
571 throw new IOException("ZipException opening \"" + archivePath.getFileName() + "\": " + ze.getMessage(), ze);
572 }
573 } else {
574 // Less common case is possible if the file manager was not initialized in JavacTask,
575 // or if non "*.jar" files are on the classpath. If this is not a ZIP/JAR file then it
576 // will ignore ZIP specific parameters in env, and may not end up being read-only.
577 // However, Javac should never attempt to write back to archives either way.
578 Map<String, ?> env = fsInfo.readOnlyJarFSEnv(null);
579 this.fileSystem = FileSystems.newFileSystem(archivePath, env);
580 }
581 packages = new HashMap<>();
582 for (Path root : fileSystem.getRootDirectories()) {
583 Files.walkFileTree(root, NO_FILE_VISIT_OPTIONS, Integer.MAX_VALUE,
584 new SimpleFileVisitor<Path>() {
585 @Override
586 public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) {
587 if (isValid(dir.getFileName())) {
588 packages.put(new RelativeDirectory(root.relativize(dir).toString()), dir);
589 return FileVisitResult.CONTINUE;
590 } else {
591 return FileVisitResult.SKIP_SUBTREE;
592 }
593 }
594 });
595 }
596 }
597
598 /**
599 * Insert all files in subdirectory subdirectory of this archive
600 * which match fileKinds into resultList
601 */
602 @Override
603 public void list(Path userPath,
604 RelativeDirectory subdirectory,
605 Set<JavaFileObject.Kind> fileKinds,
606 boolean recurse,
607 ListBuffer<JavaFileObject> resultList) throws IOException {
608 Path resolvedSubdirectory = packages.get(subdirectory);
609
610 if (resolvedSubdirectory == null)
611 return ;
612
613 int maxDepth = (recurse ? Integer.MAX_VALUE : 1);
614 Files.walkFileTree(resolvedSubdirectory, FOLLOW_LINKS_OPTIONS, maxDepth,
615 new SimpleFileVisitor<Path>() {
616 @Override
617 public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) {
618 if (isValid(dir.getFileName())) {
619 return FileVisitResult.CONTINUE;
620 } else {
621 return FileVisitResult.SKIP_SUBTREE;
622 }
623 }
624
625 @Override
626 public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) {
627 if (attrs.isRegularFile() && fileKinds.contains(getKind(file.getFileName().toString()))) {
628 JavaFileObject fe = PathFileObject.forJarPath(
629 JavacFileManager.this, file, archivePath);
630 resultList.append(fe);
631 }
632 return FileVisitResult.CONTINUE;
633 }
634 });
635
636 }
637
638 private boolean isValid(Path fileName) {
639 if (fileName == null) {
640 return true;
641 } else {
642 String name = fileName.toString();
643 if (name.endsWith("/")) {
644 name = name.substring(0, name.length() - 1);
645 }
646 return SourceVersion.isIdentifier(name);
647 }
648 }
649
650 @Override
651 public JavaFileObject getFileObject(Path userPath, RelativeFile name) throws IOException {
652 RelativeDirectory root = name.dirname();
653 Path packagepath = packages.get(root);
654 if (packagepath != null) {
655 Path relpath = packagepath.resolve(name.basename());
656 if (Files.exists(relpath)) {
657 return PathFileObject.forJarPath(JavacFileManager.this, relpath, userPath);
658 }
659 }
660 return null;
661 }
662
663 @Override
664 public void close() throws IOException {
665 fileSystem.close();
666 }
667
668 @Override
669 public boolean maintainsDirectoryIndex() {
670 return true;
671 }
672
673 @Override
674 public Iterable<RelativeDirectory> indexedDirectories() {
675 return packages.keySet();
676 }
677 }
678
679 /**
680 * container is a directory, a zip file, or a non-existent path.
681 */
682 private boolean isValidFile(String s, Set<JavaFileObject.Kind> fileKinds) {
683 JavaFileObject.Kind kind = getKind(s);
684 return fileKinds.contains(kind);
685 }
686
687 private static final boolean fileSystemIsCaseSensitive =
688 File.separatorChar == '/';
689
690 /** Hack to make Windows case sensitive. Test whether given path
691 * ends in a string of characters with the same case as given name.
692 * Ignore file separators in both path and name.
693 */
694 private boolean caseMapCheck(Path f, RelativePath name) {
695 if (fileSystemIsCaseSensitive) return true;
696 // Note that toRealPath() returns the case-sensitive
697 // spelled file name.
698 String path;
699 char sep;
700 try {
701 path = f.toRealPath(LinkOption.NOFOLLOW_LINKS).toString();
702 sep = f.getFileSystem().getSeparator().charAt(0);
703 } catch (IOException ex) {
704 return false;
705 }
706 char[] pcs = path.toCharArray();
707 char[] ncs = name.path.toCharArray();
708 int i = pcs.length - 1;
709 int j = ncs.length - 1;
710 while (i >= 0 && j >= 0) {
711 while (i >= 0 && pcs[i] == sep) i--;
712 while (j >= 0 && ncs[j] == '/') j--;
713 if (i >= 0 && j >= 0) {
714 if (pcs[i] != ncs[j]) return false;
715 i--;
716 j--;
717 }
718 }
719 return j < 0;
720 }
721
722 /** Flush any output resources.
723 */
724 @Override @DefinedBy(Api.COMPILER)
725 public void flush() {
726 contentCache.clear();
727 pathsAndContainersByLocationAndRelativeDirectory.clear();
728 nonIndexingContainersByLocation.clear();
729 }
730
731 /**
732 * Close the JavaFileManager, releasing resources.
733 */
734 @Override @DefinedBy(Api.COMPILER)
735 public void close() throws IOException {
736 if (deferredCloseTimeout > 0) {
737 deferredClose();
738 return;
739 }
740
741 locations.close();
742 for (Container container: containers.values()) {
743 container.close();
744 }
745 containers.clear();
746 pathsAndContainersByLocationAndRelativeDirectory.clear();
747 nonIndexingContainersByLocation.clear();
748 contentCache.clear();
749 resetOutputFilesWritten();
750 }
751
752 @Override @DefinedBy(Api.COMPILER)
753 public ClassLoader getClassLoader(Location location) {
754 checkNotModuleOrientedLocation(location);
755 Iterable<? extends File> path = getLocation(location);
756 if (path == null)
757 return null;
758 ListBuffer<URL> lb = new ListBuffer<>();
759 for (File f: path) {
760 try {
761 lb.append(f.toURI().toURL());
762 } catch (MalformedURLException e) {
763 throw new AssertionError(e);
764 }
765 }
766
767 return getClassLoader(lb.toArray(new URL[lb.size()]));
768 }
769
770 @Override @DefinedBy(Api.COMPILER)
771 public Iterable<JavaFileObject> list(Location location,
772 String packageName,
773 Set<JavaFileObject.Kind> kinds,
774 boolean recurse)
775 throws IOException
776 {
777 checkNotModuleOrientedLocation(location);
778 // validatePackageName(packageName);
779 nullCheck(packageName);
780 nullCheck(kinds);
781
782 RelativeDirectory subdirectory = RelativeDirectory.forPackage(packageName);
783 ListBuffer<JavaFileObject> results = new ListBuffer<>();
784
785 for (PathAndContainer pathAndContainer : pathsAndContainers(location, subdirectory)) {
786 Path directory = pathAndContainer.path;
787 Container container = pathAndContainer.container;
788 container.list(directory, subdirectory, kinds, recurse, results);
789 }
790
791 return results.toList();
792 }
793
794 @Override @DefinedBy(Api.COMPILER)
795 public String inferBinaryName(Location location, JavaFileObject file) {
796 checkNotModuleOrientedLocation(location);
797 Objects.requireNonNull(file);
798 // Need to match the path semantics of list(location, ...)
799 Iterable<? extends Path> path = getLocationAsPaths(location);
800 if (path == null) {
801 return null;
802 }
803
804 if (file instanceof PathFileObject pathFileObject) {
805 return pathFileObject.inferBinaryName(path);
806 } else
807 throw new IllegalArgumentException(file.getClass().getName());
808 }
809
810 @Override @DefinedBy(Api.COMPILER)
811 public boolean isSameFile(FileObject a, FileObject b) {
812 nullCheck(a);
813 nullCheck(b);
814 if (a instanceof PathFileObject pathFileObjectA && b instanceof PathFileObject pathFileObjectB)
815 return pathFileObjectA.isSameFile(pathFileObjectB);
816 return a.equals(b);
817 }
818
819 @Override @DefinedBy(Api.COMPILER)
820 public boolean hasLocation(Location location) {
821 nullCheck(location);
822 return locations.hasLocation(location);
823 }
824
825 protected boolean hasExplicitLocation(Location location) {
826 nullCheck(location);
827 return locations.hasExplicitLocation(location);
828 }
829
830 @Override @DefinedBy(Api.COMPILER)
831 public JavaFileObject getJavaFileForInput(Location location,
832 String className,
833 JavaFileObject.Kind kind)
834 throws IOException
835 {
836 checkNotModuleOrientedLocation(location);
837 // validateClassName(className);
838 nullCheck(className);
839 nullCheck(kind);
840 if (!SOURCE_OR_CLASS.contains(kind))
841 throw new IllegalArgumentException("Invalid kind: " + kind);
842 return getFileForInput(location, RelativeFile.forClass(className, kind));
843 }
844
845 @Override @DefinedBy(Api.COMPILER)
846 public FileObject getFileForInput(Location location,
847 String packageName,
848 String relativeName)
849 throws IOException
850 {
851 checkNotModuleOrientedLocation(location);
852 // validatePackageName(packageName);
853 nullCheck(packageName);
854 if (!isRelativeUri(relativeName))
855 throw new IllegalArgumentException("Invalid relative name: " + relativeName);
856 RelativeFile name = packageName.length() == 0
857 ? new RelativeFile(relativeName)
858 : new RelativeFile(RelativeDirectory.forPackage(packageName), relativeName);
859 return getFileForInput(location, name);
860 }
861
862 private JavaFileObject getFileForInput(Location location, RelativeFile name) throws IOException {
863 Iterable<? extends Path> path = getLocationAsPaths(location);
864 if (path == null)
865 return null;
866
867 for (Path file: path) {
868 JavaFileObject fo = getContainer(file).getFileObject(file, name);
869
870 if (fo != null) {
871 return fo;
872 }
873 }
874 return null;
875 }
876
877 @Override @DefinedBy(Api.COMPILER)
878 public JavaFileObject getJavaFileForOutput(Location location,
879 String className,
880 JavaFileObject.Kind kind,
881 FileObject sibling)
882 throws IOException
883 {
884 checkOutputLocation(location);
885 // validateClassName(className);
886 nullCheck(className);
887 nullCheck(kind);
888 if (!SOURCE_OR_CLASS.contains(kind))
889 throw new IllegalArgumentException("Invalid kind: " + kind);
890 return getFileForOutput(location, RelativeFile.forClass(className, kind), sibling);
891 }
892
893 @Override @DefinedBy(Api.COMPILER)
894 public FileObject getFileForOutput(Location location,
895 String packageName,
896 String relativeName,
897 FileObject sibling)
898 throws IOException
899 {
900 checkOutputLocation(location);
901 // validatePackageName(packageName);
902 nullCheck(packageName);
903 if (!isRelativeUri(relativeName))
904 throw new IllegalArgumentException("Invalid relative name: " + relativeName);
905 RelativeFile name = packageName.length() == 0
906 ? new RelativeFile(relativeName)
907 : new RelativeFile(RelativeDirectory.forPackage(packageName), relativeName);
908 return getFileForOutput(location, name, sibling);
909 }
910
911 private JavaFileObject getFileForOutput(Location location,
912 RelativeFile fileName,
913 FileObject sibling)
914 throws IOException
915 {
916 Path dir;
917 if (location == CLASS_OUTPUT) {
918 if (getClassOutDir() != null) {
919 dir = getClassOutDir();
920 } else {
921 // Sibling is the associated source of the class file (e.g. x/y/Foo.java).
922 // The base name for class output is the class file name (e.g. "Foo.class").
923 String baseName = fileName.basename();
924 // Use the sibling to determine the output location where possible, unless
925 // it is in a JAR/ZIP file (we don't attempt to write class files back into
926 // archives).
927 if (sibling instanceof PathFileObject pathFileObject && !pathFileObject.isJarFile()) {
928 return pathFileObject.getSibling(baseName);
929 } else {
930 // Without the sibling present, we just create an output path in the
931 // current working directory (this isn't great, but it is what older
932 // versions of the JDK did).
933 Path userPath = getPath(baseName);
934 Path realPath = fsInfo.getCanonicalFile(userPath);
935 return PathFileObject.forSimplePath(this, realPath, userPath);
936 }
937 }
938 } else if (location == SOURCE_OUTPUT) {
939 dir = (getSourceOutDir() != null ? getSourceOutDir() : getClassOutDir());
940 } else {
941 Iterable<? extends Path> path = locations.getLocation(location);
942 dir = null;
943 for (Path f: path) {
944 dir = f;
945 break;
946 }
947 }
948
949 try {
950 if (dir == null) {
951 dir = getPath(System.getProperty("user.dir"));
952 }
953 Path path = fileName.resolveAgainst(fsInfo.getCanonicalFile(dir));
954 return PathFileObject.forDirectoryPath(this, path, dir, fileName);
955 } catch (InvalidPathException e) {
956 throw new IOException("bad filename " + fileName, e);
957 }
958 }
959
960 @Override @DefinedBy(Api.COMPILER)
961 public Iterable<? extends JavaFileObject> getJavaFileObjectsFromFiles(
962 Iterable<? extends File> files)
963 {
964 ArrayList<PathFileObject> result;
965 if (files instanceof Collection<?> collection)
966 result = new ArrayList<>(collection.size());
967 else
968 result = new ArrayList<>();
969 for (File f: files) {
970 Objects.requireNonNull(f);
971 Path p = f.toPath();
972 result.add(PathFileObject.forSimplePath(this,
973 fsInfo.getCanonicalFile(p), p));
974 }
975 return result;
976 }
977
978 @Override @DefinedBy(Api.COMPILER)
979 public Iterable<? extends JavaFileObject> getJavaFileObjectsFromPaths(Collection<? extends Path> paths) {
980 ArrayList<PathFileObject> result;
981 if (paths != null) {
982 result = new ArrayList<>(paths.size());
983 for (Path p: paths)
984 result.add(PathFileObject.forSimplePath(this,
985 fsInfo.getCanonicalFile(p), p));
986 } else {
987 result = new ArrayList<>();
988 }
989 return result;
990 }
991
992 @Override @DefinedBy(Api.COMPILER)
993 public Iterable<? extends JavaFileObject> getJavaFileObjects(File... files) {
994 return getJavaFileObjectsFromFiles(Arrays.asList(nullCheck(files)));
995 }
996
997 @Override @DefinedBy(Api.COMPILER)
998 public Iterable<? extends JavaFileObject> getJavaFileObjects(Path... paths) {
999 return getJavaFileObjectsFromPaths(Arrays.asList(nullCheck(paths)));
1000 }
1001
1002 @Override @DefinedBy(Api.COMPILER)
1003 public void setLocation(Location location,
1004 Iterable<? extends File> searchpath)
1005 throws IOException
1006 {
1007 nullCheck(location);
1008 locations.setLocation(location, asPaths(searchpath));
1009 clearCachesForLocation(location);
1010 }
1011
1012 @Override @DefinedBy(Api.COMPILER)
1013 public void setLocationFromPaths(Location location,
1014 Collection<? extends Path> searchpath)
1015 throws IOException
1016 {
1017 nullCheck(location);
1018 locations.setLocation(location, nullCheck(searchpath));
1019 clearCachesForLocation(location);
1020 }
1021
1022 @Override @DefinedBy(Api.COMPILER)
1023 public Iterable<? extends File> getLocation(Location location) {
1024 nullCheck(location);
1025 return asFiles(locations.getLocation(location));
1026 }
1027
1028 @Override @DefinedBy(Api.COMPILER)
1029 public Collection<? extends Path> getLocationAsPaths(Location location) {
1030 nullCheck(location);
1031 return locations.getLocation(location);
1032 }
1033
1034 private java.util.List<PathAndContainer> pathsAndContainers(
1035 Location location, RelativeDirectory relativeDirectory) throws IOException {
1036 try {
1037 return pathsAndContainersByLocationAndRelativeDirectory.computeIfAbsent(
1038 location, this::indexPathsAndContainersByRelativeDirectory)
1039 .computeIfAbsent(
1040 relativeDirectory, d -> nonIndexingContainersByLocation.get(location));
1041 } catch (UncheckedIOException e) {
1042 throw e.getCause();
1043 }
1044 }
1045
1046 private Map<RelativeDirectory, java.util.List<PathAndContainer>> indexPathsAndContainersByRelativeDirectory(
1047 Location location) {
1048 Map<RelativeDirectory, java.util.List<PathAndContainer>> result = new HashMap<>();
1049 java.util.List<PathAndContainer> allPathsAndContainers = pathsAndContainers(location);
1050
1051 // First collect all of the containers that don't maintain their own index on
1052 // RelativeDirectory. These need to always be included for all mappings
1053 java.util.List<PathAndContainer> nonIndexingContainers = new ArrayList<>();
1054 for (PathAndContainer pathAndContainer : allPathsAndContainers) {
1055 if (!pathAndContainer.container.maintainsDirectoryIndex()) {
1056 nonIndexingContainers.add(pathAndContainer);
1057 }
1058 }
1059
1060 // Next, use the container that do maintain their own RelativeDirectory index to create a
1061 // single master index.
1062 for (PathAndContainer pathAndContainer : allPathsAndContainers) {
1063 Container container = pathAndContainer.container;
1064 if (container.maintainsDirectoryIndex()) {
1065 for (RelativeDirectory directory : container.indexedDirectories()) {
1066 result.computeIfAbsent(directory, d -> new ArrayList<>(nonIndexingContainers))
1067 .add(pathAndContainer);
1068 }
1069 }
1070 }
1071 nonIndexingContainersByLocation.put(location, nonIndexingContainers);
1072
1073 // Sorting preserves the search order used in the uncached Location path, which has
1074 // maintains consistency with the classpath order
1075 result.values().forEach(pathAndContainerList -> Collections.sort(pathAndContainerList));
1076
1077 return result;
1078 }
1079
1080 /**
1081 * For each {@linkplain #getLocationAsPaths(Location) path of the location}, compute the
1082 * corresponding {@link Container}.
1083 */
1084 private java.util.List<PathAndContainer> pathsAndContainers(Location location) {
1085 Collection<? extends Path> paths = getLocationAsPaths(location);
1086 if (paths == null) {
1087 return List.nil();
1088 }
1089 java.util.List<PathAndContainer> pathsAndContainers =
1090 new ArrayList<>(paths.size());
1091 for (Path path : paths) {
1092 Container container;
1093 try {
1094 container = getContainer(path);
1095 } catch (IOException e) {
1096 throw new UncheckedIOException(e);
1097 }
1098 pathsAndContainers.add(new PathAndContainer(path, container, pathsAndContainers.size()));
1099 }
1100 return pathsAndContainers;
1101 }
1102
1103 private static class PathAndContainer implements Comparable<PathAndContainer> {
1104 private final Path path;
1105 private final Container container;
1106 private final int index;
1107
1108 PathAndContainer(Path path, Container container, int index) {
1109 this.path = path;
1110 this.container = container;
1111 this.index = index;
1112 }
1113
1114 @Override
1115 public int compareTo(PathAndContainer other) {
1116 return index - other.index;
1117 }
1118
1119 @Override
1120 public boolean equals(Object o) {
1121 return (o instanceof PathAndContainer pathAndContainer)
1122 && path.equals(pathAndContainer.path)
1123 && container.equals(pathAndContainer.container)
1124 && index == pathAndContainer.index;
1125 }
1126
1127 @Override
1128 public int hashCode() {
1129 return Objects.hash(path, container, index);
1130 }
1131 }
1132
1133 @Override @DefinedBy(Api.COMPILER)
1134 public boolean contains(Location location, FileObject fo) throws IOException {
1135 nullCheck(location);
1136 nullCheck(fo);
1137 Path p = asPath(fo);
1138 return locations.contains(location, p);
1139 }
1140
1141 private Path getClassOutDir() {
1142 return locations.getOutputLocation(CLASS_OUTPUT);
1143 }
1144
1145 private Path getSourceOutDir() {
1146 return locations.getOutputLocation(SOURCE_OUTPUT);
1147 }
1148
1149 @Override @DefinedBy(Api.COMPILER)
1150 public Location getLocationForModule(Location location, String moduleName) throws IOException {
1151 checkModuleOrientedOrOutputLocation(location);
1152 nullCheck(moduleName);
1153 if (location == SOURCE_OUTPUT && getSourceOutDir() == null)
1154 location = CLASS_OUTPUT;
1155 return locations.getLocationForModule(location, moduleName);
1156 }
1157
1158 @Override @DefinedBy(Api.COMPILER)
1159 public <S> ServiceLoader<S> getServiceLoader(Location location, Class<S> service) throws IOException {
1160 nullCheck(location);
1161 nullCheck(service);
1162 getClass().getModule().addUses(service);
1163 if (location.isModuleOrientedLocation()) {
1164 Collection<Path> paths = locations.getLocation(location);
1165 ModuleFinder finder = ModuleFinder.of(paths.toArray(new Path[paths.size()]));
1166 ModuleLayer bootLayer = ModuleLayer.boot();
1167 Configuration cf = bootLayer.configuration().resolveAndBind(ModuleFinder.of(), finder, Collections.emptySet());
1168 ModuleLayer layer = bootLayer.defineModulesWithOneLoader(cf, ClassLoader.getSystemClassLoader());
1169 return ServiceLoader.load(layer, service);
1170 } else {
1171 return ServiceLoader.load(service, getClassLoader(location));
1172 }
1173 }
1174
1175 @Override @DefinedBy(Api.COMPILER)
1176 public Location getLocationForModule(Location location, JavaFileObject fo) throws IOException {
1177 checkModuleOrientedOrOutputLocation(location);
1178 if (!(fo instanceof PathFileObject pathFileObject))
1179 return null;
1180 Path p = Locations.normalize(pathFileObject.path);
1181 // need to find p in location
1182 return locations.getLocationForModule(location, p);
1183 }
1184
1185 @Override @DefinedBy(Api.COMPILER)
1186 public void setLocationForModule(Location location, String moduleName, Collection<? extends Path> paths)
1187 throws IOException {
1188 nullCheck(location);
1189 checkModuleOrientedOrOutputLocation(location);
1190 locations.setLocationForModule(location, nullCheck(moduleName), nullCheck(paths));
1191 clearCachesForLocation(location);
1192 }
1193
1194 @Override @DefinedBy(Api.COMPILER)
1195 public String inferModuleName(Location location) {
1196 checkNotModuleOrientedLocation(location);
1197 return locations.inferModuleName(location);
1198 }
1199
1200 @Override @DefinedBy(Api.COMPILER)
1201 public Iterable<Set<Location>> listLocationsForModules(Location location) throws IOException {
1202 checkModuleOrientedOrOutputLocation(location);
1203 return locations.listLocationsForModules(location);
1204 }
1205
1206 @Override @DefinedBy(Api.COMPILER)
1207 public Path asPath(FileObject file) {
1208 if (file instanceof PathFileObject pathFileObject) {
1209 return pathFileObject.path;
1210 } else
1211 throw new IllegalArgumentException(file.getName());
1212 }
1213
1214 /**
1215 * Enforces the specification of a "relative" name as used in
1216 * {@linkplain #getFileForInput(Location,String,String)
1217 * getFileForInput}. This method must follow the rules defined in
1218 * that method, do not make any changes without consulting the
1219 * specification.
1220 */
1221 protected static boolean isRelativeUri(URI uri) {
1222 if (uri.isAbsolute())
1223 return false;
1224 String path = uri.normalize().getPath();
1225 if (path.length() == 0 /* isEmpty() is mustang API */)
1226 return false;
1227 if (!path.equals(uri.getPath())) // implicitly checks for embedded . and ..
1228 return false;
1229 if (path.startsWith("/") || path.startsWith("./") || path.startsWith("../"))
1230 return false;
1231 return true;
1232 }
1233
1234 // Convenience method
1235 protected static boolean isRelativeUri(String u) {
1236 try {
1237 return isRelativeUri(new URI(u));
1238 } catch (URISyntaxException e) {
1239 return false;
1240 }
1241 }
1242
1243 /**
1244 * Converts a relative file name to a relative URI. This is
1245 * different from File.toURI as this method does not canonicalize
1246 * the file before creating the URI. Furthermore, no schema is
1247 * used.
1248 * @param file a relative file name
1249 * @return a relative URI
1250 * @throws IllegalArgumentException if the file name is not
1251 * relative according to the definition given in {@link
1252 * javax.tools.JavaFileManager#getFileForInput}
1253 */
1254 public static String getRelativeName(File file) {
1255 if (!file.isAbsolute()) {
1256 String result = file.getPath().replace(File.separatorChar, '/');
1257 if (isRelativeUri(result))
1258 return result;
1259 }
1260 throw new IllegalArgumentException("Invalid relative path: " + file);
1261 }
1262
1263 /**
1264 * Get a detail message from an IOException.
1265 * Most, but not all, instances of IOException provide a non-null result
1266 * for getLocalizedMessage(). But some instances return null: in these
1267 * cases, fall back to getMessage(), and if even that is null, return the
1268 * name of the exception itself.
1269 * @param e an IOException
1270 * @return a string to include in a compiler diagnostic
1271 */
1272 public static String getMessage(IOException e) {
1273 String s = e.getLocalizedMessage();
1274 if (s != null)
1275 return s;
1276 s = e.getMessage();
1277 if (s != null)
1278 return s;
1279 return e.toString();
1280 }
1281
1282 private void checkOutputLocation(Location location) {
1283 Objects.requireNonNull(location);
1284 if (!location.isOutputLocation())
1285 throw new IllegalArgumentException("location is not an output location: " + location.getName());
1286 }
1287
1288 private void checkModuleOrientedOrOutputLocation(Location location) {
1289 Objects.requireNonNull(location);
1290 if (!location.isModuleOrientedLocation() && !location.isOutputLocation())
1291 throw new IllegalArgumentException(
1292 "location is not an output location or a module-oriented location: "
1293 + location.getName());
1294 }
1295
1296 private void checkNotModuleOrientedLocation(Location location) {
1297 Objects.requireNonNull(location);
1298 if (location.isModuleOrientedLocation())
1299 throw new IllegalArgumentException("location is module-oriented: " + location.getName());
1300 }
1301
1302 /* Converters between files and paths.
1303 * These are temporary until we can update the StandardJavaFileManager API.
1304 */
1305
1306 private static Iterable<Path> asPaths(final Iterable<? extends File> files) {
1307 if (files == null)
1308 return null;
1309
1310 return () -> new Iterator<Path>() {
1311 Iterator<? extends File> iter = files.iterator();
1312
1313 @Override
1314 public boolean hasNext() {
1315 return iter.hasNext();
1316 }
1317
1318 @Override
1319 public Path next() {
1320 return iter.next().toPath();
1321 }
1322 };
1323 }
1324
1325 private static Iterable<File> asFiles(final Iterable<? extends Path> paths) {
1326 if (paths == null)
1327 return null;
1328
1329 return () -> new Iterator<File>() {
1330 Iterator<? extends Path> iter = paths.iterator();
1331
1332 @Override
1333 public boolean hasNext() {
1334 return iter.hasNext();
1335 }
1336
1337 @Override
1338 public File next() {
1339 try {
1340 return iter.next().toFile();
1341 } catch (UnsupportedOperationException e) {
1342 throw new IllegalStateException(e);
1343 }
1344 }
1345 };
1346 }
1347
1348 @Override
1349 public boolean handleOption(Option option, String value) {
1350 if (javacFileManagerOptions.contains(option)) {
1351 pathsAndContainersByLocationAndRelativeDirectory.clear();
1352 nonIndexingContainersByLocation.clear();
1353 }
1354 return super.handleOption(option, value);
1355 }
1356
1357 private void clearCachesForLocation(Location location) {
1358 nullCheck(location);
1359 pathsAndContainersByLocationAndRelativeDirectory.remove(location);
1360 nonIndexingContainersByLocation.remove(location);
1361 }
1362 }