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