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> pathCache = new HashMap<>();
 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         }
 518 
 519         /**
 520          * Insert all files in subdirectory subdirectory of this archive
 521          * which match fileKinds into resultList
 522          */
 523         @Override
 524         public void list(Path userPath,
 525                          RelativeDirectory subdirectory,
 526                          Set<JavaFileObject.Kind> fileKinds,
 527                          boolean recurse,
 528                          ListBuffer<JavaFileObject> resultList) throws IOException {
 529             Path resolvedSubdirectory = resolvePath(subdirectory);
 530 
 531             if (resolvedSubdirectory == null)
 532                 return ;
 533 
 534             int maxDepth = (recurse ? Integer.MAX_VALUE : 1);
 535             Set<FileVisitOption> opts = EnumSet.of(FOLLOW_LINKS);
 536             Files.walkFileTree(resolvedSubdirectory, opts, maxDepth,
 537                     new SimpleFileVisitor<Path>() {
 538                         @Override
 539                         public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) {
 540                             if (isValid(dir.getFileName())) {
 541                                 return FileVisitResult.CONTINUE;
 542                             } else {
 543                                 return FileVisitResult.SKIP_SUBTREE;
 544                             }
 545                         }
 546 
 547                         boolean isValid(Path fileName) {
 548                             if (fileName == null) {
 549                                 return true;
 550                             } else {
 551                                 String name = fileName.toString();
 552                                 if (name.endsWith("/")) {
 553                                     name = name.substring(0, name.length() - 1);
 554                                 }
 555                                 return SourceVersion.isIdentifier(name);
 556                             }
 557                         }
 558 
 559                         @Override
 560                         public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) {
 561                             if (attrs.isRegularFile() && fileKinds.contains(getKind(file.getFileName().toString()))) {
 562                                 JavaFileObject fe = PathFileObject.forJarPath(
 563                                         JavacFileManager.this, file, archivePath);
 564                                 resultList.append(fe);
 565                             }
 566                             return FileVisitResult.CONTINUE;
 567                         }
 568                     });
 569 
 570         }
 571 
 572         @Override
 573         public JavaFileObject getFileObject(Path userPath, RelativeFile name) throws IOException {
 574             Path p = resolvePath(name);
 575             if (p != null)
 576                 return PathFileObject.forJarPath(JavacFileManager.this, p, userPath);
 577 
 578             return null;
 579         }
 580 
 581         private synchronized Path resolvePath(RelativePath path) {
 582             if (!pathCache.containsKey(path)) {
 583                 Path relativePath = path.resolveAgainst(fileSystem);
 584 
 585                 if (!Files.exists(relativePath)) {
 586                     relativePath = null;
 587                 }
 588 
 589                 pathCache.put(path, relativePath);
 590                 return relativePath;
 591             }
 592             return pathCache.get(path);
 593         }
 594 
 595         @Override
 596         public void close() throws IOException {
 597             fileSystem.close();
 598         }
 599     }
 600 
 601     /**
 602      * container is a directory, a zip file, or a non-existent path.
 603      */
 604     private boolean isValidFile(String s, Set<JavaFileObject.Kind> fileKinds) {
 605         JavaFileObject.Kind kind = getKind(s);
 606         return fileKinds.contains(kind);
 607     }
 608 
 609     private static final boolean fileSystemIsCaseSensitive =
 610         File.separatorChar == '/';
 611 
 612     /** Hack to make Windows case sensitive. Test whether given path
 613      *  ends in a string of characters with the same case as given name.
 614      *  Ignore file separators in both path and name.
 615      */
 616     private boolean caseMapCheck(Path f, RelativePath name) {
 617         if (fileSystemIsCaseSensitive) return true;
 618         // Note that toRealPath() returns the case-sensitive
 619         // spelled file name.
 620         String path;
 621         char sep;
 622         try {
 623             path = f.toRealPath(LinkOption.NOFOLLOW_LINKS).toString();
 624             sep = f.getFileSystem().getSeparator().charAt(0);
 625         } catch (IOException ex) {
 626             return false;
 627         }
 628         char[] pcs = path.toCharArray();
 629         char[] ncs = name.path.toCharArray();
 630         int i = pcs.length - 1;
 631         int j = ncs.length - 1;
 632         while (i >= 0 && j >= 0) {
 633             while (i >= 0 && pcs[i] == sep) i--;
 634             while (j >= 0 && ncs[j] == '/') j--;
 635             if (i >= 0 && j >= 0) {
 636                 if (pcs[i] != ncs[j]) return false;
 637                 i--;
 638                 j--;
 639             }
 640         }
 641         return j < 0;
 642     }
 643 
 644     /** Flush any output resources.
 645      */
 646     @Override @DefinedBy(Api.COMPILER)
 647     public void flush() {
 648         contentCache.clear();
 649     }
 650 
 651     /**
 652      * Close the JavaFileManager, releasing resources.
 653      */
 654     @Override @DefinedBy(Api.COMPILER)
 655     public void close() throws IOException {
 656         if (deferredCloseTimeout > 0) {
 657             deferredClose();
 658             return;
 659         }
 660 
 661         locations.close();
 662         for (Container container: containers.values()) {
 663             container.close();
 664         }
 665         containers.clear();
 666         contentCache.clear();
 667     }
 668 
 669     @Override @DefinedBy(Api.COMPILER)
 670     public ClassLoader getClassLoader(Location location) {
 671         checkNotModuleOrientedLocation(location);
 672         Iterable<? extends File> path = getLocation(location);
 673         if (path == null)
 674             return null;
 675         ListBuffer<URL> lb = new ListBuffer<>();
 676         for (File f: path) {
 677             try {
 678                 lb.append(f.toURI().toURL());
 679             } catch (MalformedURLException e) {
 680                 throw new AssertionError(e);
 681             }
 682         }
 683 
 684         return getClassLoader(lb.toArray(new URL[lb.size()]));
 685     }
 686 
 687     @Override @DefinedBy(Api.COMPILER)
 688     public Iterable<JavaFileObject> list(Location location,
 689                                          String packageName,
 690                                          Set<JavaFileObject.Kind> kinds,
 691                                          boolean recurse)
 692         throws IOException
 693     {
 694         checkNotModuleOrientedLocation(location);
 695         // validatePackageName(packageName);
 696         nullCheck(packageName);
 697         nullCheck(kinds);
 698 
 699         Iterable<? extends Path> path = getLocationAsPaths(location);
 700         if (path == null)
 701             return List.nil();
 702         RelativeDirectory subdirectory = RelativeDirectory.forPackage(packageName);
 703         ListBuffer<JavaFileObject> results = new ListBuffer<>();
 704 
 705         for (Path directory : path) {
 706             Container container = getContainer(directory);
 707 
 708             container.list(directory, subdirectory, kinds, recurse, results);
 709         }
 710 
 711         return results.toList();
 712     }
 713 
 714     @Override @DefinedBy(Api.COMPILER)
 715     public String inferBinaryName(Location location, JavaFileObject file) {
 716         checkNotModuleOrientedLocation(location);
 717         Objects.requireNonNull(file);
 718         // Need to match the path semantics of list(location, ...)
 719         Iterable<? extends Path> path = getLocationAsPaths(location);
 720         if (path == null) {
 721             return null;
 722         }
 723 
 724         if (file instanceof PathFileObject) {
 725             return ((PathFileObject) file).inferBinaryName(path);
 726         } else
 727             throw new IllegalArgumentException(file.getClass().getName());
 728     }
 729 
 730     @Override @DefinedBy(Api.COMPILER)
 731     public boolean isSameFile(FileObject a, FileObject b) {
 732         nullCheck(a);
 733         nullCheck(b);
 734         if (a instanceof PathFileObject && b instanceof PathFileObject)
 735             return ((PathFileObject) a).isSameFile((PathFileObject) b);
 736         return a.equals(b);
 737     }
 738 
 739     @Override @DefinedBy(Api.COMPILER)
 740     public boolean hasLocation(Location location) {
 741         nullCheck(location);
 742         return locations.hasLocation(location);
 743     }
 744 
 745     @Override @DefinedBy(Api.COMPILER)
 746     public JavaFileObject getJavaFileForInput(Location location,
 747                                               String className,
 748                                               JavaFileObject.Kind kind)
 749         throws IOException
 750     {
 751         checkNotModuleOrientedLocation(location);
 752         // validateClassName(className);
 753         nullCheck(className);
 754         nullCheck(kind);
 755         if (!sourceOrClass.contains(kind))
 756             throw new IllegalArgumentException("Invalid kind: " + kind);
 757         return getFileForInput(location, RelativeFile.forClass(className, kind));
 758     }
 759 
 760     @Override @DefinedBy(Api.COMPILER)
 761     public FileObject getFileForInput(Location location,
 762                                       String packageName,
 763                                       String relativeName)
 764         throws IOException
 765     {
 766         checkNotModuleOrientedLocation(location);
 767         // validatePackageName(packageName);
 768         nullCheck(packageName);
 769         if (!isRelativeUri(relativeName))
 770             throw new IllegalArgumentException("Invalid relative name: " + relativeName);
 771         RelativeFile name = packageName.length() == 0
 772             ? new RelativeFile(relativeName)
 773             : new RelativeFile(RelativeDirectory.forPackage(packageName), relativeName);
 774         return getFileForInput(location, name);
 775     }
 776 
 777     private JavaFileObject getFileForInput(Location location, RelativeFile name) throws IOException {
 778         Iterable<? extends Path> path = getLocationAsPaths(location);
 779         if (path == null)
 780             return null;
 781 
 782         for (Path file: path) {
 783             JavaFileObject fo = getContainer(file).getFileObject(file, name);
 784 
 785             if (fo != null) {
 786                 return fo;
 787             }
 788         }
 789         return null;
 790     }
 791 
 792     @Override @DefinedBy(Api.COMPILER)
 793     public JavaFileObject getJavaFileForOutput(Location location,
 794                                                String className,
 795                                                JavaFileObject.Kind kind,
 796                                                FileObject sibling)
 797         throws IOException
 798     {
 799         checkOutputLocation(location);
 800         // validateClassName(className);
 801         nullCheck(className);
 802         nullCheck(kind);
 803         if (!sourceOrClass.contains(kind))
 804             throw new IllegalArgumentException("Invalid kind: " + kind);
 805         return getFileForOutput(location, RelativeFile.forClass(className, kind), sibling);
 806     }
 807 
 808     @Override @DefinedBy(Api.COMPILER)
 809     public FileObject getFileForOutput(Location location,
 810                                        String packageName,
 811                                        String relativeName,
 812                                        FileObject sibling)
 813         throws IOException
 814     {
 815         checkOutputLocation(location);
 816         // validatePackageName(packageName);
 817         nullCheck(packageName);
 818         if (!isRelativeUri(relativeName))
 819             throw new IllegalArgumentException("Invalid relative name: " + relativeName);
 820         RelativeFile name = packageName.length() == 0
 821             ? new RelativeFile(relativeName)
 822             : new RelativeFile(RelativeDirectory.forPackage(packageName), relativeName);
 823         return getFileForOutput(location, name, sibling);
 824     }
 825 
 826     private JavaFileObject getFileForOutput(Location location,
 827                                             RelativeFile fileName,
 828                                             FileObject sibling)
 829         throws IOException
 830     {
 831         Path dir;
 832         if (location == CLASS_OUTPUT) {
 833             if (getClassOutDir() != null) {
 834                 dir = getClassOutDir();
 835             } else {
 836                 String baseName = fileName.basename();
 837                 if (sibling != null && sibling instanceof PathFileObject) {
 838                     return ((PathFileObject) sibling).getSibling(baseName);
 839                 } else {
 840                     Path p = getPath(baseName);
 841                     Path real = fsInfo.getCanonicalFile(p);
 842                     return PathFileObject.forSimplePath(this, real, p);
 843                 }
 844             }
 845         } else if (location == SOURCE_OUTPUT) {
 846             dir = (getSourceOutDir() != null ? getSourceOutDir() : getClassOutDir());
 847         } else {
 848             Iterable<? extends Path> path = locations.getLocation(location);
 849             dir = null;
 850             for (Path f: path) {
 851                 dir = f;
 852                 break;
 853             }
 854         }
 855 
 856         try {
 857             if (dir == null) {
 858                 dir = getPath(System.getProperty("user.dir"));
 859             }
 860             Path path = fileName.resolveAgainst(fsInfo.getCanonicalFile(dir));
 861             return PathFileObject.forDirectoryPath(this, path, dir, fileName);
 862         } catch (InvalidPathException e) {
 863             throw new IOException("bad filename " + fileName, e);
 864         }
 865     }
 866 
 867     @Override @DefinedBy(Api.COMPILER)
 868     public Iterable<? extends JavaFileObject> getJavaFileObjectsFromFiles(
 869         Iterable<? extends File> files)
 870     {
 871         ArrayList<PathFileObject> result;
 872         if (files instanceof Collection<?>)
 873             result = new ArrayList<>(((Collection<?>)files).size());
 874         else
 875             result = new ArrayList<>();
 876         for (File f: files) {
 877             Objects.requireNonNull(f);
 878             Path p = f.toPath();
 879             result.add(PathFileObject.forSimplePath(this,
 880                     fsInfo.getCanonicalFile(p), p));
 881         }
 882         return result;
 883     }
 884 
 885     @Override @DefinedBy(Api.COMPILER)
 886     public Iterable<? extends JavaFileObject> getJavaFileObjectsFromPaths(
 887         Iterable<? extends Path> paths)
 888     {
 889         ArrayList<PathFileObject> result;
 890         if (paths instanceof Collection<?>)
 891             result = new ArrayList<>(((Collection<?>)paths).size());
 892         else
 893             result = new ArrayList<>();
 894         for (Path p: paths)
 895             result.add(PathFileObject.forSimplePath(this,
 896                     fsInfo.getCanonicalFile(p), p));
 897         return result;
 898     }
 899 
 900     @Override @DefinedBy(Api.COMPILER)
 901     public Iterable<? extends JavaFileObject> getJavaFileObjects(File... files) {
 902         return getJavaFileObjectsFromFiles(Arrays.asList(nullCheck(files)));
 903     }
 904 
 905     @Override @DefinedBy(Api.COMPILER)
 906     public Iterable<? extends JavaFileObject> getJavaFileObjects(Path... paths) {
 907         return getJavaFileObjectsFromPaths(Arrays.asList(nullCheck(paths)));
 908     }
 909 
 910     @Override @DefinedBy(Api.COMPILER)
 911     public void setLocation(Location location,
 912                             Iterable<? extends File> searchpath)
 913         throws IOException
 914     {
 915         nullCheck(location);
 916         locations.setLocation(location, asPaths(searchpath));
 917     }
 918 
 919     @Override @DefinedBy(Api.COMPILER)
 920     public void setLocationFromPaths(Location location,
 921                             Collection<? extends Path> searchpath)
 922         throws IOException
 923     {
 924         nullCheck(location);
 925         locations.setLocation(location, nullCheck(searchpath));
 926     }
 927 
 928     @Override @DefinedBy(Api.COMPILER)
 929     public Iterable<? extends File> getLocation(Location location) {
 930         nullCheck(location);
 931         return asFiles(locations.getLocation(location));
 932     }
 933 
 934     @Override @DefinedBy(Api.COMPILER)
 935     public Iterable<? extends Path> getLocationAsPaths(Location location) {
 936         nullCheck(location);
 937         return locations.getLocation(location);
 938     }
 939 
 940     private Path getClassOutDir() {
 941         return locations.getOutputLocation(CLASS_OUTPUT);
 942     }
 943 
 944     private Path getSourceOutDir() {
 945         return locations.getOutputLocation(SOURCE_OUTPUT);
 946     }
 947 
 948     @Override @DefinedBy(Api.COMPILER)
 949     public Location getLocationForModule(Location location, String moduleName) throws IOException {
 950         checkModuleOrientedOrOutputLocation(location);
 951         nullCheck(moduleName);
 952         if (location == SOURCE_OUTPUT && getSourceOutDir() == null)
 953             location = CLASS_OUTPUT;
 954         return locations.getLocationForModule(location, moduleName);
 955     }
 956 
 957     @Override @DefinedBy(Api.COMPILER)
 958     public <S> ServiceLoader<S> getServiceLoader(Location location, Class<S> service) throws IOException {
 959         nullCheck(location);
 960         nullCheck(service);
 961         Module.getModule(getClass()).addUses(service);
 962         if (location.isModuleOrientedLocation()) {
 963             Collection<Path> paths = locations.getLocation(location);
 964             ModuleFinder finder = ModuleFinder.of(paths.toArray(new Path[paths.size()]));
 965             Layer bootLayer = Layer.boot();
 966             Configuration cf = bootLayer.configuration().resolveAndBind(ModuleFinder.of(), finder, Collections.emptySet());
 967             Layer layer = bootLayer.defineModulesWithOneLoader(cf, ClassLoader.getSystemClassLoader());
 968             return ServiceLoaderHelper.load(layer, service);
 969         } else {
 970             return ServiceLoader.load(service, getClassLoader(location));
 971         }
 972     }
 973 
 974     @Override @DefinedBy(Api.COMPILER)
 975     public Location getLocationForModule(Location location, JavaFileObject fo, String pkgName) throws IOException {
 976         checkModuleOrientedOrOutputLocation(location);
 977         if (!(fo instanceof PathFileObject))
 978             throw new IllegalArgumentException(fo.getName());
 979         int depth = 1; // allow 1 for filename
 980         if (pkgName != null && !pkgName.isEmpty()) {
 981             depth += 1;
 982             for (int i = 0; i < pkgName.length(); i++) {
 983                 switch (pkgName.charAt(i)) {
 984                     case '/': case '.':
 985                         depth++;
 986                 }
 987             }
 988         }
 989         Path p = Locations.normalize(((PathFileObject) fo).path);
 990         int fc = p.getNameCount();
 991         if (depth < fc) {
 992             Path root = p.getRoot();
 993             Path subpath = p.subpath(0, fc - depth);
 994             Path dir = (root == null) ? subpath : root.resolve(subpath);
 995             // need to find dir in location
 996             return locations.getLocationForModule(location, dir);
 997         } else {
 998             return null;
 999         }
1000     }
1001 
1002     @Override @DefinedBy(Api.COMPILER)
1003     public String inferModuleName(Location location) {
1004         checkNotModuleOrientedLocation(location);
1005         return locations.inferModuleName(location);
1006     }
1007 
1008     @Override @DefinedBy(Api.COMPILER)
1009     public Iterable<Set<Location>> listLocationsForModules(Location location) throws IOException {
1010         checkModuleOrientedOrOutputLocation(location);
1011         return locations.listLocationsForModules(location);
1012     }
1013 
1014     @Override @DefinedBy(Api.COMPILER)
1015     public Path asPath(FileObject file) {
1016         if (file instanceof PathFileObject) {
1017             return ((PathFileObject) file).path;
1018         } else
1019             throw new IllegalArgumentException(file.getName());
1020     }
1021 
1022     /**
1023      * Enforces the specification of a "relative" name as used in
1024      * {@linkplain #getFileForInput(Location,String,String)
1025      * getFileForInput}.  This method must follow the rules defined in
1026      * that method, do not make any changes without consulting the
1027      * specification.
1028      */
1029     protected static boolean isRelativeUri(URI uri) {
1030         if (uri.isAbsolute())
1031             return false;
1032         String path = uri.normalize().getPath();
1033         if (path.length() == 0 /* isEmpty() is mustang API */)
1034             return false;
1035         if (!path.equals(uri.getPath())) // implicitly checks for embedded . and ..
1036             return false;
1037         if (path.startsWith("/") || path.startsWith("./") || path.startsWith("../"))
1038             return false;
1039         return true;
1040     }
1041 
1042     // Convenience method
1043     protected static boolean isRelativeUri(String u) {
1044         try {
1045             return isRelativeUri(new URI(u));
1046         } catch (URISyntaxException e) {
1047             return false;
1048         }
1049     }
1050 
1051     /**
1052      * Converts a relative file name to a relative URI.  This is
1053      * different from File.toURI as this method does not canonicalize
1054      * the file before creating the URI.  Furthermore, no schema is
1055      * used.
1056      * @param file a relative file name
1057      * @return a relative URI
1058      * @throws IllegalArgumentException if the file name is not
1059      * relative according to the definition given in {@link
1060      * javax.tools.JavaFileManager#getFileForInput}
1061      */
1062     public static String getRelativeName(File file) {
1063         if (!file.isAbsolute()) {
1064             String result = file.getPath().replace(File.separatorChar, '/');
1065             if (isRelativeUri(result))
1066                 return result;
1067         }
1068         throw new IllegalArgumentException("Invalid relative path: " + file);
1069     }
1070 
1071     /**
1072      * Get a detail message from an IOException.
1073      * Most, but not all, instances of IOException provide a non-null result
1074      * for getLocalizedMessage().  But some instances return null: in these
1075      * cases, fallover to getMessage(), and if even that is null, return the
1076      * name of the exception itself.
1077      * @param e an IOException
1078      * @return a string to include in a compiler diagnostic
1079      */
1080     public static String getMessage(IOException e) {
1081         String s = e.getLocalizedMessage();
1082         if (s != null)
1083             return s;
1084         s = e.getMessage();
1085         if (s != null)
1086             return s;
1087         return e.toString();
1088     }
1089 
1090     private void checkOutputLocation(Location location) {
1091         Objects.requireNonNull(location);
1092         if (!location.isOutputLocation())
1093             throw new IllegalArgumentException("location is not an output location: " + location.getName());
1094     }
1095 
1096     private void checkModuleOrientedOrOutputLocation(Location location) {
1097         Objects.requireNonNull(location);
1098         if (!location.isModuleOrientedLocation() && !location.isOutputLocation())
1099             throw new IllegalArgumentException(
1100                     "location is not an output location or a module-oriented location: "
1101                             + location.getName());
1102     }
1103 
1104     private void checkNotModuleOrientedLocation(Location location) {
1105         Objects.requireNonNull(location);
1106         if (location.isModuleOrientedLocation())
1107             throw new IllegalArgumentException("location is module-oriented: " + location.getName());
1108     }
1109 
1110     /* Converters between files and paths.
1111      * These are temporary until we can update the StandardJavaFileManager API.
1112      */
1113 
1114     private static Iterable<Path> asPaths(final Iterable<? extends File> files) {
1115         if (files == null)
1116             return null;
1117 
1118         return () -> new Iterator<Path>() {
1119             Iterator<? extends File> iter = files.iterator();
1120 
1121             @Override
1122             public boolean hasNext() {
1123                 return iter.hasNext();
1124             }
1125 
1126             @Override
1127             public Path next() {
1128                 return iter.next().toPath();
1129             }
1130         };
1131     }
1132 
1133     private static Iterable<File> asFiles(final Iterable<? extends Path> paths) {
1134         if (paths == null)
1135             return null;
1136 
1137         return () -> new Iterator<File>() {
1138             Iterator<? extends Path> iter = paths.iterator();
1139 
1140             @Override
1141             public boolean hasNext() {
1142                 return iter.hasNext();
1143             }
1144 
1145             @Override
1146             public File next() {
1147                 try {
1148                     return iter.next().toFile();
1149                 } catch (UnsupportedOperationException e) {
1150                     throw new IllegalStateException(e);
1151                 }
1152             }
1153         };
1154     }
1155 }