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