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