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