JavacFileManager.java revision 3262:21d9e172e9f6
11195Srgrimes/* 250472Speter * Copyright (c) 2005, 2016, Oracle and/or its affiliates. All rights reserved. 337Srgrimes * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 473251Sgshapiro * 538103Speter * This code is free software; you can redistribute it and/or modify it 673251Sgshapiro * under the terms of the GNU General Public License version 2 only, as 738103Speter * published by the Free Software Foundation. Oracle designates this 899451Sru * particular file as subject to the "Classpath" exception as provided 9114780Sdougb * by Oracle in the LICENSE file that accompanied this code. 10114780Sdougb * 1165532Snectar * This code is distributed in the hope that it will be useful, but WITHOUT 1255230Speter * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 13113674Smtm * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 14126756Smlaier * version 2 for more details (a copy is included in the LICENSE file that 15114555Sdougb * accompanied this code). 16114492Sdougb * 1798187Sgordon * You should have received a copy of the GNU General Public License version 1855230Speter * 2 along with this work; if not, write to the Free Software Foundation, 191734Sjkh * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 2017639Swosch * 2117639Swosch * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 22121911Smarkm * or visit www.oracle.com if you need additional information or have any 23121911Smarkm * questions. 24121911Smarkm */ 2537Srgrimes 26121911Smarkmpackage com.sun.tools.javac.file; 2798548Sru 2857488Speterimport java.io.File; 2974837Sgreenimport java.io.IOException; 30124214Sdesimport java.net.MalformedURLException; 3157459Smarkmimport java.net.URI; 3260677Skrisimport java.net.URISyntaxException; 3360677Skrisimport java.net.URL; 3460677Skrisimport java.nio.CharBuffer; 3582521Saleximport java.nio.charset.Charset; 36108002Sgreenimport java.nio.file.FileSystem; 37147Srgrimesimport java.nio.file.FileSystems; 3827487Sasamiimport java.nio.file.FileVisitOption; 3965168Sasamiimport java.nio.file.FileVisitResult; 4095144Sgshapiroimport java.nio.file.Files; 4195144Sgshapiroimport java.nio.file.InvalidPathException; 4295144Sgshapiroimport java.nio.file.LinkOption; 43135851Sdougbimport java.nio.file.Path; 44135851Sdougbimport java.nio.file.Paths; 45135851Sdougbimport java.nio.file.SimpleFileVisitor; 46135851Sdougbimport java.nio.file.attribute.BasicFileAttributes; 47135851Sdougbimport java.util.ArrayList; 48135851Sdougbimport java.util.Arrays; 4999451Sruimport java.util.Collection; 50135851Sdougbimport java.util.Comparator; 5199451Sruimport java.util.EnumSet; 5290281Sumeimport java.util.HashMap; 53135851Sdougbimport java.util.Iterator; 5499451Sruimport java.util.Map; 5599451Sruimport java.util.Objects; 5699451Sruimport java.util.Set; 57117292Sgshapiroimport java.util.stream.Collectors; 58117292Sgshapiroimport java.util.stream.Stream; 59117292Sgshapiro 6064598Sgshapiroimport javax.lang.model.SourceVersion; 6164598Sgshapiroimport javax.tools.FileObject; 62117292Sgshapiroimport javax.tools.JavaFileManager; 6337Srgrimesimport javax.tools.JavaFileObject; 64263Srgrimesimport javax.tools.StandardJavaFileManager; 6599449Sru 66263Srgrimesimport com.sun.tools.javac.file.RelativePath.RelativeDirectory; 67124831Sruimport com.sun.tools.javac.file.RelativePath.RelativeFile; 68124831Sruimport com.sun.tools.javac.util.Context; 69124831Sruimport com.sun.tools.javac.util.DefinedBy; 70124831Sruimport com.sun.tools.javac.util.DefinedBy.Api; 71124831Sruimport com.sun.tools.javac.util.List; 724487Sphkimport com.sun.tools.javac.util.ListBuffer; 73124831Sru 7495327Sobrienimport static java.nio.file.FileVisitOption.FOLLOW_LINKS; 755948Sjkh 764487Sphkimport static javax.tools.StandardLocation.*; 7799449Sru 78100872Sru/** 7999451Sru * This class provides access to the source, class and other files 80111810Sru * used by the compiler and related tools. 81100872Sru * 8299451Sru * <p><b>This is NOT part of any supported API. 83100872Sru * If you write code that depends on this, you do so at your own risk. 8499451Sru * This code and its internal interfaces are subject to change or 8599451Sru * deletion without notice.</b> 86121580Semax */ 87119385Smtmpublic class JavacFileManager extends BaseFileManager implements StandardJavaFileManager { 8899449Sru 8999449Sru @SuppressWarnings("cast") 9099449Sru public static char[] toArray(CharBuffer buffer) { 9199449Sru if (buffer.hasArray()) 9299449Sru return ((CharBuffer)buffer.compact().flip()).array(); 9399449Sru else 9477041Sru return buffer.toString().toCharArray(); 9599449Sru } 9677041Sru 9773251Sgshapiro private FSInfo fsInfo; 9899449Sru 9973251Sgshapiro private final Set<JavaFileObject.Kind> sourceOrClass = 100120202Smarkm EnumSet.of(JavaFileObject.Kind.SOURCE, JavaFileObject.Kind.CLASS); 10198548Sru 102100872Sru protected boolean symbolFileEnabled; 10399451Sru 10457488Speter protected enum SortFiles implements Comparator<Path> { 105100872Sru FORWARD { 10699451Sru @Override 10760677Skris public int compare(Path f1, Path f2) { 108120709Sphk return f1.getFileName().compareTo(f2.getFileName()); 10999449Sru } 110100872Sru }, 111120709Sphk REVERSE { 112120709Sphk @Override 113120709Sphk public int compare(Path f1, Path f2) { 114120709Sphk return -f1.getFileName().compareTo(f2.getFileName()); 11599451Sru } 116100872Sru } 11799451Sru } 118100872Sru 11999451Sru protected SortFiles sortFiles; 12099451Sru 12199451Sru /** 12299451Sru * Register a Context.Factory to create a JavacFileManager. 123100872Sru */ 1241731Sjkh public static void preRegister(Context context) { 125135851Sdougb context.put(JavaFileManager.class, new Context.Factory<JavaFileManager>() { 126100872Sru @Override 1271731Sjkh public JavaFileManager make(Context c) { 128135851Sdougb return new JavacFileManager(c, true, null); 129119058Sobrien } 1306177Samurai }); 131100872Sru } 13264598Sgshapiro 13364629Sgshapiro /** 13464629Sgshapiro * Create a JavacFileManager using a given context, optionally registering 13564629Sgshapiro * it as the JavaFileManager for that context. 13664629Sgshapiro */ 13764629Sgshapiro public JavacFileManager(Context context, boolean register, Charset charset) { 138100872Sru super(charset); 13937Srgrimes if (register) 140100872Sru context.put(JavaFileManager.class, this); 141147Srgrimes setContext(context); 142100872Sru } 14392100Srwatson 144100872Sru /** 14599449Sru * Set the context for JavacFileManager. 146103720Smarkm */ 147113259Sdes @Override 148113259Sdes public void setContext(Context context) { 149103738Smarkm super.setContext(context); 150100872Sru 151147Srgrimes fsInfo = FSInfo.instance(context); 152100872Sru 15337Srgrimes symbolFileEnabled = !options.isSet("ignore.symbol.file"); 154100872Sru 155288Srgrimes String sf = options.get("sortFiles"); 156100872Sru if (sf != null) { 157147Srgrimes sortFiles = (sf.equals("reverse") ? SortFiles.REVERSE : SortFiles.FORWARD); 158100872Sru } 15950126Sgreen } 160100872Sru 16113378Sache /** 162100872Sru * Set whether or not to use ct.sym as an alternate to rt.jar. 16317104Spst */ 164100872Sru public void setSymbolFileEnabled(boolean b) { 165147Srgrimes symbolFileEnabled = b; 166100872Sru } 16737Srgrimes 168100872Sru public boolean isSymbolFileEnabled() { 1691759Sjkh return symbolFileEnabled; 170100872Sru } 17199451Sru 172126977Sru // used by tests 173126977Sru public JavaFileObject getJavaFileObject(String name) { 174126977Sru return getJavaFileObjects(name).iterator().next(); 17537Srgrimes } 176147Srgrimes 177127339Sdes // used by tests 178127339Sdes public JavaFileObject getJavaFileObject(Path file) { 179127339Sdes return getJavaFileObjects(file).iterator().next(); 180127339Sdes } 1817129Srgrimes 182135875Sdougb public JavaFileObject getFileForOutput(String classname, 183135875Sdougb JavaFileObject.Kind kind, 184135875Sdougb JavaFileObject sibling) 185135875Sdougb throws IOException 186135875Sdougb { 187135875Sdougb return getJavaFileForOutput(CLASS_OUTPUT, classname, kind, sibling); 188135875Sdougb } 189135875Sdougb 190135875Sdougb @Override @DefinedBy(Api.COMPILER) 19195144Sgshapiro public Iterable<? extends JavaFileObject> getJavaFileObjectsFromStrings(Iterable<String> names) { 19295144Sgshapiro ListBuffer<Path> paths = new ListBuffer<>(); 19395144Sgshapiro for (String name : names) 194410Srgrimes paths.append(Paths.get(nullCheck(name))); 19577993Sache return getJavaFileObjectsFromPaths(paths.toList()); 19677993Sache } 19777993Sache 19877993Sache @Override @DefinedBy(Api.COMPILER) 19977993Sache public Iterable<? extends JavaFileObject> getJavaFileObjects(String... names) { 20077993Sache return getJavaFileObjectsFromStrings(Arrays.asList(nullCheck(names))); 20177993Sache } 20277993Sache 20377993Sache private static boolean isValidName(String name) { 204110663Sache // Arguably, isValidName should reject keywords (such as in SourceVersion.isName() ), 205110663Sache // but the set of keywords depends on the source level, and we don't want 206110663Sache // impls of JavaFileManager to have to be dependent on the source level. 207110663Sache // Therefore we simply check that the argument is a sequence of identifiers 208110663Sache // separated by ".". 209110663Sache for (String s : name.split("\\.", -1)) { 210110663Sache if (!SourceVersion.isIdentifier(s)) 211110663Sache return false; 212110655Snectar } 21311635Sache return true; 21477999Sache } 21511635Sache 21611635Sache private static void validateClassName(String className) { 21711635Sache if (!isValidName(className)) 21811635Sache throw new IllegalArgumentException("Invalid class name: " + className); 21911635Sache } 22077999Sache 221147Srgrimes private static void validatePackageName(String packageName) { 22248185Ssheldonh if (packageName.length() > 0 && !isValidName(packageName)) 223100872Sru throw new IllegalArgumentException("Invalid packageName name: " + packageName); 22499451Sru } 22599451Sru 226119385Smtm public static void testName(String name, 227119385Smtm boolean isValidPackageName, 22848185Ssheldonh boolean isValidClassName) 22937Srgrimes { 230 try { 231 validatePackageName(name); 232 if (!isValidPackageName) 233 throw new AssertionError("Invalid package name accepted: " + name); 234 printAscii("Valid package name: \"%s\"", name); 235 } catch (IllegalArgumentException e) { 236 if (isValidPackageName) 237 throw new AssertionError("Valid package name rejected: " + name); 238 printAscii("Invalid package name: \"%s\"", name); 239 } 240 try { 241 validateClassName(name); 242 if (!isValidClassName) 243 throw new AssertionError("Invalid class name accepted: " + name); 244 printAscii("Valid class name: \"%s\"", name); 245 } catch (IllegalArgumentException e) { 246 if (isValidClassName) 247 throw new AssertionError("Valid class name rejected: " + name); 248 printAscii("Invalid class name: \"%s\"", name); 249 } 250 } 251 252 private static void printAscii(String format, Object... args) { 253 String message; 254 try { 255 final String ascii = "US-ASCII"; 256 message = new String(String.format(null, format, args).getBytes(ascii), ascii); 257 } catch (java.io.UnsupportedEncodingException ex) { 258 throw new AssertionError(ex); 259 } 260 System.out.println(message); 261 } 262 263 /** 264 * Insert all files in a subdirectory of the platform image 265 * which match fileKinds into resultList. 266 */ 267 private void listJRTImage(RelativeDirectory subdirectory, 268 Set<JavaFileObject.Kind> fileKinds, 269 boolean recurse, 270 ListBuffer<JavaFileObject> resultList) throws IOException { 271 JRTIndex.Entry e = getJRTIndex().getEntry(subdirectory); 272 if (symbolFileEnabled && e.ctSym.hidden) 273 return; 274 for (Path file: e.files.values()) { 275 if (fileKinds.contains(getKind(file))) { 276 JavaFileObject fe 277 = PathFileObject.forJRTPath(JavacFileManager.this, file); 278 resultList.append(fe); 279 } 280 } 281 282 if (recurse) { 283 for (RelativeDirectory rd: e.subdirs) { 284 listJRTImage(rd, fileKinds, recurse, resultList); 285 } 286 } 287 } 288 289 private synchronized JRTIndex getJRTIndex() { 290 if (jrtIndex == null) 291 jrtIndex = JRTIndex.getSharedInstance(); 292 return jrtIndex; 293 } 294 295 private JRTIndex jrtIndex; 296 297 298 /** 299 * Insert all files in subdirectory subdirectory of directory directory 300 * which match fileKinds into resultList 301 */ 302 private void listDirectory(Path directory, Path realDirectory, 303 RelativeDirectory subdirectory, 304 Set<JavaFileObject.Kind> fileKinds, 305 boolean recurse, 306 ListBuffer<JavaFileObject> resultList) { 307 Path d; 308 try { 309 d = subdirectory.resolveAgainst(directory); 310 } catch (InvalidPathException ignore) { 311 return; 312 } 313 314 if (!Files.exists(d)) { 315 return; 316 } 317 318 if (!caseMapCheck(d, subdirectory)) { 319 return; 320 } 321 322 java.util.List<Path> files; 323 try (Stream<Path> s = Files.list(d)) { 324 files = (sortFiles == null ? s : s.sorted(sortFiles)).collect(Collectors.toList()); 325 } catch (IOException ignore) { 326 return; 327 } 328 329 if (realDirectory == null) 330 realDirectory = fsInfo.getCanonicalFile(directory); 331 332 for (Path f: files) { 333 String fname = f.getFileName().toString(); 334 if (fname.endsWith("/")) 335 fname = fname.substring(0, fname.length() - 1); 336 if (Files.isDirectory(f)) { 337 if (recurse && SourceVersion.isIdentifier(fname)) { 338 listDirectory(directory, realDirectory, 339 new RelativeDirectory(subdirectory, fname), 340 fileKinds, 341 recurse, 342 resultList); 343 } 344 } else { 345 if (isValidFile(fname, fileKinds)) { 346 RelativeFile file = new RelativeFile(subdirectory, fname); 347 JavaFileObject fe = PathFileObject.forDirectoryPath(this, 348 file.resolveAgainst(realDirectory), directory, file); 349 resultList.append(fe); 350 } 351 } 352 } 353 } 354 355 /** 356 * Insert all files in subdirectory subdirectory of archive archivePath 357 * which match fileKinds into resultList 358 */ 359 private void listArchive(Path archivePath, 360 RelativeDirectory subdirectory, 361 Set<JavaFileObject.Kind> fileKinds, 362 boolean recurse, 363 ListBuffer<JavaFileObject> resultList) 364 throws IOException { 365 FileSystem fs = getFileSystem(archivePath); 366 if (fs == null) { 367 return; 368 } 369 370 Path containerSubdir = subdirectory.resolveAgainst(fs); 371 if (!Files.exists(containerSubdir)) { 372 return; 373 } 374 375 int maxDepth = (recurse ? Integer.MAX_VALUE : 1); 376 Set<FileVisitOption> opts = EnumSet.of(FOLLOW_LINKS); 377 Files.walkFileTree(containerSubdir, opts, maxDepth, 378 new SimpleFileVisitor<Path>() { 379 @Override 380 public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) { 381 if (isValid(dir.getFileName())) { 382 return FileVisitResult.CONTINUE; 383 } else { 384 return FileVisitResult.SKIP_SUBTREE; 385 } 386 } 387 388 boolean isValid(Path fileName) { 389 if (fileName == null) { 390 return true; 391 } else { 392 String name = fileName.toString(); 393 if (name.endsWith("/")) { 394 name = name.substring(0, name.length() - 1); 395 } 396 return SourceVersion.isIdentifier(name); 397 } 398 } 399 400 @Override 401 public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) { 402 if (attrs.isRegularFile() && fileKinds.contains(getKind(file.getFileName().toString()))) { 403 JavaFileObject fe = PathFileObject.forJarPath( 404 JavacFileManager.this, file, archivePath); 405 resultList.append(fe); 406 } 407 return FileVisitResult.CONTINUE; 408 } 409 }); 410 411 } 412 413 /** 414 * container is a directory, a zip file, or a non-existant path. 415 * Insert all files in subdirectory subdirectory of container which 416 * match fileKinds into resultList 417 */ 418 private void listContainer(Path container, 419 RelativeDirectory subdirectory, 420 Set<JavaFileObject.Kind> fileKinds, 421 boolean recurse, 422 ListBuffer<JavaFileObject> resultList) 423 throws IOException { 424 // Very temporary and obnoxious interim hack 425 if (container.endsWith("bootmodules.jimage")) { 426 System.err.println("Warning: reference to bootmodules.jimage replaced by jrt:"); 427 container = Locations.JRT_MARKER_FILE; 428 } else if (container.getNameCount() > 0 && container.getFileName().toString().endsWith(".jimage")) { 429 System.err.println("Warning: reference to " + container + " ignored"); 430 return; 431 } 432 433 if (container == Locations.JRT_MARKER_FILE) { 434 try { 435 listJRTImage(subdirectory, 436 fileKinds, 437 recurse, 438 resultList); 439 } catch (IOException ex) { 440 ex.printStackTrace(System.err); 441 log.error("error.reading.file", container, getMessage(ex)); 442 } 443 return; 444 } 445 446 if (fsInfo.isDirectory(container)) { 447 listDirectory(container, null, 448 subdirectory, 449 fileKinds, 450 recurse, 451 resultList); 452 return; 453 } 454 455 if (Files.exists(container)) { 456 listArchive(container, 457 subdirectory, 458 fileKinds, 459 recurse, 460 resultList); 461 } 462 } 463 464 private boolean isValidFile(String s, Set<JavaFileObject.Kind> fileKinds) { 465 JavaFileObject.Kind kind = getKind(s); 466 return fileKinds.contains(kind); 467 } 468 469 private static final boolean fileSystemIsCaseSensitive = 470 File.separatorChar == '/'; 471 472 /** Hack to make Windows case sensitive. Test whether given path 473 * ends in a string of characters with the same case as given name. 474 * Ignore file separators in both path and name. 475 */ 476 private boolean caseMapCheck(Path f, RelativePath name) { 477 if (fileSystemIsCaseSensitive) return true; 478 // Note that toRealPath() returns the case-sensitive 479 // spelled file name. 480 String path; 481 char sep; 482 try { 483 path = f.toRealPath(LinkOption.NOFOLLOW_LINKS).toString(); 484 sep = f.getFileSystem().getSeparator().charAt(0); 485 } catch (IOException ex) { 486 return false; 487 } 488 char[] pcs = path.toCharArray(); 489 char[] ncs = name.path.toCharArray(); 490 int i = pcs.length - 1; 491 int j = ncs.length - 1; 492 while (i >= 0 && j >= 0) { 493 while (i >= 0 && pcs[i] == sep) i--; 494 while (j >= 0 && ncs[j] == '/') j--; 495 if (i >= 0 && j >= 0) { 496 if (pcs[i] != ncs[j]) return false; 497 i--; 498 j--; 499 } 500 } 501 return j < 0; 502 } 503 504 private FileSystem getFileSystem(Path path) throws IOException { 505 Path realPath = fsInfo.getCanonicalFile(path); 506 FileSystem fs = fileSystems.get(realPath); 507 if (fs == null) { 508 fileSystems.put(realPath, fs = FileSystems.newFileSystem(realPath, null)); 509 } 510 return fs; 511 } 512 513 private final Map<Path,FileSystem> fileSystems = new HashMap<>(); 514 515 516 /** Flush any output resources. 517 */ 518 @Override @DefinedBy(Api.COMPILER) 519 public void flush() { 520 contentCache.clear(); 521 } 522 523 /** 524 * Close the JavaFileManager, releasing resources. 525 */ 526 @Override @DefinedBy(Api.COMPILER) 527 public void close() throws IOException { 528 if (deferredCloseTimeout > 0) { 529 deferredClose(); 530 return; 531 } 532 533 for (FileSystem fs: fileSystems.values()) { 534 fs.close(); 535 } 536 fileSystems.clear(); 537 contentCache.clear(); 538 } 539 540 @Override @DefinedBy(Api.COMPILER) 541 public ClassLoader getClassLoader(Location location) { 542 nullCheck(location); 543 Iterable<? extends File> path = getLocation(location); 544 if (path == null) 545 return null; 546 ListBuffer<URL> lb = new ListBuffer<>(); 547 for (File f: path) { 548 try { 549 lb.append(f.toURI().toURL()); 550 } catch (MalformedURLException e) { 551 throw new AssertionError(e); 552 } 553 } 554 555 return getClassLoader(lb.toArray(new URL[lb.size()])); 556 } 557 558 @Override @DefinedBy(Api.COMPILER) 559 public Iterable<JavaFileObject> list(Location location, 560 String packageName, 561 Set<JavaFileObject.Kind> kinds, 562 boolean recurse) 563 throws IOException 564 { 565 // validatePackageName(packageName); 566 nullCheck(packageName); 567 nullCheck(kinds); 568 569 Iterable<? extends Path> path = getLocationAsPaths(location); 570 if (path == null) 571 return List.nil(); 572 RelativeDirectory subdirectory = RelativeDirectory.forPackage(packageName); 573 ListBuffer<JavaFileObject> results = new ListBuffer<>(); 574 575 for (Path directory : path) 576 listContainer(directory, subdirectory, kinds, recurse, results); 577 return results.toList(); 578 } 579 580 @Override @DefinedBy(Api.COMPILER) 581 public String inferBinaryName(Location location, JavaFileObject file) { 582 Objects.requireNonNull(file); 583 Objects.requireNonNull(location); 584 // Need to match the path semantics of list(location, ...) 585 Iterable<? extends Path> path = getLocationAsPaths(location); 586 if (path == null) { 587 return null; 588 } 589 590 if (file instanceof PathFileObject) { 591 return ((PathFileObject) file).inferBinaryName(path); 592 } else 593 throw new IllegalArgumentException(file.getClass().getName()); 594 } 595 596 @Override @DefinedBy(Api.COMPILER) 597 public boolean isSameFile(FileObject a, FileObject b) { 598 nullCheck(a); 599 nullCheck(b); 600 if (a instanceof PathFileObject && b instanceof PathFileObject) 601 return ((PathFileObject) a).isSameFile((PathFileObject) b); 602 return a.equals(b); 603 } 604 605 @Override @DefinedBy(Api.COMPILER) 606 public boolean hasLocation(Location location) { 607 return getLocation(location) != null; 608 } 609 610 @Override @DefinedBy(Api.COMPILER) 611 public JavaFileObject getJavaFileForInput(Location location, 612 String className, 613 JavaFileObject.Kind kind) 614 throws IOException 615 { 616 nullCheck(location); 617 // validateClassName(className); 618 nullCheck(className); 619 nullCheck(kind); 620 if (!sourceOrClass.contains(kind)) 621 throw new IllegalArgumentException("Invalid kind: " + kind); 622 return getFileForInput(location, RelativeFile.forClass(className, kind)); 623 } 624 625 @Override @DefinedBy(Api.COMPILER) 626 public FileObject getFileForInput(Location location, 627 String packageName, 628 String relativeName) 629 throws IOException 630 { 631 nullCheck(location); 632 // validatePackageName(packageName); 633 nullCheck(packageName); 634 if (!isRelativeUri(relativeName)) 635 throw new IllegalArgumentException("Invalid relative name: " + relativeName); 636 RelativeFile name = packageName.length() == 0 637 ? new RelativeFile(relativeName) 638 : new RelativeFile(RelativeDirectory.forPackage(packageName), relativeName); 639 return getFileForInput(location, name); 640 } 641 642 private JavaFileObject getFileForInput(Location location, RelativeFile name) throws IOException { 643 Iterable<? extends Path> path = getLocationAsPaths(location); 644 if (path == null) 645 return null; 646 647 for (Path file: path) { 648 if (file == Locations.JRT_MARKER_FILE) { 649 JRTIndex.Entry e = getJRTIndex().getEntry(name.dirname()); 650 if (symbolFileEnabled && e.ctSym.hidden) 651 continue; 652 Path p = e.files.get(name.basename()); 653 if (p != null) 654 return PathFileObject.forJRTPath(this, p); 655 } else if (fsInfo.isDirectory(file)) { 656 try { 657 Path f = name.resolveAgainst(file); 658 if (Files.exists(f)) 659 return PathFileObject.forSimplePath(this, 660 fsInfo.getCanonicalFile(f), f); 661 } catch (InvalidPathException ignore) { 662 } 663 } else if (Files.exists(file)) { 664 FileSystem fs = getFileSystem(file); 665 if (fs != null) { 666 Path fsRoot = fs.getRootDirectories().iterator().next(); 667 Path f = name.resolveAgainst(fsRoot); 668 if (Files.exists(f)) 669 return PathFileObject.forJarPath(this, f, file); 670 } 671 } 672 } 673 return null; 674 } 675 676 @Override @DefinedBy(Api.COMPILER) 677 public JavaFileObject getJavaFileForOutput(Location location, 678 String className, 679 JavaFileObject.Kind kind, 680 FileObject sibling) 681 throws IOException 682 { 683 nullCheck(location); 684 // validateClassName(className); 685 nullCheck(className); 686 nullCheck(kind); 687 if (!sourceOrClass.contains(kind)) 688 throw new IllegalArgumentException("Invalid kind: " + kind); 689 return getFileForOutput(location, RelativeFile.forClass(className, kind), sibling); 690 } 691 692 @Override @DefinedBy(Api.COMPILER) 693 public FileObject getFileForOutput(Location location, 694 String packageName, 695 String relativeName, 696 FileObject sibling) 697 throws IOException 698 { 699 nullCheck(location); 700 // validatePackageName(packageName); 701 nullCheck(packageName); 702 if (!isRelativeUri(relativeName)) 703 throw new IllegalArgumentException("Invalid relative name: " + relativeName); 704 RelativeFile name = packageName.length() == 0 705 ? new RelativeFile(relativeName) 706 : new RelativeFile(RelativeDirectory.forPackage(packageName), relativeName); 707 return getFileForOutput(location, name, sibling); 708 } 709 710 private JavaFileObject getFileForOutput(Location location, 711 RelativeFile fileName, 712 FileObject sibling) 713 throws IOException 714 { 715 Path dir; 716 if (location == CLASS_OUTPUT) { 717 if (getClassOutDir() != null) { 718 dir = getClassOutDir(); 719 } else { 720 String baseName = fileName.basename(); 721 if (sibling != null && sibling instanceof PathFileObject) { 722 return ((PathFileObject) sibling).getSibling(baseName); 723 } else { 724 Path p = Paths.get(baseName); 725 Path real = fsInfo.getCanonicalFile(p); 726 return PathFileObject.forSimplePath(this, real, p); 727 } 728 } 729 } else if (location == SOURCE_OUTPUT) { 730 dir = (getSourceOutDir() != null ? getSourceOutDir() : getClassOutDir()); 731 } else { 732 Iterable<? extends Path> path = locations.getLocation(location); 733 dir = null; 734 for (Path f: path) { 735 dir = f; 736 break; 737 } 738 } 739 740 try { 741 if (dir == null) { 742 dir = Paths.get(System.getProperty("user.dir")); 743 } 744 Path path = fileName.resolveAgainst(fsInfo.getCanonicalFile(dir)); 745 return PathFileObject.forDirectoryPath(this, path, dir, fileName); 746 } catch (InvalidPathException e) { 747 throw new IOException("bad filename " + fileName, e); 748 } 749 } 750 751 @Override @DefinedBy(Api.COMPILER) 752 public Iterable<? extends JavaFileObject> getJavaFileObjectsFromFiles( 753 Iterable<? extends File> files) 754 { 755 ArrayList<PathFileObject> result; 756 if (files instanceof Collection<?>) 757 result = new ArrayList<>(((Collection<?>)files).size()); 758 else 759 result = new ArrayList<>(); 760 for (File f: files) { 761 Objects.requireNonNull(f); 762 Path p = f.toPath(); 763 result.add(PathFileObject.forSimplePath(this, 764 fsInfo.getCanonicalFile(p), p)); 765 } 766 return result; 767 } 768 769 @Override @DefinedBy(Api.COMPILER) 770 public Iterable<? extends JavaFileObject> getJavaFileObjectsFromPaths( 771 Iterable<? extends Path> paths) 772 { 773 ArrayList<PathFileObject> result; 774 if (paths instanceof Collection<?>) 775 result = new ArrayList<>(((Collection<?>)paths).size()); 776 else 777 result = new ArrayList<>(); 778 for (Path p: paths) 779 result.add(PathFileObject.forSimplePath(this, 780 fsInfo.getCanonicalFile(p), p)); 781 return result; 782 } 783 784 @Override @DefinedBy(Api.COMPILER) 785 public Iterable<? extends JavaFileObject> getJavaFileObjects(File... files) { 786 return getJavaFileObjectsFromFiles(Arrays.asList(nullCheck(files))); 787 } 788 789 @Override @DefinedBy(Api.COMPILER) 790 public Iterable<? extends JavaFileObject> getJavaFileObjects(Path... paths) { 791 return getJavaFileObjectsFromPaths(Arrays.asList(nullCheck(paths))); 792 } 793 794 @Override @DefinedBy(Api.COMPILER) 795 public void setLocation(Location location, 796 Iterable<? extends File> searchpath) 797 throws IOException 798 { 799 nullCheck(location); 800 locations.setLocation(location, asPaths(searchpath)); 801 } 802 803 @Override @DefinedBy(Api.COMPILER) 804 public void setLocationFromPaths(Location location, 805 Iterable<? extends Path> searchpath) 806 throws IOException 807 { 808 nullCheck(location); 809 locations.setLocation(location, nullCheck(searchpath)); 810 } 811 812 @Override @DefinedBy(Api.COMPILER) 813 public Iterable<? extends File> getLocation(Location location) { 814 nullCheck(location); 815 return asFiles(locations.getLocation(location)); 816 } 817 818 @Override @DefinedBy(Api.COMPILER) 819 public Iterable<? extends Path> getLocationAsPaths(Location location) { 820 nullCheck(location); 821 return locations.getLocation(location); 822 } 823 824 private Path getClassOutDir() { 825 return locations.getOutputLocation(CLASS_OUTPUT); 826 } 827 828 private Path getSourceOutDir() { 829 return locations.getOutputLocation(SOURCE_OUTPUT); 830 } 831 832 @Override @DefinedBy(Api.COMPILER) 833 public Path asPath(FileObject file) { 834 if (file instanceof PathFileObject) { 835 return ((PathFileObject) file).path; 836 } else 837 throw new IllegalArgumentException(file.getName()); 838 } 839 840 /** 841 * Enforces the specification of a "relative" name as used in 842 * {@linkplain #getFileForInput(Location,String,String) 843 * getFileForInput}. This method must follow the rules defined in 844 * that method, do not make any changes without consulting the 845 * specification. 846 */ 847 protected static boolean isRelativeUri(URI uri) { 848 if (uri.isAbsolute()) 849 return false; 850 String path = uri.normalize().getPath(); 851 if (path.length() == 0 /* isEmpty() is mustang API */) 852 return false; 853 if (!path.equals(uri.getPath())) // implicitly checks for embedded . and .. 854 return false; 855 if (path.startsWith("/") || path.startsWith("./") || path.startsWith("../")) 856 return false; 857 return true; 858 } 859 860 // Convenience method 861 protected static boolean isRelativeUri(String u) { 862 try { 863 return isRelativeUri(new URI(u)); 864 } catch (URISyntaxException e) { 865 return false; 866 } 867 } 868 869 /** 870 * Converts a relative file name to a relative URI. This is 871 * different from File.toURI as this method does not canonicalize 872 * the file before creating the URI. Furthermore, no schema is 873 * used. 874 * @param file a relative file name 875 * @return a relative URI 876 * @throws IllegalArgumentException if the file name is not 877 * relative according to the definition given in {@link 878 * javax.tools.JavaFileManager#getFileForInput} 879 */ 880 public static String getRelativeName(File file) { 881 if (!file.isAbsolute()) { 882 String result = file.getPath().replace(File.separatorChar, '/'); 883 if (isRelativeUri(result)) 884 return result; 885 } 886 throw new IllegalArgumentException("Invalid relative path: " + file); 887 } 888 889 /** 890 * Get a detail message from an IOException. 891 * Most, but not all, instances of IOException provide a non-null result 892 * for getLocalizedMessage(). But some instances return null: in these 893 * cases, fallover to getMessage(), and if even that is null, return the 894 * name of the exception itself. 895 * @param e an IOException 896 * @return a string to include in a compiler diagnostic 897 */ 898 public static String getMessage(IOException e) { 899 String s = e.getLocalizedMessage(); 900 if (s != null) 901 return s; 902 s = e.getMessage(); 903 if (s != null) 904 return s; 905 return e.toString(); 906 } 907 908 /* Converters between files and paths. 909 * These are temporary until we can update the StandardJavaFileManager API. 910 */ 911 912 private static Iterable<Path> asPaths(final Iterable<? extends File> files) { 913 if (files == null) 914 return null; 915 916 return () -> new Iterator<Path>() { 917 Iterator<? extends File> iter = files.iterator(); 918 919 @Override 920 public boolean hasNext() { 921 return iter.hasNext(); 922 } 923 924 @Override 925 public Path next() { 926 return iter.next().toPath(); 927 } 928 }; 929 } 930 931 private static Iterable<File> asFiles(final Iterable<? extends Path> paths) { 932 if (paths == null) 933 return null; 934 935 return () -> new Iterator<File>() { 936 Iterator<? extends Path> iter = paths.iterator(); 937 938 @Override 939 public boolean hasNext() { 940 return iter.hasNext(); 941 } 942 943 @Override 944 public File next() { 945 try { 946 return iter.next().toFile(); 947 } catch (UnsupportedOperationException e) { 948 throw new IllegalStateException(e); 949 } 950 } 951 }; 952 } 953} 954