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