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