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 Map<String,String> env = new HashMap<>(); 565 // ignores timestamps not stored in ZIP central directory, reducing I/O 566 // This key is handled by ZipFileSystem only. 567 env.put("zipinfo-time", "false"); 568 569 if (multiReleaseValue != null && archivePath.toString().endsWith(".jar")) { 570 env.put("multi-release", multiReleaseValue); 571 FileSystemProvider jarFSProvider = fsInfo.getJarFSProvider(); 572 Assert.checkNonNull(jarFSProvider, "should have been caught before!"); 573 try { 574 this.fileSystem = jarFSProvider.newFileSystem(archivePath, env); 575 } catch (ZipException ze) { 576 throw new IOException("ZipException opening \"" + archivePath.getFileName() + "\": " + ze.getMessage(), ze); 577 } 578 } else { 579 // Less common case is possible if the file manager was not initialized in JavacTask, 580 // or if non "*.jar" files are on the classpath. 581 this.fileSystem = FileSystems.newFileSystem(archivePath, env, (ClassLoader)null); 582 } 583 packages = new HashMap<>(); 584 for (Path root : fileSystem.getRootDirectories()) { 585 Files.walkFileTree(root, NO_FILE_VISIT_OPTIONS, Integer.MAX_VALUE, 586 new SimpleFileVisitor<Path>() { 587 @Override 588 public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) { 589 if (isValid(dir.getFileName())) { 590 packages.put(new RelativeDirectory(root.relativize(dir).toString()), dir); 591 return FileVisitResult.CONTINUE; 592 } else { 593 return FileVisitResult.SKIP_SUBTREE; 594 } 595 } 596 }); 597 } 598 } 599 600 /** 601 * Insert all files in subdirectory subdirectory of this archive 602 * which match fileKinds into resultList 603 */ 604 @Override 605 public void list(Path userPath, 606 RelativeDirectory subdirectory, 607 Set<JavaFileObject.Kind> fileKinds, 608 boolean recurse, 609 ListBuffer<JavaFileObject> resultList) throws IOException { 610 Path resolvedSubdirectory = packages.get(subdirectory); 611 612 if (resolvedSubdirectory == null) 613 return ; 614 615 int maxDepth = (recurse ? Integer.MAX_VALUE : 1); 616 Files.walkFileTree(resolvedSubdirectory, FOLLOW_LINKS_OPTIONS, maxDepth, 617 new SimpleFileVisitor<Path>() { 618 @Override 619 public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) { 620 if (isValid(dir.getFileName())) { 621 return FileVisitResult.CONTINUE; 622 } else { 623 return FileVisitResult.SKIP_SUBTREE; 624 } 625 } 626 627 @Override 628 public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) { 629 if (attrs.isRegularFile() && fileKinds.contains(getKind(file.getFileName().toString()))) { 630 JavaFileObject fe = PathFileObject.forJarPath( 631 JavacFileManager.this, file, archivePath); 632 resultList.append(fe); 633 } 634 return FileVisitResult.CONTINUE; 635 } 636 }); 637 638 } 639 640 private boolean isValid(Path fileName) { 641 if (fileName == null) { 642 return true; 643 } else { 644 String name = fileName.toString(); 645 if (name.endsWith("/")) { 646 name = name.substring(0, name.length() - 1); 647 } 648 return SourceVersion.isIdentifier(name); 649 } 650 } 651 652 @Override 653 public JavaFileObject getFileObject(Path userPath, RelativeFile name) throws IOException { 654 RelativeDirectory root = name.dirname(); 655 Path packagepath = packages.get(root); 656 if (packagepath != null) { 657 Path relpath = packagepath.resolve(name.basename()); 658 if (Files.exists(relpath)) { 659 return PathFileObject.forJarPath(JavacFileManager.this, relpath, userPath); 660 } 661 } 662 return null; 663 } 664 665 @Override 666 public void close() throws IOException { 667 fileSystem.close(); 668 } 669 670 @Override 671 public boolean maintainsDirectoryIndex() { 672 return true; 673 } 674 675 @Override 676 public Iterable<RelativeDirectory> indexedDirectories() { 677 return packages.keySet(); 678 } 679 } 680 681 /** 682 * container is a directory, a zip file, or a non-existent path. 683 */ 684 private boolean isValidFile(String s, Set<JavaFileObject.Kind> fileKinds) { 685 JavaFileObject.Kind kind = getKind(s); 686 return fileKinds.contains(kind); 687 } 688 689 private static final boolean fileSystemIsCaseSensitive = 690 File.separatorChar == '/'; 691 692 /** Hack to make Windows case sensitive. Test whether given path 693 * ends in a string of characters with the same case as given name. 694 * Ignore file separators in both path and name. 695 */ 696 private boolean caseMapCheck(Path f, RelativePath name) { 697 if (fileSystemIsCaseSensitive) return true; 698 // Note that toRealPath() returns the case-sensitive 699 // spelled file name. 700 String path; 701 char sep; 702 try { 703 path = f.toRealPath(LinkOption.NOFOLLOW_LINKS).toString(); 704 sep = f.getFileSystem().getSeparator().charAt(0); 705 } catch (IOException ex) { 706 return false; 707 } 708 char[] pcs = path.toCharArray(); 709 char[] ncs = name.path.toCharArray(); 710 int i = pcs.length - 1; 711 int j = ncs.length - 1; 712 while (i >= 0 && j >= 0) { 713 while (i >= 0 && pcs[i] == sep) i--; 714 while (j >= 0 && ncs[j] == '/') j--; 715 if (i >= 0 && j >= 0) { 716 if (pcs[i] != ncs[j]) return false; 717 i--; 718 j--; 719 } 720 } 721 return j < 0; 722 } 723 724 /** Flush any output resources. 725 */ 726 @Override @DefinedBy(Api.COMPILER) 727 public void flush() { 728 contentCache.clear(); 729 pathsAndContainersByLocationAndRelativeDirectory.clear(); 730 nonIndexingContainersByLocation.clear(); 731 } 732 733 /** 734 * Close the JavaFileManager, releasing resources. 735 */ 736 @Override @DefinedBy(Api.COMPILER) 737 public void close() throws IOException { 738 if (deferredCloseTimeout > 0) { 739 deferredClose(); 740 return; 741 } 742 743 locations.close(); 744 for (Container container: containers.values()) { 745 container.close(); 746 } 747 containers.clear(); 748 pathsAndContainersByLocationAndRelativeDirectory.clear(); 749 nonIndexingContainersByLocation.clear(); 750 contentCache.clear(); 751 resetOutputFilesWritten(); 752 } 753 754 @Override @DefinedBy(Api.COMPILER) 755 public ClassLoader getClassLoader(Location location) { 756 checkNotModuleOrientedLocation(location); 757 Iterable<? extends File> path = getLocation(location); 758 if (path == null) 759 return null; 760 ListBuffer<URL> lb = new ListBuffer<>(); 761 for (File f: path) { 762 try { 763 lb.append(f.toURI().toURL()); 764 } catch (MalformedURLException e) { 765 throw new AssertionError(e); 766 } 767 } 768 769 return getClassLoader(lb.toArray(new URL[lb.size()])); 770 } 771 772 @Override @DefinedBy(Api.COMPILER) 773 public Iterable<JavaFileObject> list(Location location, 774 String packageName, 775 Set<JavaFileObject.Kind> kinds, 776 boolean recurse) 777 throws IOException 778 { 779 checkNotModuleOrientedLocation(location); 780 // validatePackageName(packageName); 781 nullCheck(packageName); 782 nullCheck(kinds); 783 784 RelativeDirectory subdirectory = RelativeDirectory.forPackage(packageName); 785 ListBuffer<JavaFileObject> results = new ListBuffer<>(); 786 787 for (PathAndContainer pathAndContainer : pathsAndContainers(location, subdirectory)) { 788 Path directory = pathAndContainer.path; 789 Container container = pathAndContainer.container; 790 container.list(directory, subdirectory, kinds, recurse, results); 791 } 792 793 return results.toList(); 794 } 795 796 @Override @DefinedBy(Api.COMPILER) 797 public String inferBinaryName(Location location, JavaFileObject file) { 798 checkNotModuleOrientedLocation(location); 799 Objects.requireNonNull(file); 800 // Need to match the path semantics of list(location, ...) 801 Iterable<? extends Path> path = getLocationAsPaths(location); 802 if (path == null) { 803 return null; 804 } 805 806 if (file instanceof PathFileObject pathFileObject) { 807 return pathFileObject.inferBinaryName(path); 808 } else 809 throw new IllegalArgumentException(file.getClass().getName()); 810 } 811 812 @Override @DefinedBy(Api.COMPILER) 813 public boolean isSameFile(FileObject a, FileObject b) { 814 nullCheck(a); 815 nullCheck(b); 816 if (a instanceof PathFileObject pathFileObjectA && b instanceof PathFileObject pathFileObjectB) 817 return pathFileObjectA.isSameFile(pathFileObjectB); 818 return a.equals(b); 819 } 820 821 @Override @DefinedBy(Api.COMPILER) 822 public boolean hasLocation(Location location) { 823 nullCheck(location); 824 return locations.hasLocation(location); 825 } 826 827 protected boolean hasExplicitLocation(Location location) { 828 nullCheck(location); 829 return locations.hasExplicitLocation(location); 830 } 831 832 @Override @DefinedBy(Api.COMPILER) 833 public JavaFileObject getJavaFileForInput(Location location, 834 String className, 835 JavaFileObject.Kind kind) 836 throws IOException 837 { 838 checkNotModuleOrientedLocation(location); 839 // validateClassName(className); 840 nullCheck(className); 841 nullCheck(kind); 842 if (!SOURCE_OR_CLASS.contains(kind)) 843 throw new IllegalArgumentException("Invalid kind: " + kind); 844 return getFileForInput(location, RelativeFile.forClass(className, kind)); 845 } 846 847 @Override @DefinedBy(Api.COMPILER) 848 public FileObject getFileForInput(Location location, 849 String packageName, 850 String relativeName) 851 throws IOException 852 { 853 checkNotModuleOrientedLocation(location); 854 // validatePackageName(packageName); 855 nullCheck(packageName); 856 if (!isRelativeUri(relativeName)) 857 throw new IllegalArgumentException("Invalid relative name: " + relativeName); 858 RelativeFile name = packageName.length() == 0 859 ? new RelativeFile(relativeName) 860 : new RelativeFile(RelativeDirectory.forPackage(packageName), relativeName); 861 return getFileForInput(location, name); 862 } 863 864 private JavaFileObject getFileForInput(Location location, RelativeFile name) throws IOException { 865 Iterable<? extends Path> path = getLocationAsPaths(location); 866 if (path == null) 867 return null; 868 869 for (Path file: path) { 870 JavaFileObject fo = getContainer(file).getFileObject(file, name); 871 872 if (fo != null) { 873 return fo; 874 } 875 } 876 return null; 877 } 878 879 @Override @DefinedBy(Api.COMPILER) 880 public JavaFileObject getJavaFileForOutput(Location location, 881 String className, 882 JavaFileObject.Kind kind, 883 FileObject sibling) 884 throws IOException 885 { 886 checkOutputLocation(location); 887 // validateClassName(className); 888 nullCheck(className); 889 nullCheck(kind); 890 if (!SOURCE_OR_CLASS.contains(kind)) 891 throw new IllegalArgumentException("Invalid kind: " + kind); 892 return getFileForOutput(location, RelativeFile.forClass(className, kind), sibling); 893 } 894 895 @Override @DefinedBy(Api.COMPILER) 896 public FileObject getFileForOutput(Location location, 897 String packageName, 898 String relativeName, 899 FileObject sibling) 900 throws IOException 901 { 902 checkOutputLocation(location); 903 // validatePackageName(packageName); 904 nullCheck(packageName); 905 if (!isRelativeUri(relativeName)) 906 throw new IllegalArgumentException("Invalid relative name: " + relativeName); 907 RelativeFile name = packageName.length() == 0 908 ? new RelativeFile(relativeName) 909 : new RelativeFile(RelativeDirectory.forPackage(packageName), relativeName); 910 return getFileForOutput(location, name, sibling); 911 } 912 913 private JavaFileObject getFileForOutput(Location location, 914 RelativeFile fileName, 915 FileObject sibling) 916 throws IOException 917 { 918 Path dir; 919 if (location == CLASS_OUTPUT) { 920 if (getClassOutDir() != null) { 921 dir = getClassOutDir(); 922 } else { 923 // Sibling is the associated source of the class file (e.g. x/y/Foo.java). 924 // The base name for class output is the class file name (e.g. "Foo.class"). 925 String baseName = fileName.basename(); 926 // Use the sibling to determine the output location where possible, unless 927 // it is in a JAR/ZIP file (we don't attempt to write class files back into 928 // archives). 929 if (sibling instanceof PathFileObject pathFileObject && !pathFileObject.isJarFile()) { 930 return pathFileObject.getSibling(baseName); 931 } else { 932 // Without the sibling present, we just create an output path in the 933 // current working directory (this isn't great, but it is what older 934 // versions of the JDK did). 935 Path userPath = getPath(baseName); 936 Path realPath = fsInfo.getCanonicalFile(userPath); 937 return PathFileObject.forSimplePath(this, realPath, userPath); 938 } 939 } 940 } else if (location == SOURCE_OUTPUT) { 941 dir = (getSourceOutDir() != null ? getSourceOutDir() : getClassOutDir()); 942 } else { 943 Iterable<? extends Path> path = locations.getLocation(location); 944 dir = null; 945 for (Path f: path) { 946 dir = f; 947 break; 948 } 949 } 950 951 try { 952 if (dir == null) { 953 dir = getPath(System.getProperty("user.dir")); 954 } 955 Path path = fileName.resolveAgainst(fsInfo.getCanonicalFile(dir)); 956 return PathFileObject.forDirectoryPath(this, path, dir, fileName); 957 } catch (InvalidPathException e) { 958 throw new IOException("bad filename " + fileName, e); 959 } 960 } 961 962 @Override @DefinedBy(Api.COMPILER) 963 public Iterable<? extends JavaFileObject> getJavaFileObjectsFromFiles( 964 Iterable<? extends File> files) 965 { 966 ArrayList<PathFileObject> result; 967 if (files instanceof Collection<?> collection) 968 result = new ArrayList<>(collection.size()); 969 else 970 result = new ArrayList<>(); 971 for (File f: files) { 972 Objects.requireNonNull(f); 973 Path p = f.toPath(); 974 result.add(PathFileObject.forSimplePath(this, 975 fsInfo.getCanonicalFile(p), p)); 976 } 977 return result; 978 } 979 980 @Override @DefinedBy(Api.COMPILER) 981 public Iterable<? extends JavaFileObject> getJavaFileObjectsFromPaths(Collection<? extends Path> paths) { 982 ArrayList<PathFileObject> result; 983 if (paths != null) { 984 result = new ArrayList<>(paths.size()); 985 for (Path p: paths) 986 result.add(PathFileObject.forSimplePath(this, 987 fsInfo.getCanonicalFile(p), p)); 988 } else { 989 result = new ArrayList<>(); 990 } 991 return result; 992 } 993 994 @Override @DefinedBy(Api.COMPILER) 995 public Iterable<? extends JavaFileObject> getJavaFileObjects(File... files) { 996 return getJavaFileObjectsFromFiles(Arrays.asList(nullCheck(files))); 997 } 998 999 @Override @DefinedBy(Api.COMPILER) 1000 public Iterable<? extends JavaFileObject> getJavaFileObjects(Path... paths) { 1001 return getJavaFileObjectsFromPaths(Arrays.asList(nullCheck(paths))); 1002 } 1003 1004 @Override @DefinedBy(Api.COMPILER) 1005 public void setLocation(Location location, 1006 Iterable<? extends File> searchpath) 1007 throws IOException 1008 { 1009 nullCheck(location); 1010 locations.setLocation(location, asPaths(searchpath)); 1011 clearCachesForLocation(location); 1012 } 1013 1014 @Override @DefinedBy(Api.COMPILER) 1015 public void setLocationFromPaths(Location location, 1016 Collection<? extends Path> searchpath) 1017 throws IOException 1018 { 1019 nullCheck(location); 1020 locations.setLocation(location, nullCheck(searchpath)); 1021 clearCachesForLocation(location); 1022 } 1023 1024 @Override @DefinedBy(Api.COMPILER) 1025 public Iterable<? extends File> getLocation(Location location) { 1026 nullCheck(location); 1027 return asFiles(locations.getLocation(location)); 1028 } 1029 1030 @Override @DefinedBy(Api.COMPILER) 1031 public Collection<? extends Path> getLocationAsPaths(Location location) { 1032 nullCheck(location); 1033 return locations.getLocation(location); 1034 } 1035 1036 private java.util.List<PathAndContainer> pathsAndContainers( 1037 Location location, RelativeDirectory relativeDirectory) throws IOException { 1038 try { 1039 return pathsAndContainersByLocationAndRelativeDirectory.computeIfAbsent( 1040 location, this::indexPathsAndContainersByRelativeDirectory) 1041 .computeIfAbsent( 1042 relativeDirectory, d -> nonIndexingContainersByLocation.get(location)); 1043 } catch (UncheckedIOException e) { 1044 throw e.getCause(); 1045 } 1046 } 1047 1048 private Map<RelativeDirectory, java.util.List<PathAndContainer>> indexPathsAndContainersByRelativeDirectory( 1049 Location location) { 1050 Map<RelativeDirectory, java.util.List<PathAndContainer>> result = new HashMap<>(); 1051 java.util.List<PathAndContainer> allPathsAndContainers = pathsAndContainers(location); 1052 1053 // First collect all of the containers that don't maintain their own index on 1054 // RelativeDirectory. These need to always be included for all mappings 1055 java.util.List<PathAndContainer> nonIndexingContainers = new ArrayList<>(); 1056 for (PathAndContainer pathAndContainer : allPathsAndContainers) { 1057 if (!pathAndContainer.container.maintainsDirectoryIndex()) { 1058 nonIndexingContainers.add(pathAndContainer); 1059 } 1060 } 1061 1062 // Next, use the container that do maintain their own RelativeDirectory index to create a 1063 // single master index. 1064 for (PathAndContainer pathAndContainer : allPathsAndContainers) { 1065 Container container = pathAndContainer.container; 1066 if (container.maintainsDirectoryIndex()) { 1067 for (RelativeDirectory directory : container.indexedDirectories()) { 1068 result.computeIfAbsent(directory, d -> new ArrayList<>(nonIndexingContainers)) 1069 .add(pathAndContainer); 1070 } 1071 } 1072 } 1073 nonIndexingContainersByLocation.put(location, nonIndexingContainers); 1074 1075 // Sorting preserves the search order used in the uncached Location path, which has 1076 // maintains consistency with the classpath order 1077 result.values().forEach(pathAndContainerList -> Collections.sort(pathAndContainerList)); 1078 1079 return result; 1080 } 1081 1082 /** 1083 * For each {@linkplain #getLocationAsPaths(Location) path of the location}, compute the 1084 * corresponding {@link Container}. 1085 */ 1086 private java.util.List<PathAndContainer> pathsAndContainers(Location location) { 1087 Collection<? extends Path> paths = getLocationAsPaths(location); 1088 if (paths == null) { 1089 return List.nil(); 1090 } 1091 java.util.List<PathAndContainer> pathsAndContainers = 1092 new ArrayList<>(paths.size()); 1093 for (Path path : paths) { 1094 Container container; 1095 try { 1096 container = getContainer(path); 1097 } catch (IOException e) { 1098 throw new UncheckedIOException(e); 1099 } 1100 pathsAndContainers.add(new PathAndContainer(path, container, pathsAndContainers.size())); 1101 } 1102 return pathsAndContainers; 1103 } 1104 1105 private static class PathAndContainer implements Comparable<PathAndContainer> { 1106 private final Path path; 1107 private final Container container; 1108 private final int index; 1109 1110 PathAndContainer(Path path, Container container, int index) { 1111 this.path = path; 1112 this.container = container; 1113 this.index = index; 1114 } 1115 1116 @Override 1117 public int compareTo(PathAndContainer other) { 1118 return index - other.index; 1119 } 1120 1121 @Override 1122 public boolean equals(Object o) { 1123 return (o instanceof PathAndContainer pathAndContainer) 1124 && path.equals(pathAndContainer.path) 1125 && container.equals(pathAndContainer.container) 1126 && index == pathAndContainer.index; 1127 } 1128 1129 @Override 1130 public int hashCode() { 1131 return Objects.hash(path, container, index); 1132 } 1133 } 1134 1135 @Override @DefinedBy(Api.COMPILER) 1136 public boolean contains(Location location, FileObject fo) throws IOException { 1137 nullCheck(location); 1138 nullCheck(fo); 1139 Path p = asPath(fo); 1140 return locations.contains(location, p); 1141 } 1142 1143 private Path getClassOutDir() { 1144 return locations.getOutputLocation(CLASS_OUTPUT); 1145 } 1146 1147 private Path getSourceOutDir() { 1148 return locations.getOutputLocation(SOURCE_OUTPUT); 1149 } 1150 1151 @Override @DefinedBy(Api.COMPILER) 1152 public Location getLocationForModule(Location location, String moduleName) throws IOException { 1153 checkModuleOrientedOrOutputLocation(location); 1154 nullCheck(moduleName); 1155 if (location == SOURCE_OUTPUT && getSourceOutDir() == null) 1156 location = CLASS_OUTPUT; 1157 return locations.getLocationForModule(location, moduleName); 1158 } 1159 1160 @Override @DefinedBy(Api.COMPILER) 1161 public <S> ServiceLoader<S> getServiceLoader(Location location, Class<S> service) throws IOException { 1162 nullCheck(location); 1163 nullCheck(service); 1164 getClass().getModule().addUses(service); 1165 if (location.isModuleOrientedLocation()) { 1166 Collection<Path> paths = locations.getLocation(location); 1167 ModuleFinder finder = ModuleFinder.of(paths.toArray(new Path[paths.size()])); 1168 ModuleLayer bootLayer = ModuleLayer.boot(); 1169 Configuration cf = bootLayer.configuration().resolveAndBind(ModuleFinder.of(), finder, Collections.emptySet()); 1170 ModuleLayer layer = bootLayer.defineModulesWithOneLoader(cf, ClassLoader.getSystemClassLoader()); 1171 return ServiceLoader.load(layer, service); 1172 } else { 1173 return ServiceLoader.load(service, getClassLoader(location)); 1174 } 1175 } 1176 1177 @Override @DefinedBy(Api.COMPILER) 1178 public Location getLocationForModule(Location location, JavaFileObject fo) throws IOException { 1179 checkModuleOrientedOrOutputLocation(location); 1180 if (!(fo instanceof PathFileObject pathFileObject)) 1181 return null; 1182 Path p = Locations.normalize(pathFileObject.path); 1183 // need to find p in location 1184 return locations.getLocationForModule(location, p); 1185 } 1186 1187 @Override @DefinedBy(Api.COMPILER) 1188 public void setLocationForModule(Location location, String moduleName, Collection<? extends Path> paths) 1189 throws IOException { 1190 nullCheck(location); 1191 checkModuleOrientedOrOutputLocation(location); 1192 locations.setLocationForModule(location, nullCheck(moduleName), nullCheck(paths)); 1193 clearCachesForLocation(location); 1194 } 1195 1196 @Override @DefinedBy(Api.COMPILER) 1197 public String inferModuleName(Location location) { 1198 checkNotModuleOrientedLocation(location); 1199 return locations.inferModuleName(location); 1200 } 1201 1202 @Override @DefinedBy(Api.COMPILER) 1203 public Iterable<Set<Location>> listLocationsForModules(Location location) throws IOException { 1204 checkModuleOrientedOrOutputLocation(location); 1205 return locations.listLocationsForModules(location); 1206 } 1207 1208 @Override @DefinedBy(Api.COMPILER) 1209 public Path asPath(FileObject file) { 1210 if (file instanceof PathFileObject pathFileObject) { 1211 return pathFileObject.path; 1212 } else 1213 throw new IllegalArgumentException(file.getName()); 1214 } 1215 1216 /** 1217 * Enforces the specification of a "relative" name as used in 1218 * {@linkplain #getFileForInput(Location,String,String) 1219 * getFileForInput}. This method must follow the rules defined in 1220 * that method, do not make any changes without consulting the 1221 * specification. 1222 */ 1223 protected static boolean isRelativeUri(URI uri) { 1224 if (uri.isAbsolute()) 1225 return false; 1226 String path = uri.normalize().getPath(); 1227 if (path.length() == 0 /* isEmpty() is mustang API */) 1228 return false; 1229 if (!path.equals(uri.getPath())) // implicitly checks for embedded . and .. 1230 return false; 1231 if (path.startsWith("/") || path.startsWith("./") || path.startsWith("../")) 1232 return false; 1233 return true; 1234 } 1235 1236 // Convenience method 1237 protected static boolean isRelativeUri(String u) { 1238 try { 1239 return isRelativeUri(new URI(u)); 1240 } catch (URISyntaxException e) { 1241 return false; 1242 } 1243 } 1244 1245 /** 1246 * Converts a relative file name to a relative URI. This is 1247 * different from File.toURI as this method does not canonicalize 1248 * the file before creating the URI. Furthermore, no schema is 1249 * used. 1250 * @param file a relative file name 1251 * @return a relative URI 1252 * @throws IllegalArgumentException if the file name is not 1253 * relative according to the definition given in {@link 1254 * javax.tools.JavaFileManager#getFileForInput} 1255 */ 1256 public static String getRelativeName(File file) { 1257 if (!file.isAbsolute()) { 1258 String result = file.getPath().replace(File.separatorChar, '/'); 1259 if (isRelativeUri(result)) 1260 return result; 1261 } 1262 throw new IllegalArgumentException("Invalid relative path: " + file); 1263 } 1264 1265 /** 1266 * Get a detail message from an IOException. 1267 * Most, but not all, instances of IOException provide a non-null result 1268 * for getLocalizedMessage(). But some instances return null: in these 1269 * cases, fall back to getMessage(), and if even that is null, return the 1270 * name of the exception itself. 1271 * @param e an IOException 1272 * @return a string to include in a compiler diagnostic 1273 */ 1274 public static String getMessage(IOException e) { 1275 String s = e.getLocalizedMessage(); 1276 if (s != null) 1277 return s; 1278 s = e.getMessage(); 1279 if (s != null) 1280 return s; 1281 return e.toString(); 1282 } 1283 1284 private void checkOutputLocation(Location location) { 1285 Objects.requireNonNull(location); 1286 if (!location.isOutputLocation()) 1287 throw new IllegalArgumentException("location is not an output location: " + location.getName()); 1288 } 1289 1290 private void checkModuleOrientedOrOutputLocation(Location location) { 1291 Objects.requireNonNull(location); 1292 if (!location.isModuleOrientedLocation() && !location.isOutputLocation()) 1293 throw new IllegalArgumentException( 1294 "location is not an output location or a module-oriented location: " 1295 + location.getName()); 1296 } 1297 1298 private void checkNotModuleOrientedLocation(Location location) { 1299 Objects.requireNonNull(location); 1300 if (location.isModuleOrientedLocation()) 1301 throw new IllegalArgumentException("location is module-oriented: " + location.getName()); 1302 } 1303 1304 /* Converters between files and paths. 1305 * These are temporary until we can update the StandardJavaFileManager API. 1306 */ 1307 1308 private static Iterable<Path> asPaths(final Iterable<? extends File> files) { 1309 if (files == null) 1310 return null; 1311 1312 return () -> new Iterator<Path>() { 1313 Iterator<? extends File> iter = files.iterator(); 1314 1315 @Override 1316 public boolean hasNext() { 1317 return iter.hasNext(); 1318 } 1319 1320 @Override 1321 public Path next() { 1322 return iter.next().toPath(); 1323 } 1324 }; 1325 } 1326 1327 private static Iterable<File> asFiles(final Iterable<? extends Path> paths) { 1328 if (paths == null) 1329 return null; 1330 1331 return () -> new Iterator<File>() { 1332 Iterator<? extends Path> iter = paths.iterator(); 1333 1334 @Override 1335 public boolean hasNext() { 1336 return iter.hasNext(); 1337 } 1338 1339 @Override 1340 public File next() { 1341 try { 1342 return iter.next().toFile(); 1343 } catch (UnsupportedOperationException e) { 1344 throw new IllegalStateException(e); 1345 } 1346 } 1347 }; 1348 } 1349 1350 @Override 1351 public boolean handleOption(Option option, String value) { 1352 if (javacFileManagerOptions.contains(option)) { 1353 pathsAndContainersByLocationAndRelativeDirectory.clear(); 1354 nonIndexingContainersByLocation.clear(); 1355 } 1356 return super.handleOption(option, value); 1357 } 1358 1359 private void clearCachesForLocation(Location location) { 1360 nullCheck(location); 1361 pathsAndContainersByLocationAndRelativeDirectory.remove(location); 1362 nonIndexingContainersByLocation.remove(location); 1363 } 1364 }