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