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