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