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