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