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