1 /* 2 * Copyright (c) 2005, 2016, 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.IOException; 30 import java.net.MalformedURLException; 31 import java.net.URI; 32 import java.net.URISyntaxException; 33 import java.net.URL; 34 import java.nio.CharBuffer; 35 import java.nio.charset.Charset; 36 import java.nio.file.FileSystem; 37 import java.nio.file.FileSystems; 38 import java.nio.file.FileVisitOption; 39 import java.nio.file.FileVisitResult; 40 import java.nio.file.Files; 41 import java.nio.file.InvalidPathException; 42 import java.nio.file.LinkOption; 43 import java.nio.file.Path; 44 import java.nio.file.Paths; 45 import java.nio.file.ProviderNotFoundException; 46 import java.nio.file.SimpleFileVisitor; 47 import java.nio.file.attribute.BasicFileAttributes; 48 import java.nio.file.spi.FileSystemProvider; 49 import java.util.ArrayList; 50 import java.util.Arrays; 51 import java.util.Collection; 52 import java.util.Collections; 53 import java.util.Comparator; 54 import java.util.EnumSet; 55 import java.util.HashMap; 56 import java.util.Iterator; 57 import java.util.Map; 58 import java.util.Objects; 59 import java.util.ServiceLoader; 60 import java.util.Set; 61 import java.util.stream.Collectors; 62 import java.util.stream.Stream; 63 64 import javax.lang.model.SourceVersion; 65 import javax.tools.FileObject; 66 import javax.tools.JavaFileManager; 67 import javax.tools.JavaFileObject; 68 import javax.tools.StandardJavaFileManager; 69 70 import com.sun.tools.javac.file.RelativePath.RelativeDirectory; 71 import com.sun.tools.javac.file.RelativePath.RelativeFile; 72 import com.sun.tools.javac.util.Assert; 73 import com.sun.tools.javac.util.Context; 74 import com.sun.tools.javac.util.Context.Factory; 75 import com.sun.tools.javac.util.DefinedBy; 76 import com.sun.tools.javac.util.DefinedBy.Api; 77 import com.sun.tools.javac.util.List; 78 import com.sun.tools.javac.util.ListBuffer; 79 import com.sun.tools.javac.util.JDK9Wrappers.Configuration; 80 import com.sun.tools.javac.util.JDK9Wrappers.Layer; 81 import com.sun.tools.javac.util.JDK9Wrappers.ModuleFinder; 82 import com.sun.tools.javac.util.JDK9Wrappers.Module; 83 import com.sun.tools.javac.util.JDK9Wrappers.ServiceLoaderHelper; 84 85 import static java.nio.file.FileVisitOption.FOLLOW_LINKS; 86 87 import static javax.tools.StandardLocation.*; 88 89 /** 90 * This class provides access to the source, class and other files 91 * used by the compiler and related tools. 92 * 93 * <p><b>This is NOT part of any supported API. 94 * If you write code that depends on this, you do so at your own risk. 95 * This code and its internal interfaces are subject to change or 96 * deletion without notice.</b> 97 */ 98 public class JavacFileManager extends BaseFileManager implements StandardJavaFileManager { 99 100 @SuppressWarnings("cast") 101 public static char[] toArray(CharBuffer buffer) { 102 if (buffer.hasArray()) 103 return ((CharBuffer)buffer.compact().flip()).array(); 104 else 105 return buffer.toString().toCharArray(); 106 } 107 108 private FSInfo fsInfo; 109 110 private final Set<JavaFileObject.Kind> sourceOrClass = 111 EnumSet.of(JavaFileObject.Kind.SOURCE, JavaFileObject.Kind.CLASS); 112 113 protected boolean symbolFileEnabled; 114 115 private PathFactory pathFactory = Paths::get; 116 117 protected enum SortFiles implements Comparator<Path> { 118 FORWARD { 119 @Override 120 public int compare(Path f1, Path f2) { 121 return f1.getFileName().compareTo(f2.getFileName()); 122 } 123 }, 124 REVERSE { 125 @Override 126 public int compare(Path f1, Path f2) { 127 return -f1.getFileName().compareTo(f2.getFileName()); 128 } 129 } 130 } 131 132 protected SortFiles sortFiles; 133 134 /** 135 * Register a Context.Factory to create a JavacFileManager. 136 */ 137 public static void preRegister(Context context) { 138 context.put(JavaFileManager.class, 139 (Factory<JavaFileManager>)c -> new JavacFileManager(c, true, null)); 140 } 141 142 /** 143 * Create a JavacFileManager using a given context, optionally registering 144 * it as the JavaFileManager for that context. 145 */ 146 public JavacFileManager(Context context, boolean register, Charset charset) { 147 super(charset); 148 if (register) 149 context.put(JavaFileManager.class, this); 150 setContext(context); 151 } 152 153 /** 154 * Set the context for JavacFileManager. 155 */ 156 @Override 157 public void setContext(Context context) { 158 super.setContext(context); 159 160 fsInfo = FSInfo.instance(context); 161 162 symbolFileEnabled = !options.isSet("ignore.symbol.file"); 163 164 String sf = options.get("sortFiles"); 165 if (sf != null) { 166 sortFiles = (sf.equals("reverse") ? SortFiles.REVERSE : SortFiles.FORWARD); 167 } 168 } 169 170 @Override @DefinedBy(DefinedBy.Api.COMPILER) 171 public void setPathFactory(PathFactory f) { 172 pathFactory = Objects.requireNonNull(f); 173 locations.setPathFactory(f); 174 } 175 176 private Path getPath(String first, String... more) { 177 return pathFactory.getPath(first, more); 178 } 179 180 /** 181 * Set whether or not to use ct.sym as an alternate to rt.jar. 182 */ 183 public void setSymbolFileEnabled(boolean b) { 184 symbolFileEnabled = b; 185 } 186 187 public boolean isSymbolFileEnabled() { 188 return symbolFileEnabled; 189 } 190 191 // used by tests 192 public JavaFileObject getJavaFileObject(String name) { 193 return getJavaFileObjects(name).iterator().next(); 194 } 195 196 // used by tests 197 public JavaFileObject getJavaFileObject(Path file) { 198 return getJavaFileObjects(file).iterator().next(); 199 } 200 201 public JavaFileObject getFileForOutput(String classname, 202 JavaFileObject.Kind kind, 203 JavaFileObject sibling) 204 throws IOException 205 { 206 return getJavaFileForOutput(CLASS_OUTPUT, classname, kind, sibling); 207 } 208 209 @Override @DefinedBy(Api.COMPILER) 210 public Iterable<? extends JavaFileObject> getJavaFileObjectsFromStrings(Iterable<String> names) { 211 ListBuffer<Path> paths = new ListBuffer<>(); 212 for (String name : names) 213 paths.append(getPath(nullCheck(name))); 214 return getJavaFileObjectsFromPaths(paths.toList()); 215 } 216 217 @Override @DefinedBy(Api.COMPILER) 218 public Iterable<? extends JavaFileObject> getJavaFileObjects(String... names) { 219 return getJavaFileObjectsFromStrings(Arrays.asList(nullCheck(names))); 220 } 221 222 private static boolean isValidName(String name) { 223 // Arguably, isValidName should reject keywords (such as in SourceVersion.isName() ), 224 // but the set of keywords depends on the source level, and we don't want 225 // impls of JavaFileManager to have to be dependent on the source level. 226 // Therefore we simply check that the argument is a sequence of identifiers 227 // separated by ".". 228 for (String s : name.split("\\.", -1)) { 229 if (!SourceVersion.isIdentifier(s)) 230 return false; 231 } 232 return true; 233 } 234 235 private static void validateClassName(String className) { 236 if (!isValidName(className)) 237 throw new IllegalArgumentException("Invalid class name: " + className); 238 } 239 240 private static void validatePackageName(String packageName) { 241 if (packageName.length() > 0 && !isValidName(packageName)) 242 throw new IllegalArgumentException("Invalid packageName name: " + packageName); 243 } 244 245 public static void testName(String name, 246 boolean isValidPackageName, 247 boolean isValidClassName) 248 { 249 try { 250 validatePackageName(name); 251 if (!isValidPackageName) 252 throw new AssertionError("Invalid package name accepted: " + name); 253 printAscii("Valid package name: \"%s\"", name); 254 } catch (IllegalArgumentException e) { 255 if (isValidPackageName) 256 throw new AssertionError("Valid package name rejected: " + name); 257 printAscii("Invalid package name: \"%s\"", name); 258 } 259 try { 260 validateClassName(name); 261 if (!isValidClassName) 262 throw new AssertionError("Invalid class name accepted: " + name); 263 printAscii("Valid class name: \"%s\"", name); 264 } catch (IllegalArgumentException e) { 265 if (isValidClassName) 266 throw new AssertionError("Valid class name rejected: " + name); 267 printAscii("Invalid class name: \"%s\"", name); 268 } 269 } 270 271 private static void printAscii(String format, Object... args) { 272 String message; 273 try { 274 final String ascii = "US-ASCII"; 275 message = new String(String.format(null, format, args).getBytes(ascii), ascii); 276 } catch (java.io.UnsupportedEncodingException ex) { 277 throw new AssertionError(ex); 278 } 279 System.out.println(message); 280 } 281 282 private final Map<Path, Container> containers = new HashMap<>(); 283 284 synchronized Container getContainer(Path path) throws IOException { 285 Container fs = containers.get(path); 286 287 if (fs != null) { 288 return fs; 289 } 290 291 if (fsInfo.isFile(path) && path.equals(Locations.thisSystemModules)) { 292 containers.put(path, fs = new JRTImageContainer()); 293 return fs; 294 } 295 296 Path realPath = fsInfo.getCanonicalFile(path); 297 298 fs = containers.get(realPath); 299 300 if (fs != null) { 301 containers.put(path, fs); 302 return fs; 303 } 304 305 BasicFileAttributes attr = null; 306 307 try { 308 attr = Files.readAttributes(realPath, BasicFileAttributes.class); 309 } catch (IOException ex) { 310 //non-existing 311 fs = MISSING_CONTAINER; 312 } 313 314 if (attr != null) { 315 if (attr.isDirectory()) { 316 fs = new DirectoryContainer(realPath); 317 } else { 318 try { 319 fs = new ArchiveContainer(realPath); 320 } catch (ProviderNotFoundException | SecurityException ex) { 321 throw new IOException(ex); 322 } 323 } 324 } 325 326 containers.put(realPath, fs); 327 containers.put(path, fs); 328 329 return fs; 330 } 331 332 private interface Container { 333 /** 334 * Insert all files in subdirectory subdirectory of container which 335 * match fileKinds into resultList 336 */ 337 public abstract void list(Path userPath, 338 RelativeDirectory subdirectory, 339 Set<JavaFileObject.Kind> fileKinds, 340 boolean recurse, 341 ListBuffer<JavaFileObject> resultList) throws IOException; 342 public abstract JavaFileObject getFileObject(Path userPath, RelativeFile name) throws IOException; 343 public abstract void close() throws IOException; 344 } 345 346 private static final Container MISSING_CONTAINER = new Container() { 347 @Override 348 public void list(Path userPath, 349 RelativeDirectory subdirectory, 350 Set<JavaFileObject.Kind> fileKinds, 351 boolean recurse, 352 ListBuffer<JavaFileObject> resultList) throws IOException { 353 } 354 @Override 355 public JavaFileObject getFileObject(Path userPath, RelativeFile name) throws IOException { 356 return null; 357 } 358 @Override 359 public void close() throws IOException {} 360 }; 361 362 private final class JRTImageContainer implements Container { 363 364 /** 365 * Insert all files in a subdirectory of the platform image 366 * which match fileKinds into resultList. 367 */ 368 @Override 369 public void list(Path userPath, 370 RelativeDirectory subdirectory, 371 Set<JavaFileObject.Kind> fileKinds, 372 boolean recurse, 373 ListBuffer<JavaFileObject> resultList) throws IOException { 374 try { 375 JRTIndex.Entry e = getJRTIndex().getEntry(subdirectory); 376 if (symbolFileEnabled && e.ctSym.hidden) 377 return; 378 for (Path file: e.files.values()) { 379 if (fileKinds.contains(getKind(file))) { 380 JavaFileObject fe 381 = PathFileObject.forJRTPath(JavacFileManager.this, file); 382 resultList.append(fe); 383 } 384 } 385 386 if (recurse) { 387 for (RelativeDirectory rd: e.subdirs) { 388 list(userPath, rd, fileKinds, recurse, resultList); 389 } 390 } 391 } catch (IOException ex) { 392 ex.printStackTrace(System.err); 393 log.error("error.reading.file", userPath, getMessage(ex)); 394 } 395 } 396 397 @Override 398 public JavaFileObject getFileObject(Path userPath, RelativeFile name) throws IOException { 399 JRTIndex.Entry e = getJRTIndex().getEntry(name.dirname()); 400 if (symbolFileEnabled && e.ctSym.hidden) 401 return null; 402 Path p = e.files.get(name.basename()); 403 if (p != null) { 404 return PathFileObject.forJRTPath(JavacFileManager.this, p); 405 } else { 406 return null; 407 } 408 } 409 410 @Override 411 public void close() throws IOException { 412 } 413 } 414 415 private synchronized JRTIndex getJRTIndex() { 416 if (jrtIndex == null) 417 jrtIndex = JRTIndex.getSharedInstance(); 418 return jrtIndex; 419 } 420 421 private JRTIndex jrtIndex; 422 423 private final class DirectoryContainer implements Container { 424 private final Path directory; 425 426 public DirectoryContainer(Path directory) { 427 this.directory = directory; 428 } 429 430 /** 431 * Insert all files in subdirectory subdirectory of directory userPath 432 * which match fileKinds into resultList 433 */ 434 @Override 435 public void list(Path userPath, 436 RelativeDirectory subdirectory, 437 Set<JavaFileObject.Kind> fileKinds, 438 boolean recurse, 439 ListBuffer<JavaFileObject> resultList) throws IOException { 440 Path d; 441 try { 442 d = subdirectory.resolveAgainst(userPath); 443 } catch (InvalidPathException ignore) { 444 return ; 445 } 446 447 if (!Files.exists(d)) { 448 return; 449 } 450 451 if (!caseMapCheck(d, subdirectory)) { 452 return; 453 } 454 455 java.util.List<Path> files; 456 try (Stream<Path> s = Files.list(d)) { 457 files = (sortFiles == null ? s : s.sorted(sortFiles)).collect(Collectors.toList()); 458 } catch (IOException ignore) { 459 return; 460 } 461 462 for (Path f: files) { 463 String fname = f.getFileName().toString(); 464 if (fname.endsWith("/")) 465 fname = fname.substring(0, fname.length() - 1); 466 if (Files.isDirectory(f)) { 467 if (recurse && SourceVersion.isIdentifier(fname)) { 468 list(userPath, 469 new RelativeDirectory(subdirectory, fname), 470 fileKinds, 471 recurse, 472 resultList); 473 } 474 } else { 475 if (isValidFile(fname, fileKinds)) { 476 RelativeFile file = new RelativeFile(subdirectory, fname); 477 JavaFileObject fe = PathFileObject.forDirectoryPath(JavacFileManager.this, 478 file.resolveAgainst(directory), userPath, file); 479 resultList.append(fe); 480 } 481 } 482 } 483 } 484 485 @Override 486 public JavaFileObject getFileObject(Path userPath, RelativeFile name) throws IOException { 487 try { 488 Path f = name.resolveAgainst(userPath); 489 if (Files.exists(f)) 490 return PathFileObject.forSimplePath(JavacFileManager.this, 491 fsInfo.getCanonicalFile(f), f); 492 } catch (InvalidPathException ignore) { 493 } 494 return null; 495 } 496 497 @Override 498 public void close() throws IOException { 499 } 500 } 501 502 private final class ArchiveContainer implements Container { 503 private final Path archivePath; 504 private final FileSystem fileSystem; 505 private final Map<RelativePath, Path> pathCache = new HashMap<>(); 506 507 public ArchiveContainer(Path archivePath) throws IOException, ProviderNotFoundException, SecurityException { 508 this.archivePath = archivePath; 509 if (multiReleaseValue != null && archivePath.toString().endsWith(".jar")) { 510 Map<String,String> env = Collections.singletonMap("multi-release", multiReleaseValue); 511 FileSystemProvider jarFSProvider = fsInfo.getJarFSProvider(); 512 Assert.checkNonNull(jarFSProvider, "should have been caught before!"); 513 this.fileSystem = jarFSProvider.newFileSystem(archivePath, env); 514 } else { 515 this.fileSystem = FileSystems.newFileSystem(archivePath, null); 516 } 517 } 518 519 /** 520 * Insert all files in subdirectory subdirectory of this archive 521 * which match fileKinds into resultList 522 */ 523 @Override 524 public void list(Path userPath, 525 RelativeDirectory subdirectory, 526 Set<JavaFileObject.Kind> fileKinds, 527 boolean recurse, 528 ListBuffer<JavaFileObject> resultList) throws IOException { 529 Path resolvedSubdirectory = resolvePath(subdirectory); 530 531 if (resolvedSubdirectory == null) 532 return ; 533 534 int maxDepth = (recurse ? Integer.MAX_VALUE : 1); 535 Set<FileVisitOption> opts = EnumSet.of(FOLLOW_LINKS); 536 Files.walkFileTree(resolvedSubdirectory, opts, maxDepth, 537 new SimpleFileVisitor<Path>() { 538 @Override 539 public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) { 540 if (isValid(dir.getFileName())) { 541 return FileVisitResult.CONTINUE; 542 } else { 543 return FileVisitResult.SKIP_SUBTREE; 544 } 545 } 546 547 boolean isValid(Path fileName) { 548 if (fileName == null) { 549 return true; 550 } else { 551 String name = fileName.toString(); 552 if (name.endsWith("/")) { 553 name = name.substring(0, name.length() - 1); 554 } 555 return SourceVersion.isIdentifier(name); 556 } 557 } 558 559 @Override 560 public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) { 561 if (attrs.isRegularFile() && fileKinds.contains(getKind(file.getFileName().toString()))) { 562 JavaFileObject fe = PathFileObject.forJarPath( 563 JavacFileManager.this, file, archivePath); 564 resultList.append(fe); 565 } 566 return FileVisitResult.CONTINUE; 567 } 568 }); 569 570 } 571 572 @Override 573 public JavaFileObject getFileObject(Path userPath, RelativeFile name) throws IOException { 574 Path p = resolvePath(name); 575 if (p != null) 576 return PathFileObject.forJarPath(JavacFileManager.this, p, userPath); 577 578 return null; 579 } 580 581 private synchronized Path resolvePath(RelativePath path) { 582 if (!pathCache.containsKey(path)) { 583 Path relativePath = path.resolveAgainst(fileSystem); 584 585 if (!Files.exists(relativePath)) { 586 relativePath = null; 587 } 588 589 pathCache.put(path, relativePath); 590 return relativePath; 591 } 592 return pathCache.get(path); 593 } 594 595 @Override 596 public void close() throws IOException { 597 fileSystem.close(); 598 } 599 } 600 601 /** 602 * container is a directory, a zip file, or a non-existent path. 603 */ 604 private boolean isValidFile(String s, Set<JavaFileObject.Kind> fileKinds) { 605 JavaFileObject.Kind kind = getKind(s); 606 return fileKinds.contains(kind); 607 } 608 609 private static final boolean fileSystemIsCaseSensitive = 610 File.separatorChar == '/'; 611 612 /** Hack to make Windows case sensitive. Test whether given path 613 * ends in a string of characters with the same case as given name. 614 * Ignore file separators in both path and name. 615 */ 616 private boolean caseMapCheck(Path f, RelativePath name) { 617 if (fileSystemIsCaseSensitive) return true; 618 // Note that toRealPath() returns the case-sensitive 619 // spelled file name. 620 String path; 621 char sep; 622 try { 623 path = f.toRealPath(LinkOption.NOFOLLOW_LINKS).toString(); 624 sep = f.getFileSystem().getSeparator().charAt(0); 625 } catch (IOException ex) { 626 return false; 627 } 628 char[] pcs = path.toCharArray(); 629 char[] ncs = name.path.toCharArray(); 630 int i = pcs.length - 1; 631 int j = ncs.length - 1; 632 while (i >= 0 && j >= 0) { 633 while (i >= 0 && pcs[i] == sep) i--; 634 while (j >= 0 && ncs[j] == '/') j--; 635 if (i >= 0 && j >= 0) { 636 if (pcs[i] != ncs[j]) return false; 637 i--; 638 j--; 639 } 640 } 641 return j < 0; 642 } 643 644 /** Flush any output resources. 645 */ 646 @Override @DefinedBy(Api.COMPILER) 647 public void flush() { 648 contentCache.clear(); 649 } 650 651 /** 652 * Close the JavaFileManager, releasing resources. 653 */ 654 @Override @DefinedBy(Api.COMPILER) 655 public void close() throws IOException { 656 if (deferredCloseTimeout > 0) { 657 deferredClose(); 658 return; 659 } 660 661 locations.close(); 662 for (Container container: containers.values()) { 663 container.close(); 664 } 665 containers.clear(); 666 contentCache.clear(); 667 } 668 669 @Override @DefinedBy(Api.COMPILER) 670 public ClassLoader getClassLoader(Location location) { 671 checkNotModuleOrientedLocation(location); 672 Iterable<? extends File> path = getLocation(location); 673 if (path == null) 674 return null; 675 ListBuffer<URL> lb = new ListBuffer<>(); 676 for (File f: path) { 677 try { 678 lb.append(f.toURI().toURL()); 679 } catch (MalformedURLException e) { 680 throw new AssertionError(e); 681 } 682 } 683 684 return getClassLoader(lb.toArray(new URL[lb.size()])); 685 } 686 687 @Override @DefinedBy(Api.COMPILER) 688 public Iterable<JavaFileObject> list(Location location, 689 String packageName, 690 Set<JavaFileObject.Kind> kinds, 691 boolean recurse) 692 throws IOException 693 { 694 checkNotModuleOrientedLocation(location); 695 // validatePackageName(packageName); 696 nullCheck(packageName); 697 nullCheck(kinds); 698 699 Iterable<? extends Path> path = getLocationAsPaths(location); 700 if (path == null) 701 return List.nil(); 702 RelativeDirectory subdirectory = RelativeDirectory.forPackage(packageName); 703 ListBuffer<JavaFileObject> results = new ListBuffer<>(); 704 705 for (Path directory : path) { 706 Container container = getContainer(directory); 707 708 container.list(directory, subdirectory, kinds, recurse, results); 709 } 710 711 return results.toList(); 712 } 713 714 @Override @DefinedBy(Api.COMPILER) 715 public String inferBinaryName(Location location, JavaFileObject file) { 716 checkNotModuleOrientedLocation(location); 717 Objects.requireNonNull(file); 718 // Need to match the path semantics of list(location, ...) 719 Iterable<? extends Path> path = getLocationAsPaths(location); 720 if (path == null) { 721 return null; 722 } 723 724 if (file instanceof PathFileObject) { 725 return ((PathFileObject) file).inferBinaryName(path); 726 } else 727 throw new IllegalArgumentException(file.getClass().getName()); 728 } 729 730 @Override @DefinedBy(Api.COMPILER) 731 public boolean isSameFile(FileObject a, FileObject b) { 732 nullCheck(a); 733 nullCheck(b); 734 if (a instanceof PathFileObject && b instanceof PathFileObject) 735 return ((PathFileObject) a).isSameFile((PathFileObject) b); 736 return a.equals(b); 737 } 738 739 @Override @DefinedBy(Api.COMPILER) 740 public boolean hasLocation(Location location) { 741 nullCheck(location); 742 return locations.hasLocation(location); 743 } 744 745 @Override @DefinedBy(Api.COMPILER) 746 public JavaFileObject getJavaFileForInput(Location location, 747 String className, 748 JavaFileObject.Kind kind) 749 throws IOException 750 { 751 checkNotModuleOrientedLocation(location); 752 // validateClassName(className); 753 nullCheck(className); 754 nullCheck(kind); 755 if (!sourceOrClass.contains(kind)) 756 throw new IllegalArgumentException("Invalid kind: " + kind); 757 return getFileForInput(location, RelativeFile.forClass(className, kind)); 758 } 759 760 @Override @DefinedBy(Api.COMPILER) 761 public FileObject getFileForInput(Location location, 762 String packageName, 763 String relativeName) 764 throws IOException 765 { 766 checkNotModuleOrientedLocation(location); 767 // validatePackageName(packageName); 768 nullCheck(packageName); 769 if (!isRelativeUri(relativeName)) 770 throw new IllegalArgumentException("Invalid relative name: " + relativeName); 771 RelativeFile name = packageName.length() == 0 772 ? new RelativeFile(relativeName) 773 : new RelativeFile(RelativeDirectory.forPackage(packageName), relativeName); 774 return getFileForInput(location, name); 775 } 776 777 private JavaFileObject getFileForInput(Location location, RelativeFile name) throws IOException { 778 Iterable<? extends Path> path = getLocationAsPaths(location); 779 if (path == null) 780 return null; 781 782 for (Path file: path) { 783 JavaFileObject fo = getContainer(file).getFileObject(file, name); 784 785 if (fo != null) { 786 return fo; 787 } 788 } 789 return null; 790 } 791 792 @Override @DefinedBy(Api.COMPILER) 793 public JavaFileObject getJavaFileForOutput(Location location, 794 String className, 795 JavaFileObject.Kind kind, 796 FileObject sibling) 797 throws IOException 798 { 799 checkOutputLocation(location); 800 // validateClassName(className); 801 nullCheck(className); 802 nullCheck(kind); 803 if (!sourceOrClass.contains(kind)) 804 throw new IllegalArgumentException("Invalid kind: " + kind); 805 return getFileForOutput(location, RelativeFile.forClass(className, kind), sibling); 806 } 807 808 @Override @DefinedBy(Api.COMPILER) 809 public FileObject getFileForOutput(Location location, 810 String packageName, 811 String relativeName, 812 FileObject sibling) 813 throws IOException 814 { 815 checkOutputLocation(location); 816 // validatePackageName(packageName); 817 nullCheck(packageName); 818 if (!isRelativeUri(relativeName)) 819 throw new IllegalArgumentException("Invalid relative name: " + relativeName); 820 RelativeFile name = packageName.length() == 0 821 ? new RelativeFile(relativeName) 822 : new RelativeFile(RelativeDirectory.forPackage(packageName), relativeName); 823 return getFileForOutput(location, name, sibling); 824 } 825 826 private JavaFileObject getFileForOutput(Location location, 827 RelativeFile fileName, 828 FileObject sibling) 829 throws IOException 830 { 831 Path dir; 832 if (location == CLASS_OUTPUT) { 833 if (getClassOutDir() != null) { 834 dir = getClassOutDir(); 835 } else { 836 String baseName = fileName.basename(); 837 if (sibling != null && sibling instanceof PathFileObject) { 838 return ((PathFileObject) sibling).getSibling(baseName); 839 } else { 840 Path p = getPath(baseName); 841 Path real = fsInfo.getCanonicalFile(p); 842 return PathFileObject.forSimplePath(this, real, p); 843 } 844 } 845 } else if (location == SOURCE_OUTPUT) { 846 dir = (getSourceOutDir() != null ? getSourceOutDir() : getClassOutDir()); 847 } else { 848 Iterable<? extends Path> path = locations.getLocation(location); 849 dir = null; 850 for (Path f: path) { 851 dir = f; 852 break; 853 } 854 } 855 856 try { 857 if (dir == null) { 858 dir = getPath(System.getProperty("user.dir")); 859 } 860 Path path = fileName.resolveAgainst(fsInfo.getCanonicalFile(dir)); 861 return PathFileObject.forDirectoryPath(this, path, dir, fileName); 862 } catch (InvalidPathException e) { 863 throw new IOException("bad filename " + fileName, e); 864 } 865 } 866 867 @Override @DefinedBy(Api.COMPILER) 868 public Iterable<? extends JavaFileObject> getJavaFileObjectsFromFiles( 869 Iterable<? extends File> files) 870 { 871 ArrayList<PathFileObject> result; 872 if (files instanceof Collection<?>) 873 result = new ArrayList<>(((Collection<?>)files).size()); 874 else 875 result = new ArrayList<>(); 876 for (File f: files) { 877 Objects.requireNonNull(f); 878 Path p = f.toPath(); 879 result.add(PathFileObject.forSimplePath(this, 880 fsInfo.getCanonicalFile(p), p)); 881 } 882 return result; 883 } 884 885 @Override @DefinedBy(Api.COMPILER) 886 public Iterable<? extends JavaFileObject> getJavaFileObjectsFromPaths( 887 Iterable<? extends Path> paths) 888 { 889 ArrayList<PathFileObject> result; 890 if (paths instanceof Collection<?>) 891 result = new ArrayList<>(((Collection<?>)paths).size()); 892 else 893 result = new ArrayList<>(); 894 for (Path p: paths) 895 result.add(PathFileObject.forSimplePath(this, 896 fsInfo.getCanonicalFile(p), p)); 897 return result; 898 } 899 900 @Override @DefinedBy(Api.COMPILER) 901 public Iterable<? extends JavaFileObject> getJavaFileObjects(File... files) { 902 return getJavaFileObjectsFromFiles(Arrays.asList(nullCheck(files))); 903 } 904 905 @Override @DefinedBy(Api.COMPILER) 906 public Iterable<? extends JavaFileObject> getJavaFileObjects(Path... paths) { 907 return getJavaFileObjectsFromPaths(Arrays.asList(nullCheck(paths))); 908 } 909 910 @Override @DefinedBy(Api.COMPILER) 911 public void setLocation(Location location, 912 Iterable<? extends File> searchpath) 913 throws IOException 914 { 915 nullCheck(location); 916 locations.setLocation(location, asPaths(searchpath)); 917 } 918 919 @Override @DefinedBy(Api.COMPILER) 920 public void setLocationFromPaths(Location location, 921 Collection<? extends Path> searchpath) 922 throws IOException 923 { 924 nullCheck(location); 925 locations.setLocation(location, nullCheck(searchpath)); 926 } 927 928 @Override @DefinedBy(Api.COMPILER) 929 public Iterable<? extends File> getLocation(Location location) { 930 nullCheck(location); 931 return asFiles(locations.getLocation(location)); 932 } 933 934 @Override @DefinedBy(Api.COMPILER) 935 public Iterable<? extends Path> getLocationAsPaths(Location location) { 936 nullCheck(location); 937 return locations.getLocation(location); 938 } 939 940 private Path getClassOutDir() { 941 return locations.getOutputLocation(CLASS_OUTPUT); 942 } 943 944 private Path getSourceOutDir() { 945 return locations.getOutputLocation(SOURCE_OUTPUT); 946 } 947 948 @Override @DefinedBy(Api.COMPILER) 949 public Location getLocationForModule(Location location, String moduleName) throws IOException { 950 checkModuleOrientedOrOutputLocation(location); 951 nullCheck(moduleName); 952 if (location == SOURCE_OUTPUT && getSourceOutDir() == null) 953 location = CLASS_OUTPUT; 954 return locations.getLocationForModule(location, moduleName); 955 } 956 957 @Override @DefinedBy(Api.COMPILER) 958 public <S> ServiceLoader<S> getServiceLoader(Location location, Class<S> service) throws IOException { 959 nullCheck(location); 960 nullCheck(service); 961 Module.getModule(getClass()).addUses(service); 962 if (location.isModuleOrientedLocation()) { 963 Collection<Path> paths = locations.getLocation(location); 964 ModuleFinder finder = ModuleFinder.of(paths.toArray(new Path[paths.size()])); 965 Layer bootLayer = Layer.boot(); 966 Configuration cf = bootLayer.configuration().resolveAndBind(ModuleFinder.of(), finder, Collections.emptySet()); 967 Layer layer = bootLayer.defineModulesWithOneLoader(cf, ClassLoader.getSystemClassLoader()); 968 return ServiceLoaderHelper.load(layer, service); 969 } else { 970 return ServiceLoader.load(service, getClassLoader(location)); 971 } 972 } 973 974 @Override @DefinedBy(Api.COMPILER) 975 public Location getLocationForModule(Location location, JavaFileObject fo, String pkgName) throws IOException { 976 checkModuleOrientedOrOutputLocation(location); 977 if (!(fo instanceof PathFileObject)) 978 throw new IllegalArgumentException(fo.getName()); 979 int depth = 1; // allow 1 for filename 980 if (pkgName != null && !pkgName.isEmpty()) { 981 depth += 1; 982 for (int i = 0; i < pkgName.length(); i++) { 983 switch (pkgName.charAt(i)) { 984 case '/': case '.': 985 depth++; 986 } 987 } 988 } 989 Path p = Locations.normalize(((PathFileObject) fo).path); 990 int fc = p.getNameCount(); 991 if (depth < fc) { 992 Path root = p.getRoot(); 993 Path subpath = p.subpath(0, fc - depth); 994 Path dir = (root == null) ? subpath : root.resolve(subpath); 995 // need to find dir in location 996 return locations.getLocationForModule(location, dir); 997 } else { 998 return null; 999 } 1000 } 1001 1002 @Override @DefinedBy(Api.COMPILER) 1003 public String inferModuleName(Location location) { 1004 checkNotModuleOrientedLocation(location); 1005 return locations.inferModuleName(location); 1006 } 1007 1008 @Override @DefinedBy(Api.COMPILER) 1009 public Iterable<Set<Location>> listLocationsForModules(Location location) throws IOException { 1010 checkModuleOrientedOrOutputLocation(location); 1011 return locations.listLocationsForModules(location); 1012 } 1013 1014 @Override @DefinedBy(Api.COMPILER) 1015 public Path asPath(FileObject file) { 1016 if (file instanceof PathFileObject) { 1017 return ((PathFileObject) file).path; 1018 } else 1019 throw new IllegalArgumentException(file.getName()); 1020 } 1021 1022 /** 1023 * Enforces the specification of a "relative" name as used in 1024 * {@linkplain #getFileForInput(Location,String,String) 1025 * getFileForInput}. This method must follow the rules defined in 1026 * that method, do not make any changes without consulting the 1027 * specification. 1028 */ 1029 protected static boolean isRelativeUri(URI uri) { 1030 if (uri.isAbsolute()) 1031 return false; 1032 String path = uri.normalize().getPath(); 1033 if (path.length() == 0 /* isEmpty() is mustang API */) 1034 return false; 1035 if (!path.equals(uri.getPath())) // implicitly checks for embedded . and .. 1036 return false; 1037 if (path.startsWith("/") || path.startsWith("./") || path.startsWith("../")) 1038 return false; 1039 return true; 1040 } 1041 1042 // Convenience method 1043 protected static boolean isRelativeUri(String u) { 1044 try { 1045 return isRelativeUri(new URI(u)); 1046 } catch (URISyntaxException e) { 1047 return false; 1048 } 1049 } 1050 1051 /** 1052 * Converts a relative file name to a relative URI. This is 1053 * different from File.toURI as this method does not canonicalize 1054 * the file before creating the URI. Furthermore, no schema is 1055 * used. 1056 * @param file a relative file name 1057 * @return a relative URI 1058 * @throws IllegalArgumentException if the file name is not 1059 * relative according to the definition given in {@link 1060 * javax.tools.JavaFileManager#getFileForInput} 1061 */ 1062 public static String getRelativeName(File file) { 1063 if (!file.isAbsolute()) { 1064 String result = file.getPath().replace(File.separatorChar, '/'); 1065 if (isRelativeUri(result)) 1066 return result; 1067 } 1068 throw new IllegalArgumentException("Invalid relative path: " + file); 1069 } 1070 1071 /** 1072 * Get a detail message from an IOException. 1073 * Most, but not all, instances of IOException provide a non-null result 1074 * for getLocalizedMessage(). But some instances return null: in these 1075 * cases, fallover to getMessage(), and if even that is null, return the 1076 * name of the exception itself. 1077 * @param e an IOException 1078 * @return a string to include in a compiler diagnostic 1079 */ 1080 public static String getMessage(IOException e) { 1081 String s = e.getLocalizedMessage(); 1082 if (s != null) 1083 return s; 1084 s = e.getMessage(); 1085 if (s != null) 1086 return s; 1087 return e.toString(); 1088 } 1089 1090 private void checkOutputLocation(Location location) { 1091 Objects.requireNonNull(location); 1092 if (!location.isOutputLocation()) 1093 throw new IllegalArgumentException("location is not an output location: " + location.getName()); 1094 } 1095 1096 private void checkModuleOrientedOrOutputLocation(Location location) { 1097 Objects.requireNonNull(location); 1098 if (!location.isModuleOrientedLocation() && !location.isOutputLocation()) 1099 throw new IllegalArgumentException( 1100 "location is not an output location or a module-oriented location: " 1101 + location.getName()); 1102 } 1103 1104 private void checkNotModuleOrientedLocation(Location location) { 1105 Objects.requireNonNull(location); 1106 if (location.isModuleOrientedLocation()) 1107 throw new IllegalArgumentException("location is module-oriented: " + location.getName()); 1108 } 1109 1110 /* Converters between files and paths. 1111 * These are temporary until we can update the StandardJavaFileManager API. 1112 */ 1113 1114 private static Iterable<Path> asPaths(final Iterable<? extends File> files) { 1115 if (files == null) 1116 return null; 1117 1118 return () -> new Iterator<Path>() { 1119 Iterator<? extends File> iter = files.iterator(); 1120 1121 @Override 1122 public boolean hasNext() { 1123 return iter.hasNext(); 1124 } 1125 1126 @Override 1127 public Path next() { 1128 return iter.next().toPath(); 1129 } 1130 }; 1131 } 1132 1133 private static Iterable<File> asFiles(final Iterable<? extends Path> paths) { 1134 if (paths == null) 1135 return null; 1136 1137 return () -> new Iterator<File>() { 1138 Iterator<? extends Path> iter = paths.iterator(); 1139 1140 @Override 1141 public boolean hasNext() { 1142 return iter.hasNext(); 1143 } 1144 1145 @Override 1146 public File next() { 1147 try { 1148 return iter.next().toFile(); 1149 } catch (UnsupportedOperationException e) { 1150 throw new IllegalStateException(e); 1151 } 1152 } 1153 }; 1154 } 1155 }