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