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