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