1/* 2 * Copyright (c) 2005, 2017, 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 26package com.sun.tools.javac.file; 27 28import java.io.File; 29import java.io.IOException; 30import java.net.MalformedURLException; 31import java.net.URI; 32import java.net.URISyntaxException; 33import java.net.URL; 34import java.nio.CharBuffer; 35import java.nio.charset.Charset; 36import java.nio.file.FileSystem; 37import java.nio.file.FileSystems; 38import java.nio.file.FileVisitOption; 39import java.nio.file.FileVisitResult; 40import java.nio.file.Files; 41import java.nio.file.InvalidPathException; 42import java.nio.file.LinkOption; 43import java.nio.file.Path; 44import java.nio.file.Paths; 45import java.nio.file.ProviderNotFoundException; 46import java.nio.file.SimpleFileVisitor; 47import java.nio.file.attribute.BasicFileAttributes; 48import java.nio.file.spi.FileSystemProvider; 49import java.util.ArrayList; 50import java.util.Arrays; 51import java.util.Collection; 52import java.util.Collections; 53import java.util.Comparator; 54import java.util.EnumSet; 55import java.util.HashMap; 56import java.util.Iterator; 57import java.util.Map; 58import java.util.Objects; 59import java.util.ServiceLoader; 60import java.util.Set; 61import java.util.stream.Collectors; 62import java.util.stream.Stream; 63 64import javax.lang.model.SourceVersion; 65import javax.tools.FileObject; 66import javax.tools.JavaFileManager; 67import javax.tools.JavaFileObject; 68import javax.tools.StandardJavaFileManager; 69 70import com.sun.tools.javac.file.RelativePath.RelativeDirectory; 71import com.sun.tools.javac.file.RelativePath.RelativeFile; 72import com.sun.tools.javac.util.Assert; 73import com.sun.tools.javac.util.Context; 74import com.sun.tools.javac.util.Context.Factory; 75import com.sun.tools.javac.util.DefinedBy; 76import com.sun.tools.javac.util.DefinedBy.Api; 77import com.sun.tools.javac.util.List; 78import com.sun.tools.javac.util.ListBuffer; 79import com.sun.tools.javac.util.JDK9Wrappers.Configuration; 80import com.sun.tools.javac.util.JDK9Wrappers.Layer; 81import com.sun.tools.javac.util.JDK9Wrappers.ModuleFinder; 82import com.sun.tools.javac.util.JDK9Wrappers.Module; 83import com.sun.tools.javac.util.JDK9Wrappers.ServiceLoaderHelper; 84 85import static java.nio.file.FileVisitOption.FOLLOW_LINKS; 86 87import 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 */ 98public 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 try { 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 } catch (InvalidPathException e) { 482 throw new IOException("error accessing directory " + directory + e); 483 } 484 } 485 } 486 } 487 } 488 489 @Override 490 public JavaFileObject getFileObject(Path userPath, RelativeFile name) throws IOException { 491 try { 492 Path f = name.resolveAgainst(userPath); 493 if (Files.exists(f)) 494 return PathFileObject.forSimplePath(JavacFileManager.this, 495 fsInfo.getCanonicalFile(f), f); 496 } catch (InvalidPathException ignore) { 497 } 498 return null; 499 } 500 501 @Override 502 public void close() throws IOException { 503 } 504 } 505 506 private final class ArchiveContainer implements Container { 507 private final Path archivePath; 508 private final FileSystem fileSystem; 509 private final Map<RelativePath, Path> packages; 510 511 public ArchiveContainer(Path archivePath) throws IOException, ProviderNotFoundException, SecurityException { 512 this.archivePath = archivePath; 513 if (multiReleaseValue != null && archivePath.toString().endsWith(".jar")) { 514 Map<String,String> env = Collections.singletonMap("multi-release", multiReleaseValue); 515 FileSystemProvider jarFSProvider = fsInfo.getJarFSProvider(); 516 Assert.checkNonNull(jarFSProvider, "should have been caught before!"); 517 this.fileSystem = jarFSProvider.newFileSystem(archivePath, env); 518 } else { 519 this.fileSystem = FileSystems.newFileSystem(archivePath, null); 520 } 521 packages = new HashMap<>(); 522 for (Path root : fileSystem.getRootDirectories()) { 523 Files.walkFileTree(root, EnumSet.noneOf(FileVisitOption.class), Integer.MAX_VALUE, 524 new SimpleFileVisitor<Path>() { 525 @Override 526 public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) { 527 if (isValid(dir.getFileName())) { 528 packages.put(new RelativeDirectory(root.relativize(dir).toString()), dir); 529 return FileVisitResult.CONTINUE; 530 } else { 531 return FileVisitResult.SKIP_SUBTREE; 532 } 533 } 534 }); 535 } 536 } 537 538 /** 539 * Insert all files in subdirectory subdirectory of this archive 540 * which match fileKinds into resultList 541 */ 542 @Override 543 public void list(Path userPath, 544 RelativeDirectory subdirectory, 545 Set<JavaFileObject.Kind> fileKinds, 546 boolean recurse, 547 ListBuffer<JavaFileObject> resultList) throws IOException { 548 Path resolvedSubdirectory = packages.get(subdirectory); 549 550 if (resolvedSubdirectory == null) 551 return ; 552 553 int maxDepth = (recurse ? Integer.MAX_VALUE : 1); 554 Set<FileVisitOption> opts = EnumSet.of(FOLLOW_LINKS); 555 Files.walkFileTree(resolvedSubdirectory, opts, maxDepth, 556 new SimpleFileVisitor<Path>() { 557 @Override 558 public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) { 559 if (isValid(dir.getFileName())) { 560 return FileVisitResult.CONTINUE; 561 } else { 562 return FileVisitResult.SKIP_SUBTREE; 563 } 564 } 565 566 @Override 567 public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) { 568 if (attrs.isRegularFile() && fileKinds.contains(getKind(file.getFileName().toString()))) { 569 JavaFileObject fe = PathFileObject.forJarPath( 570 JavacFileManager.this, file, archivePath); 571 resultList.append(fe); 572 } 573 return FileVisitResult.CONTINUE; 574 } 575 }); 576 577 } 578 579 private boolean isValid(Path fileName) { 580 if (fileName == null) { 581 return true; 582 } else { 583 String name = fileName.toString(); 584 if (name.endsWith("/")) { 585 name = name.substring(0, name.length() - 1); 586 } 587 return SourceVersion.isIdentifier(name); 588 } 589 } 590 591 @Override 592 public JavaFileObject getFileObject(Path userPath, RelativeFile name) throws IOException { 593 RelativeDirectory root = name.dirname(); 594 Path packagepath = packages.get(root); 595 if (packagepath != null) { 596 Path relpath = packagepath.resolve(name.basename()); 597 if (Files.exists(relpath)) { 598 return PathFileObject.forJarPath(JavacFileManager.this, relpath, userPath); 599 } 600 } 601 return null; 602 } 603 604 @Override 605 public void close() throws IOException { 606 fileSystem.close(); 607 } 608 } 609 610 /** 611 * container is a directory, a zip file, or a non-existent path. 612 */ 613 private boolean isValidFile(String s, Set<JavaFileObject.Kind> fileKinds) { 614 JavaFileObject.Kind kind = getKind(s); 615 return fileKinds.contains(kind); 616 } 617 618 private static final boolean fileSystemIsCaseSensitive = 619 File.separatorChar == '/'; 620 621 /** Hack to make Windows case sensitive. Test whether given path 622 * ends in a string of characters with the same case as given name. 623 * Ignore file separators in both path and name. 624 */ 625 private boolean caseMapCheck(Path f, RelativePath name) { 626 if (fileSystemIsCaseSensitive) return true; 627 // Note that toRealPath() returns the case-sensitive 628 // spelled file name. 629 String path; 630 char sep; 631 try { 632 path = f.toRealPath(LinkOption.NOFOLLOW_LINKS).toString(); 633 sep = f.getFileSystem().getSeparator().charAt(0); 634 } catch (IOException ex) { 635 return false; 636 } 637 char[] pcs = path.toCharArray(); 638 char[] ncs = name.path.toCharArray(); 639 int i = pcs.length - 1; 640 int j = ncs.length - 1; 641 while (i >= 0 && j >= 0) { 642 while (i >= 0 && pcs[i] == sep) i--; 643 while (j >= 0 && ncs[j] == '/') j--; 644 if (i >= 0 && j >= 0) { 645 if (pcs[i] != ncs[j]) return false; 646 i--; 647 j--; 648 } 649 } 650 return j < 0; 651 } 652 653 /** Flush any output resources. 654 */ 655 @Override @DefinedBy(Api.COMPILER) 656 public void flush() { 657 contentCache.clear(); 658 } 659 660 /** 661 * Close the JavaFileManager, releasing resources. 662 */ 663 @Override @DefinedBy(Api.COMPILER) 664 public void close() throws IOException { 665 if (deferredCloseTimeout > 0) { 666 deferredClose(); 667 return; 668 } 669 670 locations.close(); 671 for (Container container: containers.values()) { 672 container.close(); 673 } 674 containers.clear(); 675 contentCache.clear(); 676 } 677 678 @Override @DefinedBy(Api.COMPILER) 679 public ClassLoader getClassLoader(Location location) { 680 checkNotModuleOrientedLocation(location); 681 Iterable<? extends File> path = getLocation(location); 682 if (path == null) 683 return null; 684 ListBuffer<URL> lb = new ListBuffer<>(); 685 for (File f: path) { 686 try { 687 lb.append(f.toURI().toURL()); 688 } catch (MalformedURLException e) { 689 throw new AssertionError(e); 690 } 691 } 692 693 return getClassLoader(lb.toArray(new URL[lb.size()])); 694 } 695 696 @Override @DefinedBy(Api.COMPILER) 697 public Iterable<JavaFileObject> list(Location location, 698 String packageName, 699 Set<JavaFileObject.Kind> kinds, 700 boolean recurse) 701 throws IOException 702 { 703 checkNotModuleOrientedLocation(location); 704 // validatePackageName(packageName); 705 nullCheck(packageName); 706 nullCheck(kinds); 707 708 Iterable<? extends Path> path = getLocationAsPaths(location); 709 if (path == null) 710 return List.nil(); 711 RelativeDirectory subdirectory = RelativeDirectory.forPackage(packageName); 712 ListBuffer<JavaFileObject> results = new ListBuffer<>(); 713 714 for (Path directory : path) { 715 Container container = getContainer(directory); 716 717 container.list(directory, subdirectory, kinds, recurse, results); 718 } 719 720 return results.toList(); 721 } 722 723 @Override @DefinedBy(Api.COMPILER) 724 public String inferBinaryName(Location location, JavaFileObject file) { 725 checkNotModuleOrientedLocation(location); 726 Objects.requireNonNull(file); 727 // Need to match the path semantics of list(location, ...) 728 Iterable<? extends Path> path = getLocationAsPaths(location); 729 if (path == null) { 730 return null; 731 } 732 733 if (file instanceof PathFileObject) { 734 return ((PathFileObject) file).inferBinaryName(path); 735 } else 736 throw new IllegalArgumentException(file.getClass().getName()); 737 } 738 739 @Override @DefinedBy(Api.COMPILER) 740 public boolean isSameFile(FileObject a, FileObject b) { 741 nullCheck(a); 742 nullCheck(b); 743 if (a instanceof PathFileObject && b instanceof PathFileObject) 744 return ((PathFileObject) a).isSameFile((PathFileObject) b); 745 return a.equals(b); 746 } 747 748 @Override @DefinedBy(Api.COMPILER) 749 public boolean hasLocation(Location location) { 750 nullCheck(location); 751 return locations.hasLocation(location); 752 } 753 754 @Override @DefinedBy(Api.COMPILER) 755 public JavaFileObject getJavaFileForInput(Location location, 756 String className, 757 JavaFileObject.Kind kind) 758 throws IOException 759 { 760 checkNotModuleOrientedLocation(location); 761 // validateClassName(className); 762 nullCheck(className); 763 nullCheck(kind); 764 if (!sourceOrClass.contains(kind)) 765 throw new IllegalArgumentException("Invalid kind: " + kind); 766 return getFileForInput(location, RelativeFile.forClass(className, kind)); 767 } 768 769 @Override @DefinedBy(Api.COMPILER) 770 public FileObject getFileForInput(Location location, 771 String packageName, 772 String relativeName) 773 throws IOException 774 { 775 checkNotModuleOrientedLocation(location); 776 // validatePackageName(packageName); 777 nullCheck(packageName); 778 if (!isRelativeUri(relativeName)) 779 throw new IllegalArgumentException("Invalid relative name: " + relativeName); 780 RelativeFile name = packageName.length() == 0 781 ? new RelativeFile(relativeName) 782 : new RelativeFile(RelativeDirectory.forPackage(packageName), relativeName); 783 return getFileForInput(location, name); 784 } 785 786 private JavaFileObject getFileForInput(Location location, RelativeFile name) throws IOException { 787 Iterable<? extends Path> path = getLocationAsPaths(location); 788 if (path == null) 789 return null; 790 791 for (Path file: path) { 792 JavaFileObject fo = getContainer(file).getFileObject(file, name); 793 794 if (fo != null) { 795 return fo; 796 } 797 } 798 return null; 799 } 800 801 @Override @DefinedBy(Api.COMPILER) 802 public JavaFileObject getJavaFileForOutput(Location location, 803 String className, 804 JavaFileObject.Kind kind, 805 FileObject sibling) 806 throws IOException 807 { 808 checkOutputLocation(location); 809 // validateClassName(className); 810 nullCheck(className); 811 nullCheck(kind); 812 if (!sourceOrClass.contains(kind)) 813 throw new IllegalArgumentException("Invalid kind: " + kind); 814 return getFileForOutput(location, RelativeFile.forClass(className, kind), sibling); 815 } 816 817 @Override @DefinedBy(Api.COMPILER) 818 public FileObject getFileForOutput(Location location, 819 String packageName, 820 String relativeName, 821 FileObject sibling) 822 throws IOException 823 { 824 checkOutputLocation(location); 825 // validatePackageName(packageName); 826 nullCheck(packageName); 827 if (!isRelativeUri(relativeName)) 828 throw new IllegalArgumentException("Invalid relative name: " + relativeName); 829 RelativeFile name = packageName.length() == 0 830 ? new RelativeFile(relativeName) 831 : new RelativeFile(RelativeDirectory.forPackage(packageName), relativeName); 832 return getFileForOutput(location, name, sibling); 833 } 834 835 private JavaFileObject getFileForOutput(Location location, 836 RelativeFile fileName, 837 FileObject sibling) 838 throws IOException 839 { 840 Path dir; 841 if (location == CLASS_OUTPUT) { 842 if (getClassOutDir() != null) { 843 dir = getClassOutDir(); 844 } else { 845 String baseName = fileName.basename(); 846 if (sibling != null && sibling instanceof PathFileObject) { 847 return ((PathFileObject) sibling).getSibling(baseName); 848 } else { 849 Path p = getPath(baseName); 850 Path real = fsInfo.getCanonicalFile(p); 851 return PathFileObject.forSimplePath(this, real, p); 852 } 853 } 854 } else if (location == SOURCE_OUTPUT) { 855 dir = (getSourceOutDir() != null ? getSourceOutDir() : getClassOutDir()); 856 } else { 857 Iterable<? extends Path> path = locations.getLocation(location); 858 dir = null; 859 for (Path f: path) { 860 dir = f; 861 break; 862 } 863 } 864 865 try { 866 if (dir == null) { 867 dir = getPath(System.getProperty("user.dir")); 868 } 869 Path path = fileName.resolveAgainst(fsInfo.getCanonicalFile(dir)); 870 return PathFileObject.forDirectoryPath(this, path, dir, fileName); 871 } catch (InvalidPathException e) { 872 throw new IOException("bad filename " + fileName, e); 873 } 874 } 875 876 @Override @DefinedBy(Api.COMPILER) 877 public Iterable<? extends JavaFileObject> getJavaFileObjectsFromFiles( 878 Iterable<? extends File> files) 879 { 880 ArrayList<PathFileObject> result; 881 if (files instanceof Collection<?>) 882 result = new ArrayList<>(((Collection<?>)files).size()); 883 else 884 result = new ArrayList<>(); 885 for (File f: files) { 886 Objects.requireNonNull(f); 887 Path p = f.toPath(); 888 result.add(PathFileObject.forSimplePath(this, 889 fsInfo.getCanonicalFile(p), p)); 890 } 891 return result; 892 } 893 894 @Override @DefinedBy(Api.COMPILER) 895 public Iterable<? extends JavaFileObject> getJavaFileObjectsFromPaths( 896 Iterable<? extends Path> paths) 897 { 898 ArrayList<PathFileObject> result; 899 if (paths instanceof Collection<?>) 900 result = new ArrayList<>(((Collection<?>)paths).size()); 901 else 902 result = new ArrayList<>(); 903 for (Path p: paths) 904 result.add(PathFileObject.forSimplePath(this, 905 fsInfo.getCanonicalFile(p), p)); 906 return result; 907 } 908 909 @Override @DefinedBy(Api.COMPILER) 910 public Iterable<? extends JavaFileObject> getJavaFileObjects(File... files) { 911 return getJavaFileObjectsFromFiles(Arrays.asList(nullCheck(files))); 912 } 913 914 @Override @DefinedBy(Api.COMPILER) 915 public Iterable<? extends JavaFileObject> getJavaFileObjects(Path... paths) { 916 return getJavaFileObjectsFromPaths(Arrays.asList(nullCheck(paths))); 917 } 918 919 @Override @DefinedBy(Api.COMPILER) 920 public void setLocation(Location location, 921 Iterable<? extends File> searchpath) 922 throws IOException 923 { 924 nullCheck(location); 925 locations.setLocation(location, asPaths(searchpath)); 926 } 927 928 @Override @DefinedBy(Api.COMPILER) 929 public void setLocationFromPaths(Location location, 930 Collection<? extends Path> searchpath) 931 throws IOException 932 { 933 nullCheck(location); 934 locations.setLocation(location, nullCheck(searchpath)); 935 } 936 937 @Override @DefinedBy(Api.COMPILER) 938 public Iterable<? extends File> getLocation(Location location) { 939 nullCheck(location); 940 return asFiles(locations.getLocation(location)); 941 } 942 943 @Override @DefinedBy(Api.COMPILER) 944 public Iterable<? extends Path> getLocationAsPaths(Location location) { 945 nullCheck(location); 946 return locations.getLocation(location); 947 } 948 949 @Override @DefinedBy(Api.COMPILER) 950 public boolean contains(Location location, FileObject fo) throws IOException { 951 nullCheck(location); 952 nullCheck(fo); 953 Path p = asPath(fo); 954 return locations.contains(location, p); 955 } 956 957 private Path getClassOutDir() { 958 return locations.getOutputLocation(CLASS_OUTPUT); 959 } 960 961 private Path getSourceOutDir() { 962 return locations.getOutputLocation(SOURCE_OUTPUT); 963 } 964 965 @Override @DefinedBy(Api.COMPILER) 966 public Location getLocationForModule(Location location, String moduleName) throws IOException { 967 checkModuleOrientedOrOutputLocation(location); 968 nullCheck(moduleName); 969 if (location == SOURCE_OUTPUT && getSourceOutDir() == null) 970 location = CLASS_OUTPUT; 971 return locations.getLocationForModule(location, moduleName); 972 } 973 974 @Override @DefinedBy(Api.COMPILER) 975 public <S> ServiceLoader<S> getServiceLoader(Location location, Class<S> service) throws IOException { 976 nullCheck(location); 977 nullCheck(service); 978 Module.getModule(getClass()).addUses(service); 979 if (location.isModuleOrientedLocation()) { 980 Collection<Path> paths = locations.getLocation(location); 981 ModuleFinder finder = ModuleFinder.of(paths.toArray(new Path[paths.size()])); 982 Layer bootLayer = Layer.boot(); 983 Configuration cf = bootLayer.configuration().resolveAndBind(ModuleFinder.of(), finder, Collections.emptySet()); 984 Layer layer = bootLayer.defineModulesWithOneLoader(cf, ClassLoader.getSystemClassLoader()); 985 return ServiceLoaderHelper.load(layer, service); 986 } else { 987 return ServiceLoader.load(service, getClassLoader(location)); 988 } 989 } 990 991 @Override @DefinedBy(Api.COMPILER) 992 public Location getLocationForModule(Location location, JavaFileObject fo) throws IOException { 993 checkModuleOrientedOrOutputLocation(location); 994 if (!(fo instanceof PathFileObject)) 995 return null; 996 Path p = Locations.normalize(((PathFileObject) fo).path); 997 // need to find p in location 998 return locations.getLocationForModule(location, p); 999 } 1000 1001 @Override @DefinedBy(Api.COMPILER) 1002 public void setLocationForModule(Location location, String moduleName, Collection<? extends Path> paths) 1003 throws IOException { 1004 nullCheck(location); 1005 checkModuleOrientedOrOutputLocation(location); 1006 locations.setLocationForModule(location, nullCheck(moduleName), nullCheck(paths)); 1007 } 1008 1009 @Override @DefinedBy(Api.COMPILER) 1010 public String inferModuleName(Location location) { 1011 checkNotModuleOrientedLocation(location); 1012 return locations.inferModuleName(location); 1013 } 1014 1015 @Override @DefinedBy(Api.COMPILER) 1016 public Iterable<Set<Location>> listLocationsForModules(Location location) throws IOException { 1017 checkModuleOrientedOrOutputLocation(location); 1018 return locations.listLocationsForModules(location); 1019 } 1020 1021 @Override @DefinedBy(Api.COMPILER) 1022 public Path asPath(FileObject file) { 1023 if (file instanceof PathFileObject) { 1024 return ((PathFileObject) file).path; 1025 } else 1026 throw new IllegalArgumentException(file.getName()); 1027 } 1028 1029 /** 1030 * Enforces the specification of a "relative" name as used in 1031 * {@linkplain #getFileForInput(Location,String,String) 1032 * getFileForInput}. This method must follow the rules defined in 1033 * that method, do not make any changes without consulting the 1034 * specification. 1035 */ 1036 protected static boolean isRelativeUri(URI uri) { 1037 if (uri.isAbsolute()) 1038 return false; 1039 String path = uri.normalize().getPath(); 1040 if (path.length() == 0 /* isEmpty() is mustang API */) 1041 return false; 1042 if (!path.equals(uri.getPath())) // implicitly checks for embedded . and .. 1043 return false; 1044 if (path.startsWith("/") || path.startsWith("./") || path.startsWith("../")) 1045 return false; 1046 return true; 1047 } 1048 1049 // Convenience method 1050 protected static boolean isRelativeUri(String u) { 1051 try { 1052 return isRelativeUri(new URI(u)); 1053 } catch (URISyntaxException e) { 1054 return false; 1055 } 1056 } 1057 1058 /** 1059 * Converts a relative file name to a relative URI. This is 1060 * different from File.toURI as this method does not canonicalize 1061 * the file before creating the URI. Furthermore, no schema is 1062 * used. 1063 * @param file a relative file name 1064 * @return a relative URI 1065 * @throws IllegalArgumentException if the file name is not 1066 * relative according to the definition given in {@link 1067 * javax.tools.JavaFileManager#getFileForInput} 1068 */ 1069 public static String getRelativeName(File file) { 1070 if (!file.isAbsolute()) { 1071 String result = file.getPath().replace(File.separatorChar, '/'); 1072 if (isRelativeUri(result)) 1073 return result; 1074 } 1075 throw new IllegalArgumentException("Invalid relative path: " + file); 1076 } 1077 1078 /** 1079 * Get a detail message from an IOException. 1080 * Most, but not all, instances of IOException provide a non-null result 1081 * for getLocalizedMessage(). But some instances return null: in these 1082 * cases, fallover to getMessage(), and if even that is null, return the 1083 * name of the exception itself. 1084 * @param e an IOException 1085 * @return a string to include in a compiler diagnostic 1086 */ 1087 public static String getMessage(IOException e) { 1088 String s = e.getLocalizedMessage(); 1089 if (s != null) 1090 return s; 1091 s = e.getMessage(); 1092 if (s != null) 1093 return s; 1094 return e.toString(); 1095 } 1096 1097 private void checkOutputLocation(Location location) { 1098 Objects.requireNonNull(location); 1099 if (!location.isOutputLocation()) 1100 throw new IllegalArgumentException("location is not an output location: " + location.getName()); 1101 } 1102 1103 private void checkModuleOrientedOrOutputLocation(Location location) { 1104 Objects.requireNonNull(location); 1105 if (!location.isModuleOrientedLocation() && !location.isOutputLocation()) 1106 throw new IllegalArgumentException( 1107 "location is not an output location or a module-oriented location: " 1108 + location.getName()); 1109 } 1110 1111 private void checkNotModuleOrientedLocation(Location location) { 1112 Objects.requireNonNull(location); 1113 if (location.isModuleOrientedLocation()) 1114 throw new IllegalArgumentException("location is module-oriented: " + location.getName()); 1115 } 1116 1117 /* Converters between files and paths. 1118 * These are temporary until we can update the StandardJavaFileManager API. 1119 */ 1120 1121 private static Iterable<Path> asPaths(final Iterable<? extends File> files) { 1122 if (files == null) 1123 return null; 1124 1125 return () -> new Iterator<Path>() { 1126 Iterator<? extends File> iter = files.iterator(); 1127 1128 @Override 1129 public boolean hasNext() { 1130 return iter.hasNext(); 1131 } 1132 1133 @Override 1134 public Path next() { 1135 return iter.next().toPath(); 1136 } 1137 }; 1138 } 1139 1140 private static Iterable<File> asFiles(final Iterable<? extends Path> paths) { 1141 if (paths == null) 1142 return null; 1143 1144 return () -> new Iterator<File>() { 1145 Iterator<? extends Path> iter = paths.iterator(); 1146 1147 @Override 1148 public boolean hasNext() { 1149 return iter.hasNext(); 1150 } 1151 1152 @Override 1153 public File next() { 1154 try { 1155 return iter.next().toFile(); 1156 } catch (UnsupportedOperationException e) { 1157 throw new IllegalStateException(e); 1158 } 1159 } 1160 }; 1161 } 1162} 1163