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