JavacFileManager.java revision 3262:21d9e172e9f6
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.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.getNameCount() > 0 && 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 if (deferredCloseTimeout > 0) { 529 deferredClose(); 530 return; 531 } 532 533 for (FileSystem fs: fileSystems.values()) { 534 fs.close(); 535 } 536 fileSystems.clear(); 537 contentCache.clear(); 538 } 539 540 @Override @DefinedBy(Api.COMPILER) 541 public ClassLoader getClassLoader(Location location) { 542 nullCheck(location); 543 Iterable<? extends File> path = getLocation(location); 544 if (path == null) 545 return null; 546 ListBuffer<URL> lb = new ListBuffer<>(); 547 for (File f: path) { 548 try { 549 lb.append(f.toURI().toURL()); 550 } catch (MalformedURLException e) { 551 throw new AssertionError(e); 552 } 553 } 554 555 return getClassLoader(lb.toArray(new URL[lb.size()])); 556 } 557 558 @Override @DefinedBy(Api.COMPILER) 559 public Iterable<JavaFileObject> list(Location location, 560 String packageName, 561 Set<JavaFileObject.Kind> kinds, 562 boolean recurse) 563 throws IOException 564 { 565 // validatePackageName(packageName); 566 nullCheck(packageName); 567 nullCheck(kinds); 568 569 Iterable<? extends Path> path = getLocationAsPaths(location); 570 if (path == null) 571 return List.nil(); 572 RelativeDirectory subdirectory = RelativeDirectory.forPackage(packageName); 573 ListBuffer<JavaFileObject> results = new ListBuffer<>(); 574 575 for (Path directory : path) 576 listContainer(directory, subdirectory, kinds, recurse, results); 577 return results.toList(); 578 } 579 580 @Override @DefinedBy(Api.COMPILER) 581 public String inferBinaryName(Location location, JavaFileObject file) { 582 Objects.requireNonNull(file); 583 Objects.requireNonNull(location); 584 // Need to match the path semantics of list(location, ...) 585 Iterable<? extends Path> path = getLocationAsPaths(location); 586 if (path == null) { 587 return null; 588 } 589 590 if (file instanceof PathFileObject) { 591 return ((PathFileObject) file).inferBinaryName(path); 592 } else 593 throw new IllegalArgumentException(file.getClass().getName()); 594 } 595 596 @Override @DefinedBy(Api.COMPILER) 597 public boolean isSameFile(FileObject a, FileObject b) { 598 nullCheck(a); 599 nullCheck(b); 600 if (a instanceof PathFileObject && b instanceof PathFileObject) 601 return ((PathFileObject) a).isSameFile((PathFileObject) b); 602 return a.equals(b); 603 } 604 605 @Override @DefinedBy(Api.COMPILER) 606 public boolean hasLocation(Location location) { 607 return getLocation(location) != null; 608 } 609 610 @Override @DefinedBy(Api.COMPILER) 611 public JavaFileObject getJavaFileForInput(Location location, 612 String className, 613 JavaFileObject.Kind kind) 614 throws IOException 615 { 616 nullCheck(location); 617 // validateClassName(className); 618 nullCheck(className); 619 nullCheck(kind); 620 if (!sourceOrClass.contains(kind)) 621 throw new IllegalArgumentException("Invalid kind: " + kind); 622 return getFileForInput(location, RelativeFile.forClass(className, kind)); 623 } 624 625 @Override @DefinedBy(Api.COMPILER) 626 public FileObject getFileForInput(Location location, 627 String packageName, 628 String relativeName) 629 throws IOException 630 { 631 nullCheck(location); 632 // validatePackageName(packageName); 633 nullCheck(packageName); 634 if (!isRelativeUri(relativeName)) 635 throw new IllegalArgumentException("Invalid relative name: " + relativeName); 636 RelativeFile name = packageName.length() == 0 637 ? new RelativeFile(relativeName) 638 : new RelativeFile(RelativeDirectory.forPackage(packageName), relativeName); 639 return getFileForInput(location, name); 640 } 641 642 private JavaFileObject getFileForInput(Location location, RelativeFile name) throws IOException { 643 Iterable<? extends Path> path = getLocationAsPaths(location); 644 if (path == null) 645 return null; 646 647 for (Path file: path) { 648 if (file == Locations.JRT_MARKER_FILE) { 649 JRTIndex.Entry e = getJRTIndex().getEntry(name.dirname()); 650 if (symbolFileEnabled && e.ctSym.hidden) 651 continue; 652 Path p = e.files.get(name.basename()); 653 if (p != null) 654 return PathFileObject.forJRTPath(this, p); 655 } else if (fsInfo.isDirectory(file)) { 656 try { 657 Path f = name.resolveAgainst(file); 658 if (Files.exists(f)) 659 return PathFileObject.forSimplePath(this, 660 fsInfo.getCanonicalFile(f), f); 661 } catch (InvalidPathException ignore) { 662 } 663 } else if (Files.exists(file)) { 664 FileSystem fs = getFileSystem(file); 665 if (fs != null) { 666 Path fsRoot = fs.getRootDirectories().iterator().next(); 667 Path f = name.resolveAgainst(fsRoot); 668 if (Files.exists(f)) 669 return PathFileObject.forJarPath(this, f, file); 670 } 671 } 672 } 673 return null; 674 } 675 676 @Override @DefinedBy(Api.COMPILER) 677 public JavaFileObject getJavaFileForOutput(Location location, 678 String className, 679 JavaFileObject.Kind kind, 680 FileObject sibling) 681 throws IOException 682 { 683 nullCheck(location); 684 // validateClassName(className); 685 nullCheck(className); 686 nullCheck(kind); 687 if (!sourceOrClass.contains(kind)) 688 throw new IllegalArgumentException("Invalid kind: " + kind); 689 return getFileForOutput(location, RelativeFile.forClass(className, kind), sibling); 690 } 691 692 @Override @DefinedBy(Api.COMPILER) 693 public FileObject getFileForOutput(Location location, 694 String packageName, 695 String relativeName, 696 FileObject sibling) 697 throws IOException 698 { 699 nullCheck(location); 700 // validatePackageName(packageName); 701 nullCheck(packageName); 702 if (!isRelativeUri(relativeName)) 703 throw new IllegalArgumentException("Invalid relative name: " + relativeName); 704 RelativeFile name = packageName.length() == 0 705 ? new RelativeFile(relativeName) 706 : new RelativeFile(RelativeDirectory.forPackage(packageName), relativeName); 707 return getFileForOutput(location, name, sibling); 708 } 709 710 private JavaFileObject getFileForOutput(Location location, 711 RelativeFile fileName, 712 FileObject sibling) 713 throws IOException 714 { 715 Path dir; 716 if (location == CLASS_OUTPUT) { 717 if (getClassOutDir() != null) { 718 dir = getClassOutDir(); 719 } else { 720 String baseName = fileName.basename(); 721 if (sibling != null && sibling instanceof PathFileObject) { 722 return ((PathFileObject) sibling).getSibling(baseName); 723 } else { 724 Path p = Paths.get(baseName); 725 Path real = fsInfo.getCanonicalFile(p); 726 return PathFileObject.forSimplePath(this, real, p); 727 } 728 } 729 } else if (location == SOURCE_OUTPUT) { 730 dir = (getSourceOutDir() != null ? getSourceOutDir() : getClassOutDir()); 731 } else { 732 Iterable<? extends Path> path = locations.getLocation(location); 733 dir = null; 734 for (Path f: path) { 735 dir = f; 736 break; 737 } 738 } 739 740 try { 741 if (dir == null) { 742 dir = Paths.get(System.getProperty("user.dir")); 743 } 744 Path path = fileName.resolveAgainst(fsInfo.getCanonicalFile(dir)); 745 return PathFileObject.forDirectoryPath(this, path, dir, fileName); 746 } catch (InvalidPathException e) { 747 throw new IOException("bad filename " + fileName, e); 748 } 749 } 750 751 @Override @DefinedBy(Api.COMPILER) 752 public Iterable<? extends JavaFileObject> getJavaFileObjectsFromFiles( 753 Iterable<? extends File> files) 754 { 755 ArrayList<PathFileObject> result; 756 if (files instanceof Collection<?>) 757 result = new ArrayList<>(((Collection<?>)files).size()); 758 else 759 result = new ArrayList<>(); 760 for (File f: files) { 761 Objects.requireNonNull(f); 762 Path p = f.toPath(); 763 result.add(PathFileObject.forSimplePath(this, 764 fsInfo.getCanonicalFile(p), p)); 765 } 766 return result; 767 } 768 769 @Override @DefinedBy(Api.COMPILER) 770 public Iterable<? extends JavaFileObject> getJavaFileObjectsFromPaths( 771 Iterable<? extends Path> paths) 772 { 773 ArrayList<PathFileObject> result; 774 if (paths instanceof Collection<?>) 775 result = new ArrayList<>(((Collection<?>)paths).size()); 776 else 777 result = new ArrayList<>(); 778 for (Path p: paths) 779 result.add(PathFileObject.forSimplePath(this, 780 fsInfo.getCanonicalFile(p), p)); 781 return result; 782 } 783 784 @Override @DefinedBy(Api.COMPILER) 785 public Iterable<? extends JavaFileObject> getJavaFileObjects(File... files) { 786 return getJavaFileObjectsFromFiles(Arrays.asList(nullCheck(files))); 787 } 788 789 @Override @DefinedBy(Api.COMPILER) 790 public Iterable<? extends JavaFileObject> getJavaFileObjects(Path... paths) { 791 return getJavaFileObjectsFromPaths(Arrays.asList(nullCheck(paths))); 792 } 793 794 @Override @DefinedBy(Api.COMPILER) 795 public void setLocation(Location location, 796 Iterable<? extends File> searchpath) 797 throws IOException 798 { 799 nullCheck(location); 800 locations.setLocation(location, asPaths(searchpath)); 801 } 802 803 @Override @DefinedBy(Api.COMPILER) 804 public void setLocationFromPaths(Location location, 805 Iterable<? extends Path> searchpath) 806 throws IOException 807 { 808 nullCheck(location); 809 locations.setLocation(location, nullCheck(searchpath)); 810 } 811 812 @Override @DefinedBy(Api.COMPILER) 813 public Iterable<? extends File> getLocation(Location location) { 814 nullCheck(location); 815 return asFiles(locations.getLocation(location)); 816 } 817 818 @Override @DefinedBy(Api.COMPILER) 819 public Iterable<? extends Path> getLocationAsPaths(Location location) { 820 nullCheck(location); 821 return locations.getLocation(location); 822 } 823 824 private Path getClassOutDir() { 825 return locations.getOutputLocation(CLASS_OUTPUT); 826 } 827 828 private Path getSourceOutDir() { 829 return locations.getOutputLocation(SOURCE_OUTPUT); 830 } 831 832 @Override @DefinedBy(Api.COMPILER) 833 public Path asPath(FileObject file) { 834 if (file instanceof PathFileObject) { 835 return ((PathFileObject) file).path; 836 } else 837 throw new IllegalArgumentException(file.getName()); 838 } 839 840 /** 841 * Enforces the specification of a "relative" name as used in 842 * {@linkplain #getFileForInput(Location,String,String) 843 * getFileForInput}. This method must follow the rules defined in 844 * that method, do not make any changes without consulting the 845 * specification. 846 */ 847 protected static boolean isRelativeUri(URI uri) { 848 if (uri.isAbsolute()) 849 return false; 850 String path = uri.normalize().getPath(); 851 if (path.length() == 0 /* isEmpty() is mustang API */) 852 return false; 853 if (!path.equals(uri.getPath())) // implicitly checks for embedded . and .. 854 return false; 855 if (path.startsWith("/") || path.startsWith("./") || path.startsWith("../")) 856 return false; 857 return true; 858 } 859 860 // Convenience method 861 protected static boolean isRelativeUri(String u) { 862 try { 863 return isRelativeUri(new URI(u)); 864 } catch (URISyntaxException e) { 865 return false; 866 } 867 } 868 869 /** 870 * Converts a relative file name to a relative URI. This is 871 * different from File.toURI as this method does not canonicalize 872 * the file before creating the URI. Furthermore, no schema is 873 * used. 874 * @param file a relative file name 875 * @return a relative URI 876 * @throws IllegalArgumentException if the file name is not 877 * relative according to the definition given in {@link 878 * javax.tools.JavaFileManager#getFileForInput} 879 */ 880 public static String getRelativeName(File file) { 881 if (!file.isAbsolute()) { 882 String result = file.getPath().replace(File.separatorChar, '/'); 883 if (isRelativeUri(result)) 884 return result; 885 } 886 throw new IllegalArgumentException("Invalid relative path: " + file); 887 } 888 889 /** 890 * Get a detail message from an IOException. 891 * Most, but not all, instances of IOException provide a non-null result 892 * for getLocalizedMessage(). But some instances return null: in these 893 * cases, fallover to getMessage(), and if even that is null, return the 894 * name of the exception itself. 895 * @param e an IOException 896 * @return a string to include in a compiler diagnostic 897 */ 898 public static String getMessage(IOException e) { 899 String s = e.getLocalizedMessage(); 900 if (s != null) 901 return s; 902 s = e.getMessage(); 903 if (s != null) 904 return s; 905 return e.toString(); 906 } 907 908 /* Converters between files and paths. 909 * These are temporary until we can update the StandardJavaFileManager API. 910 */ 911 912 private static Iterable<Path> asPaths(final Iterable<? extends File> files) { 913 if (files == null) 914 return null; 915 916 return () -> new Iterator<Path>() { 917 Iterator<? extends File> iter = files.iterator(); 918 919 @Override 920 public boolean hasNext() { 921 return iter.hasNext(); 922 } 923 924 @Override 925 public Path next() { 926 return iter.next().toPath(); 927 } 928 }; 929 } 930 931 private static Iterable<File> asFiles(final Iterable<? extends Path> paths) { 932 if (paths == null) 933 return null; 934 935 return () -> new Iterator<File>() { 936 Iterator<? extends Path> iter = paths.iterator(); 937 938 @Override 939 public boolean hasNext() { 940 return iter.hasNext(); 941 } 942 943 @Override 944 public File next() { 945 try { 946 return iter.next().toFile(); 947 } catch (UnsupportedOperationException e) { 948 throw new IllegalStateException(e); 949 } 950 } 951 }; 952 } 953} 954