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