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