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