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