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 (fname.endsWith("/")) 335 fname = fname.substring(0, fname.length() - 1); 336 if (Files.isDirectory(f)) { 337 if (recurse && SourceVersion.isIdentifier(fname)) { 338 listDirectory(directory, 339 new RelativeDirectory(subdirectory, fname), 340 fileKinds, 341 recurse, 342 resultList); 343 } 344 } else { 345 if (isValidFile(fname, fileKinds)) { 346 JavaFileObject fe = 347 new RegularFileObject(this, fname, d.resolve(fname)); 348 resultList.append(fe); 349 } 350 } 351 } 352 } 353 354 /** 355 * Insert all files in subdirectory subdirectory of archive archive 356 * which match fileKinds into resultList 357 */ 358 private void listArchive(Archive archive, 359 RelativeDirectory subdirectory, 360 Set<JavaFileObject.Kind> fileKinds, 361 boolean recurse, 362 ListBuffer<JavaFileObject> resultList) { 363 // Get the files directly in the subdir 364 List<String> files = archive.getFiles(subdirectory); 365 if (files != null) { 366 for (; !files.isEmpty(); files = files.tail) { 367 String file = files.head; 368 if (isValidFile(file, fileKinds)) { 369 resultList.append(archive.getFileObject(subdirectory, file)); 370 } 371 } 372 } 373 if (recurse) { 374 for (RelativeDirectory s: archive.getSubdirectories()) { 375 if (subdirectory.contains(s)) { 376 // Because the archive map is a flat list of directories, 377 // the enclosing loop will pick up all child subdirectories. 378 // Therefore, there is no need to recurse deeper. 379 listArchive(archive, s, fileKinds, false, resultList); 380 } 381 } 382 } 383 } 384 385 /** 386 * container is a directory, a zip file, or a non-existant path. 387 * Insert all files in subdirectory subdirectory of container which 388 * match fileKinds into resultList 389 */ 390 private void listContainer(Path container, 391 RelativeDirectory subdirectory, 392 Set<JavaFileObject.Kind> fileKinds, 393 boolean recurse, 394 ListBuffer<JavaFileObject> resultList) { 395 Archive archive = archives.get(container); 396 if (archive == null) { 397 // Very temporary and obnoxious interim hack 398 if (container.endsWith("bootmodules.jimage")) { 399 System.err.println("Warning: reference to bootmodules.jimage replaced by jrt:"); 400 container = Locations.JRT_MARKER_FILE; 401 } else if (container.getFileName().toString().endsWith(".jimage")) { 402 System.err.println("Warning: reference to " + container + " ignored"); 403 return; 404 } 405 406 // archives are not created for directories or jrt: images 407 if (container == Locations.JRT_MARKER_FILE) { 408 try { 409 listJRTImage(subdirectory, 410 fileKinds, 411 recurse, 412 resultList); 413 } catch (IOException ex) { 414 ex.printStackTrace(System.err); 415 log.error("error.reading.file", container, getMessage(ex)); 416 } 417 return; 418 } 419 420 if (fsInfo.isDirectory(container)) { 421 listDirectory(container, 422 subdirectory, 423 fileKinds, 424 recurse, 425 resultList); 426 return; 427 } 428 429 // Not a directory; either a file or non-existant, create the archive 430 try { 431 archive = openArchive(container); 432 } catch (IOException ex) { 433 log.error("error.reading.file", container, getMessage(ex)); 434 return; 435 } 436 } 437 listArchive(archive, 438 subdirectory, 439 fileKinds, 440 recurse, 441 resultList); 442 } 443 444 private boolean isValidFile(String s, Set<JavaFileObject.Kind> fileKinds) { 445 JavaFileObject.Kind kind = getKind(s); 446 return fileKinds.contains(kind); 447 } 448 449 private static final boolean fileSystemIsCaseSensitive = 450 File.separatorChar == '/'; 451 452 /** Hack to make Windows case sensitive. Test whether given path 453 * ends in a string of characters with the same case as given name. 454 * Ignore file separators in both path and name. 455 */ 456 private boolean caseMapCheck(Path f, RelativePath name) { 457 if (fileSystemIsCaseSensitive) return true; 458 // Note that toRealPath() returns the case-sensitive 459 // spelled file name. 460 String path; 461 char sep; 462 try { 463 path = f.toRealPath(LinkOption.NOFOLLOW_LINKS).toString(); 464 sep = f.getFileSystem().getSeparator().charAt(0); 465 } catch (IOException ex) { 466 return false; 467 } 468 char[] pcs = path.toCharArray(); 469 char[] ncs = name.path.toCharArray(); 470 int i = pcs.length - 1; 471 int j = ncs.length - 1; 472 while (i >= 0 && j >= 0) { 473 while (i >= 0 && pcs[i] == sep) i--; 474 while (j >= 0 && ncs[j] == '/') j--; 475 if (i >= 0 && j >= 0) { 476 if (pcs[i] != ncs[j]) return false; 477 i--; 478 j--; 479 } 480 } 481 return j < 0; 482 } 483 484 /** 485 * An archive provides a flat directory structure of a ZipFile by 486 * mapping directory names to lists of files (basenames). 487 */ 488 public interface Archive { 489 void close() throws IOException; 490 491 boolean contains(RelativePath name); 492 493 JavaFileObject getFileObject(RelativeDirectory subdirectory, String file); 494 495 List<String> getFiles(RelativeDirectory subdirectory); 496 497 Set<RelativeDirectory> getSubdirectories(); 498 } 499 500 public class MissingArchive implements Archive { 501 final Path zipFileName; 502 public MissingArchive(Path name) { 503 zipFileName = name; 504 } 505 @Override 506 public boolean contains(RelativePath name) { 507 return false; 508 } 509 510 @Override 511 public void close() { 512 } 513 514 @Override 515 public JavaFileObject getFileObject(RelativeDirectory subdirectory, String file) { 516 return null; 517 } 518 519 @Override 520 public List<String> getFiles(RelativeDirectory subdirectory) { 521 return List.nil(); 522 } 523 524 @Override 525 public Set<RelativeDirectory> getSubdirectories() { 526 return Collections.emptySet(); 527 } 528 529 @Override 530 public String toString() { 531 return "MissingArchive[" + zipFileName + "]"; 532 } 533 } 534 535 /** A directory of zip files already opened. 536 */ 537 Map<Path, Archive> archives = new HashMap<>(); 538 539 /* 540 * This method looks for a ZipFormatException and takes appropriate 541 * evasive action. If there is a failure in the fast mode then we 542 * fail over to the platform zip, and allow it to deal with a potentially 543 * non compliant zip file. 544 */ 545 protected Archive openArchive(Path zipFilename) throws IOException { 546 try { 547 return openArchive(zipFilename, contextUseOptimizedZip); 548 } catch (IOException ioe) { 549 if (ioe instanceof ZipFileIndex.ZipFormatException) { 550 return openArchive(zipFilename, false); 551 } else { 552 throw ioe; 553 } 554 } 555 } 556 557 /** Open a new zip file directory, and cache it. 558 */ 559 private Archive openArchive(Path zipFileName, boolean useOptimizedZip) throws IOException { 560 Archive archive; 561 try { 562 563 ZipFile zdir = null; 564 565 boolean usePreindexedCache = false; 566 String preindexCacheLocation = null; 567 568 if (!useOptimizedZip) { 569 zdir = new ZipFile(zipFileName.toFile()); 570 } else { 571 usePreindexedCache = options.isSet("usezipindex"); 572 preindexCacheLocation = options.get("java.io.tmpdir"); 573 String optCacheLoc = options.get("cachezipindexdir"); 574 575 if (optCacheLoc != null && optCacheLoc.length() != 0) { 576 if (optCacheLoc.startsWith("\"")) { 577 if (optCacheLoc.endsWith("\"")) { 578 optCacheLoc = optCacheLoc.substring(1, optCacheLoc.length() - 1); 579 } 580 else { 581 optCacheLoc = optCacheLoc.substring(1); 582 } 583 } 584 585 File cacheDir = new File(optCacheLoc); 586 if (cacheDir.exists() && cacheDir.canWrite()) { 587 preindexCacheLocation = optCacheLoc; 588 if (!preindexCacheLocation.endsWith("/") && 589 !preindexCacheLocation.endsWith(File.separator)) { 590 preindexCacheLocation += File.separator; 591 } 592 } 593 } 594 } 595 596 if (!useOptimizedZip) { 597 archive = new ZipArchive(this, zdir); 598 } else { 599 archive = new ZipFileIndexArchive(this, 600 zipFileIndexCache.getZipFileIndex(zipFileName, 601 null, 602 usePreindexedCache, 603 preindexCacheLocation, 604 options.isSet("writezipindexfiles"))); 605 } 606 } catch (FileNotFoundException | NoSuchFileException ex) { 607 archive = new MissingArchive(zipFileName); 608 } catch (ZipFileIndex.ZipFormatException zfe) { 609 throw zfe; 610 } catch (IOException ex) { 611 if (Files.exists(zipFileName)) 612 log.error("error.reading.file", zipFileName, getMessage(ex)); 613 archive = new MissingArchive(zipFileName); 614 } 615 616 archives.put(zipFileName, archive); 617 return archive; 618 } 619 620 /** Flush any output resources. 621 */ 622 @Override @DefinedBy(Api.COMPILER) 623 public void flush() { 624 contentCache.clear(); 625 } 626 627 /** 628 * Close the JavaFileManager, releasing resources. 629 */ 630 @Override @DefinedBy(Api.COMPILER) 631 public void close() { 632 for (Iterator<Archive> i = archives.values().iterator(); i.hasNext(); ) { 633 Archive a = i.next(); 634 i.remove(); 635 try { 636 a.close(); 637 } catch (IOException ignore) { 638 } 639 } 640 } 641 642 @Override @DefinedBy(Api.COMPILER) 643 public ClassLoader getClassLoader(Location location) { 644 nullCheck(location); 645 Iterable<? extends File> path = getLocation(location); 646 if (path == null) 647 return null; 648 ListBuffer<URL> lb = new ListBuffer<>(); 649 for (File f: path) { 650 try { 651 lb.append(f.toURI().toURL()); 652 } catch (MalformedURLException e) { 653 throw new AssertionError(e); 654 } 655 } 656 657 return getClassLoader(lb.toArray(new URL[lb.size()])); 658 } 659 660 @Override @DefinedBy(Api.COMPILER) 661 public Iterable<JavaFileObject> list(Location location, 662 String packageName, 663 Set<JavaFileObject.Kind> kinds, 664 boolean recurse) 665 throws IOException 666 { 667 // validatePackageName(packageName); 668 nullCheck(packageName); 669 nullCheck(kinds); 670 671 Iterable<? extends Path> path = getLocationAsPaths(location); 672 if (path == null) 673 return List.nil(); 674 RelativeDirectory subdirectory = RelativeDirectory.forPackage(packageName); 675 ListBuffer<JavaFileObject> results = new ListBuffer<>(); 676 677 for (Path directory : path) 678 listContainer(directory, subdirectory, kinds, recurse, results); 679 return results.toList(); 680 } 681 682 @Override @DefinedBy(Api.COMPILER) 683 public String inferBinaryName(Location location, JavaFileObject file) { 684 Objects.requireNonNull(file); 685 Objects.requireNonNull(location); 686 // Need to match the path semantics of list(location, ...) 687 Iterable<? extends Path> path = getLocationAsPaths(location); 688 if (path == null) { 689 return null; 690 } 691 692 if (file instanceof BaseFileObject) { 693 return ((BaseFileObject) file).inferBinaryName(path); 694 } else if (file instanceof PathFileObject) { 695 return ((PathFileObject) file).inferBinaryName(null); 696 } else 697 throw new IllegalArgumentException(file.getClass().getName()); 698 } 699 700 @Override @DefinedBy(Api.COMPILER) 701 public boolean isSameFile(FileObject a, FileObject b) { 702 nullCheck(a); 703 nullCheck(b); 704 if (a instanceof PathFileObject && b instanceof PathFileObject) 705 return ((PathFileObject) a).isSameFile((PathFileObject) b); 706 // In time, we should phase out BaseFileObject in favor of PathFileObject 707 if (!(a instanceof BaseFileObject || a instanceof PathFileObject)) 708 throw new IllegalArgumentException("Not supported: " + a); 709 if (!(b instanceof BaseFileObject || b instanceof PathFileObject)) 710 throw new IllegalArgumentException("Not supported: " + b); 711 return a.equals(b); 712 } 713 714 @Override @DefinedBy(Api.COMPILER) 715 public boolean hasLocation(Location location) { 716 return getLocation(location) != null; 717 } 718 719 @Override @DefinedBy(Api.COMPILER) 720 public JavaFileObject getJavaFileForInput(Location location, 721 String className, 722 JavaFileObject.Kind kind) 723 throws IOException 724 { 725 nullCheck(location); 726 // validateClassName(className); 727 nullCheck(className); 728 nullCheck(kind); 729 if (!sourceOrClass.contains(kind)) 730 throw new IllegalArgumentException("Invalid kind: " + kind); 731 return getFileForInput(location, RelativeFile.forClass(className, kind)); 732 } 733 734 @Override @DefinedBy(Api.COMPILER) 735 public FileObject getFileForInput(Location location, 736 String packageName, 737 String relativeName) 738 throws IOException 739 { 740 nullCheck(location); 741 // validatePackageName(packageName); 742 nullCheck(packageName); 743 if (!isRelativeUri(relativeName)) 744 throw new IllegalArgumentException("Invalid relative name: " + relativeName); 745 RelativeFile name = packageName.length() == 0 746 ? new RelativeFile(relativeName) 747 : new RelativeFile(RelativeDirectory.forPackage(packageName), relativeName); 748 return getFileForInput(location, name); 749 } 750 751 private JavaFileObject getFileForInput(Location location, RelativeFile name) throws IOException { 752 Iterable<? extends Path> path = getLocationAsPaths(location); 753 if (path == null) 754 return null; 755 756 for (Path file: path) { 757 Archive a = archives.get(file); 758 if (a == null) { 759 // archives are not created for directories or jrt: images 760 if (file == Locations.JRT_MARKER_FILE) { 761 JRTIndex.Entry e = getJRTIndex().getEntry(name.dirname()); 762 if (symbolFileEnabled && e.ctSym.hidden) 763 continue; 764 Path p = e.files.get(name.basename()); 765 if (p != null) 766 return PathFileObject.createJRTPathFileObject(this, p); 767 continue; 768 } else if (fsInfo.isDirectory(file)) { 769 try { 770 Path f = name.getFile(file); 771 if (Files.exists(f)) 772 return new RegularFileObject(this, f); 773 } catch (InvalidPathException ignore) { 774 } 775 continue; 776 } 777 // Not a directory, create the archive 778 a = openArchive(file); 779 } 780 // Process the archive 781 if (a.contains(name)) { 782 return a.getFileObject(name.dirname(), name.basename()); 783 } 784 } 785 return null; 786 } 787 788 @Override @DefinedBy(Api.COMPILER) 789 public JavaFileObject getJavaFileForOutput(Location location, 790 String className, 791 JavaFileObject.Kind kind, 792 FileObject sibling) 793 throws IOException 794 { 795 nullCheck(location); 796 // validateClassName(className); 797 nullCheck(className); 798 nullCheck(kind); 799 if (!sourceOrClass.contains(kind)) 800 throw new IllegalArgumentException("Invalid kind: " + kind); 801 return getFileForOutput(location, RelativeFile.forClass(className, kind), sibling); 802 } 803 804 @Override @DefinedBy(Api.COMPILER) 805 public FileObject getFileForOutput(Location location, 806 String packageName, 807 String relativeName, 808 FileObject sibling) 809 throws IOException 810 { 811 nullCheck(location); 812 // validatePackageName(packageName); 813 nullCheck(packageName); 814 if (!isRelativeUri(relativeName)) 815 throw new IllegalArgumentException("Invalid relative name: " + relativeName); 816 RelativeFile name = packageName.length() == 0 817 ? new RelativeFile(relativeName) 818 : new RelativeFile(RelativeDirectory.forPackage(packageName), relativeName); 819 return getFileForOutput(location, name, sibling); 820 } 821 822 private JavaFileObject getFileForOutput(Location location, 823 RelativeFile fileName, 824 FileObject sibling) 825 throws IOException 826 { 827 Path dir; 828 if (location == CLASS_OUTPUT) { 829 if (getClassOutDir() != null) { 830 dir = getClassOutDir(); 831 } else { 832 Path siblingDir = null; 833 if (sibling != null && sibling instanceof RegularFileObject) { 834 siblingDir = ((RegularFileObject)sibling).file.getParent(); 835 } 836 if (siblingDir == null) 837 return new RegularFileObject(this, Paths.get(fileName.basename())); 838 else 839 return new RegularFileObject(this, siblingDir.resolve(fileName.basename())); 840 } 841 } else if (location == SOURCE_OUTPUT) { 842 dir = (getSourceOutDir() != null ? getSourceOutDir() : getClassOutDir()); 843 } else { 844 Iterable<? extends Path> path = locations.getLocation(location); 845 dir = null; 846 for (Path f: path) { 847 dir = f; 848 break; 849 } 850 } 851 852 try { 853 Path file = fileName.getFile(dir); // null-safe 854 return new RegularFileObject(this, file); 855 } catch (InvalidPathException e) { 856 throw new IOException("bad filename " + fileName, e); 857 } 858 } 859 860 @Override @DefinedBy(Api.COMPILER) 861 public Iterable<? extends JavaFileObject> getJavaFileObjectsFromFiles( 862 Iterable<? extends File> files) 863 { 864 ArrayList<RegularFileObject> result; 865 if (files instanceof Collection<?>) 866 result = new ArrayList<>(((Collection<?>)files).size()); 867 else 868 result = new ArrayList<>(); 869 for (File f: files) 870 result.add(new RegularFileObject(this, nullCheck(f).toPath())); 871 return result; 872 } 873 874 @Override @DefinedBy(Api.COMPILER) 875 public Iterable<? extends JavaFileObject> getJavaFileObjectsFromPaths( 876 Iterable<? extends Path> paths) 877 { 878 ArrayList<RegularFileObject> result; 879 if (paths instanceof Collection<?>) 880 result = new ArrayList<>(((Collection<?>)paths).size()); 881 else 882 result = new ArrayList<>(); 883 for (Path p: paths) 884 result.add(new RegularFileObject(this, nullCheck(p))); 885 return result; 886 } 887 888 @Override @DefinedBy(Api.COMPILER) 889 public Iterable<? extends JavaFileObject> getJavaFileObjects(File... files) { 890 return getJavaFileObjectsFromFiles(Arrays.asList(nullCheck(files))); 891 } 892 893 @Override @DefinedBy(Api.COMPILER) 894 public Iterable<? extends JavaFileObject> getJavaFileObjects(Path... paths) { 895 return getJavaFileObjectsFromPaths(Arrays.asList(nullCheck(paths))); 896 } 897 898 @Override @DefinedBy(Api.COMPILER) 899 public void setLocation(Location location, 900 Iterable<? extends File> searchpath) 901 throws IOException 902 { 903 nullCheck(location); 904 locations.setLocation(location, asPaths(searchpath)); 905 } 906 907 @Override @DefinedBy(Api.COMPILER) 908 public void setLocationFromPaths(Location location, 909 Iterable<? extends Path> searchpath) 910 throws IOException 911 { 912 nullCheck(location); 913 locations.setLocation(location, nullCheck(searchpath)); 914 } 915 916 @Override @DefinedBy(Api.COMPILER) 917 public Iterable<? extends File> getLocation(Location location) { 918 nullCheck(location); 919 return asFiles(locations.getLocation(location)); 920 } 921 922 @Override @DefinedBy(Api.COMPILER) 923 public Iterable<? extends Path> getLocationAsPaths(Location location) { 924 nullCheck(location); 925 return locations.getLocation(location); 926 } 927 928 private Path getClassOutDir() { 929 return locations.getOutputLocation(CLASS_OUTPUT); 930 } 931 932 private Path getSourceOutDir() { 933 return locations.getOutputLocation(SOURCE_OUTPUT); 934 } 935 936 @Override @DefinedBy(Api.COMPILER) 937 public Path asPath(FileObject file) { 938 if (file instanceof RegularFileObject) { 939 return ((RegularFileObject) file).file; 940 } else 941 throw new IllegalArgumentException(file.getName()); 942 } 943 944 /** 945 * Enforces the specification of a "relative" name as used in 946 * {@linkplain #getFileForInput(Location,String,String) 947 * getFileForInput}. This method must follow the rules defined in 948 * that method, do not make any changes without consulting the 949 * specification. 950 */ 951 protected static boolean isRelativeUri(URI uri) { 952 if (uri.isAbsolute()) 953 return false; 954 String path = uri.normalize().getPath(); 955 if (path.length() == 0 /* isEmpty() is mustang API */) 956 return false; 957 if (!path.equals(uri.getPath())) // implicitly checks for embedded . and .. 958 return false; 959 if (path.startsWith("/") || path.startsWith("./") || path.startsWith("../")) 960 return false; 961 return true; 962 } 963 964 // Convenience method 965 protected static boolean isRelativeUri(String u) { 966 try { 967 return isRelativeUri(new URI(u)); 968 } catch (URISyntaxException e) { 969 return false; 970 } 971 } 972 973 /** 974 * Converts a relative file name to a relative URI. This is 975 * different from File.toURI as this method does not canonicalize 976 * the file before creating the URI. Furthermore, no schema is 977 * used. 978 * @param file a relative file name 979 * @return a relative URI 980 * @throws IllegalArgumentException if the file name is not 981 * relative according to the definition given in {@link 982 * javax.tools.JavaFileManager#getFileForInput} 983 */ 984 public static String getRelativeName(File file) { 985 if (!file.isAbsolute()) { 986 String result = file.getPath().replace(File.separatorChar, '/'); 987 if (isRelativeUri(result)) 988 return result; 989 } 990 throw new IllegalArgumentException("Invalid relative path: " + file); 991 } 992 993 /** 994 * Get a detail message from an IOException. 995 * Most, but not all, instances of IOException provide a non-null result 996 * for getLocalizedMessage(). But some instances return null: in these 997 * cases, fallover to getMessage(), and if even that is null, return the 998 * name of the exception itself. 999 * @param e an IOException 1000 * @return a string to include in a compiler diagnostic 1001 */ 1002 public static String getMessage(IOException e) { 1003 String s = e.getLocalizedMessage(); 1004 if (s != null) 1005 return s; 1006 s = e.getMessage(); 1007 if (s != null) 1008 return s; 1009 return e.toString(); 1010 } 1011 1012 /* Converters between files and paths. 1013 * These are temporary until we can update the StandardJavaFileManager API. 1014 */ 1015 1016 private static Iterable<Path> asPaths(final Iterable<? extends File> files) { 1017 if (files == null) 1018 return null; 1019 1020 return () -> new Iterator<Path>() { 1021 Iterator<? extends File> iter = files.iterator(); 1022 1023 @Override 1024 public boolean hasNext() { 1025 return iter.hasNext(); 1026 } 1027 1028 @Override 1029 public Path next() { 1030 return iter.next().toPath(); 1031 } 1032 }; 1033 } 1034 1035 private static Iterable<File> asFiles(final Iterable<? extends Path> paths) { 1036 if (paths == null) 1037 return null; 1038 1039 return () -> new Iterator<File>() { 1040 Iterator<? extends Path> iter = paths.iterator(); 1041 1042 @Override 1043 public boolean hasNext() { 1044 return iter.hasNext(); 1045 } 1046 1047 @Override 1048 public File next() { 1049 try { 1050 return iter.next().toFile(); 1051 } catch (UnsupportedOperationException e) { 1052 throw new IllegalStateException(e); 1053 } 1054 } 1055 }; 1056 } 1057 }