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