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