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