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