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