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