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