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 (fname.endsWith("/"))
 335                 fname = fname.substring(0, fname.length() - 1);
 336             if (Files.isDirectory(f)) {
 337                 if (recurse && SourceVersion.isIdentifier(fname)) {
 338                     listDirectory(directory,
 339                                   new RelativeDirectory(subdirectory, fname),
 340                                   fileKinds,
 341                                   recurse,
 342                                   resultList);
 343                 }
 344             } else {
 345                 if (isValidFile(fname, fileKinds)) {
 346                     JavaFileObject fe =
 347                         new RegularFileObject(this, fname, d.resolve(fname));
 348                     resultList.append(fe);
 349                 }
 350             }
 351         }
 352     }
 353 
 354     /**
 355      * Insert all files in subdirectory subdirectory of archive archive
 356      * which match fileKinds into resultList
 357      */
 358     private void listArchive(Archive archive,
 359                                RelativeDirectory subdirectory,
 360                                Set<JavaFileObject.Kind> fileKinds,
 361                                boolean recurse,
 362                                ListBuffer<JavaFileObject> resultList) {
 363         // Get the files directly in the subdir
 364         List<String> files = archive.getFiles(subdirectory);
 365         if (files != null) {
 366             for (; !files.isEmpty(); files = files.tail) {
 367                 String file = files.head;
 368                 if (isValidFile(file, fileKinds)) {
 369                     resultList.append(archive.getFileObject(subdirectory, file));
 370                 }
 371             }
 372         }
 373         if (recurse) {
 374             for (RelativeDirectory s: archive.getSubdirectories()) {
 375                 if (subdirectory.contains(s)) {
 376                     // Because the archive map is a flat list of directories,
 377                     // the enclosing loop will pick up all child subdirectories.
 378                     // Therefore, there is no need to recurse deeper.
 379                     listArchive(archive, s, fileKinds, false, resultList);
 380                 }
 381             }
 382         }
 383     }
 384 
 385     /**
 386      * container is a directory, a zip file, or a non-existant path.
 387      * Insert all files in subdirectory subdirectory of container which
 388      * match fileKinds into resultList
 389      */
 390     private void listContainer(Path container,
 391                                RelativeDirectory subdirectory,
 392                                Set<JavaFileObject.Kind> fileKinds,
 393                                boolean recurse,
 394                                ListBuffer<JavaFileObject> resultList) {
 395         Archive archive = archives.get(container);
 396         if (archive == null) {
 397             // Very temporary and obnoxious interim hack
 398             if (container.endsWith("bootmodules.jimage")) {
 399                 System.err.println("Warning: reference to bootmodules.jimage replaced by jrt:");
 400                 container = Locations.JRT_MARKER_FILE;
 401             } else if (container.getFileName().toString().endsWith(".jimage")) {
 402                 System.err.println("Warning: reference to " + container + " ignored");
 403                 return;
 404             }
 405 
 406             // archives are not created for directories or jrt: images
 407             if (container == Locations.JRT_MARKER_FILE) {
 408                 try {
 409                     listJRTImage(subdirectory,
 410                             fileKinds,
 411                             recurse,
 412                             resultList);
 413                 } catch (IOException ex) {
 414                     ex.printStackTrace(System.err);
 415                     log.error("error.reading.file", container, getMessage(ex));
 416                 }
 417                 return;
 418             }
 419 
 420             if  (fsInfo.isDirectory(container)) {
 421                 listDirectory(container,
 422                               subdirectory,
 423                               fileKinds,
 424                               recurse,
 425                               resultList);
 426                 return;
 427             }
 428 
 429             // Not a directory; either a file or non-existant, create the archive
 430             try {
 431                 archive = openArchive(container);
 432             } catch (IOException ex) {
 433                 log.error("error.reading.file", container, getMessage(ex));
 434                 return;
 435             }
 436         }
 437         listArchive(archive,
 438                     subdirectory,
 439                     fileKinds,
 440                     recurse,
 441                     resultList);
 442     }
 443 
 444     private boolean isValidFile(String s, Set<JavaFileObject.Kind> fileKinds) {
 445         JavaFileObject.Kind kind = getKind(s);
 446         return fileKinds.contains(kind);
 447     }
 448 
 449     private static final boolean fileSystemIsCaseSensitive =
 450         File.separatorChar == '/';
 451 
 452     /** Hack to make Windows case sensitive. Test whether given path
 453      *  ends in a string of characters with the same case as given name.
 454      *  Ignore file separators in both path and name.
 455      */
 456     private boolean caseMapCheck(Path f, RelativePath name) {
 457         if (fileSystemIsCaseSensitive) return true;
 458         // Note that toRealPath() returns the case-sensitive
 459         // spelled file name.
 460         String path;
 461         char sep;
 462         try {
 463             path = f.toRealPath(LinkOption.NOFOLLOW_LINKS).toString();
 464             sep = f.getFileSystem().getSeparator().charAt(0);
 465         } catch (IOException ex) {
 466             return false;
 467         }
 468         char[] pcs = path.toCharArray();
 469         char[] ncs = name.path.toCharArray();
 470         int i = pcs.length - 1;
 471         int j = ncs.length - 1;
 472         while (i >= 0 && j >= 0) {
 473             while (i >= 0 && pcs[i] == sep) i--;
 474             while (j >= 0 && ncs[j] == '/') j--;
 475             if (i >= 0 && j >= 0) {
 476                 if (pcs[i] != ncs[j]) return false;
 477                 i--;
 478                 j--;
 479             }
 480         }
 481         return j < 0;
 482     }
 483 
 484     /**
 485      * An archive provides a flat directory structure of a ZipFile by
 486      * mapping directory names to lists of files (basenames).
 487      */
 488     public interface Archive {
 489         void close() throws IOException;
 490 
 491         boolean contains(RelativePath name);
 492 
 493         JavaFileObject getFileObject(RelativeDirectory subdirectory, String file);
 494 
 495         List<String> getFiles(RelativeDirectory subdirectory);
 496 
 497         Set<RelativeDirectory> getSubdirectories();
 498     }
 499 
 500     public class MissingArchive implements Archive {
 501         final Path zipFileName;
 502         public MissingArchive(Path name) {
 503             zipFileName = name;
 504         }
 505         @Override
 506         public boolean contains(RelativePath name) {
 507             return false;
 508         }
 509 
 510         @Override
 511         public void close() {
 512         }
 513 
 514         @Override
 515         public JavaFileObject getFileObject(RelativeDirectory subdirectory, String file) {
 516             return null;
 517         }
 518 
 519         @Override
 520         public List<String> getFiles(RelativeDirectory subdirectory) {
 521             return List.nil();
 522         }
 523 
 524         @Override
 525         public Set<RelativeDirectory> getSubdirectories() {
 526             return Collections.emptySet();
 527         }
 528 
 529         @Override
 530         public String toString() {
 531             return "MissingArchive[" + zipFileName + "]";
 532         }
 533     }
 534 
 535     /** A directory of zip files already opened.
 536      */
 537     Map<Path, Archive> archives = new HashMap<>();
 538 
 539     /*
 540      * This method looks for a ZipFormatException and takes appropriate
 541      * evasive action. If there is a failure in the fast mode then we
 542      * fail over to the platform zip, and allow it to deal with a potentially
 543      * non compliant zip file.
 544      */
 545     protected Archive openArchive(Path zipFilename) throws IOException {
 546         try {
 547             return openArchive(zipFilename, contextUseOptimizedZip);
 548         } catch (IOException ioe) {
 549             if (ioe instanceof ZipFileIndex.ZipFormatException) {
 550                 return openArchive(zipFilename, false);
 551             } else {
 552                 throw ioe;
 553             }
 554         }
 555     }
 556 
 557     /** Open a new zip file directory, and cache it.
 558      */
 559     private Archive openArchive(Path zipFileName, boolean useOptimizedZip) throws IOException {
 560         Archive archive;
 561         try {
 562 
 563             ZipFile zdir = null;
 564 
 565             boolean usePreindexedCache = false;
 566             String preindexCacheLocation = null;
 567 
 568             if (!useOptimizedZip) {
 569                 zdir = new ZipFile(zipFileName.toFile());
 570             } else {
 571                 usePreindexedCache = options.isSet("usezipindex");
 572                 preindexCacheLocation = options.get("java.io.tmpdir");
 573                 String optCacheLoc = options.get("cachezipindexdir");
 574 
 575                 if (optCacheLoc != null && optCacheLoc.length() != 0) {
 576                     if (optCacheLoc.startsWith("\"")) {
 577                         if (optCacheLoc.endsWith("\"")) {
 578                             optCacheLoc = optCacheLoc.substring(1, optCacheLoc.length() - 1);
 579                         }
 580                         else {
 581                             optCacheLoc = optCacheLoc.substring(1);
 582                         }
 583                     }
 584 
 585                     File cacheDir = new File(optCacheLoc);
 586                     if (cacheDir.exists() && cacheDir.canWrite()) {
 587                         preindexCacheLocation = optCacheLoc;
 588                         if (!preindexCacheLocation.endsWith("/") &&
 589                             !preindexCacheLocation.endsWith(File.separator)) {
 590                             preindexCacheLocation += File.separator;
 591                         }
 592                     }
 593                 }
 594             }
 595 
 596                 if (!useOptimizedZip) {
 597                     archive = new ZipArchive(this, zdir);
 598                 } else {
 599                     archive = new ZipFileIndexArchive(this,
 600                                     zipFileIndexCache.getZipFileIndex(zipFileName,
 601                                     null,
 602                                     usePreindexedCache,
 603                                     preindexCacheLocation,
 604                                     options.isSet("writezipindexfiles")));
 605                 }
 606         } catch (FileNotFoundException | NoSuchFileException ex) {
 607             archive = new MissingArchive(zipFileName);
 608         } catch (ZipFileIndex.ZipFormatException zfe) {
 609             throw zfe;
 610         } catch (IOException ex) {
 611             if (Files.exists(zipFileName))
 612                 log.error("error.reading.file", zipFileName, getMessage(ex));
 613             archive = new MissingArchive(zipFileName);
 614         }
 615 
 616         archives.put(zipFileName, archive);
 617         return archive;
 618     }
 619 
 620     /** Flush any output resources.
 621      */
 622     @Override @DefinedBy(Api.COMPILER)
 623     public void flush() {
 624         contentCache.clear();
 625     }
 626 
 627     /**
 628      * Close the JavaFileManager, releasing resources.
 629      */
 630     @Override @DefinedBy(Api.COMPILER)
 631     public void close() {
 632         for (Iterator<Archive> i = archives.values().iterator(); i.hasNext(); ) {
 633             Archive a = i.next();
 634             i.remove();
 635             try {
 636                 a.close();
 637             } catch (IOException ignore) {
 638             }
 639         }
 640     }
 641 
 642     @Override @DefinedBy(Api.COMPILER)
 643     public ClassLoader getClassLoader(Location location) {
 644         nullCheck(location);
 645         Iterable<? extends File> path = getLocation(location);
 646         if (path == null)
 647             return null;
 648         ListBuffer<URL> lb = new ListBuffer<>();
 649         for (File f: path) {
 650             try {
 651                 lb.append(f.toURI().toURL());
 652             } catch (MalformedURLException e) {
 653                 throw new AssertionError(e);
 654             }
 655         }
 656 
 657         return getClassLoader(lb.toArray(new URL[lb.size()]));
 658     }
 659 
 660     @Override @DefinedBy(Api.COMPILER)
 661     public Iterable<JavaFileObject> list(Location location,
 662                                          String packageName,
 663                                          Set<JavaFileObject.Kind> kinds,
 664                                          boolean recurse)
 665         throws IOException
 666     {
 667         // validatePackageName(packageName);
 668         nullCheck(packageName);
 669         nullCheck(kinds);
 670 
 671         Iterable<? extends Path> path = getLocationAsPaths(location);
 672         if (path == null)
 673             return List.nil();
 674         RelativeDirectory subdirectory = RelativeDirectory.forPackage(packageName);
 675         ListBuffer<JavaFileObject> results = new ListBuffer<>();
 676 
 677         for (Path directory : path)
 678             listContainer(directory, subdirectory, kinds, recurse, results);
 679         return results.toList();
 680     }
 681 
 682     @Override @DefinedBy(Api.COMPILER)
 683     public String inferBinaryName(Location location, JavaFileObject file) {
 684         Objects.requireNonNull(file);
 685         Objects.requireNonNull(location);
 686         // Need to match the path semantics of list(location, ...)
 687         Iterable<? extends Path> path = getLocationAsPaths(location);
 688         if (path == null) {
 689             return null;
 690         }
 691 
 692         if (file instanceof BaseFileObject) {
 693             return ((BaseFileObject) file).inferBinaryName(path);
 694         } else if (file instanceof PathFileObject) {
 695             return ((PathFileObject) file).inferBinaryName(null);
 696         } else
 697             throw new IllegalArgumentException(file.getClass().getName());
 698     }
 699 
 700     @Override @DefinedBy(Api.COMPILER)
 701     public boolean isSameFile(FileObject a, FileObject b) {
 702         nullCheck(a);
 703         nullCheck(b);
 704         if (a instanceof PathFileObject && b instanceof PathFileObject)
 705             return ((PathFileObject) a).isSameFile((PathFileObject) b);
 706         // In time, we should phase out BaseFileObject in favor of PathFileObject
 707         if (!(a instanceof BaseFileObject  || a instanceof PathFileObject))
 708             throw new IllegalArgumentException("Not supported: " + a);
 709         if (!(b instanceof BaseFileObject || b instanceof PathFileObject))
 710             throw new IllegalArgumentException("Not supported: " + b);
 711         return a.equals(b);
 712     }
 713 
 714     @Override @DefinedBy(Api.COMPILER)
 715     public boolean hasLocation(Location location) {
 716         return getLocation(location) != null;
 717     }
 718 
 719     @Override @DefinedBy(Api.COMPILER)
 720     public JavaFileObject getJavaFileForInput(Location location,
 721                                               String className,
 722                                               JavaFileObject.Kind kind)
 723         throws IOException
 724     {
 725         nullCheck(location);
 726         // validateClassName(className);
 727         nullCheck(className);
 728         nullCheck(kind);
 729         if (!sourceOrClass.contains(kind))
 730             throw new IllegalArgumentException("Invalid kind: " + kind);
 731         return getFileForInput(location, RelativeFile.forClass(className, kind));
 732     }
 733 
 734     @Override @DefinedBy(Api.COMPILER)
 735     public FileObject getFileForInput(Location location,
 736                                       String packageName,
 737                                       String relativeName)
 738         throws IOException
 739     {
 740         nullCheck(location);
 741         // validatePackageName(packageName);
 742         nullCheck(packageName);
 743         if (!isRelativeUri(relativeName))
 744             throw new IllegalArgumentException("Invalid relative name: " + relativeName);
 745         RelativeFile name = packageName.length() == 0
 746             ? new RelativeFile(relativeName)
 747             : new RelativeFile(RelativeDirectory.forPackage(packageName), relativeName);
 748         return getFileForInput(location, name);
 749     }
 750 
 751     private JavaFileObject getFileForInput(Location location, RelativeFile name) throws IOException {
 752         Iterable<? extends Path> path = getLocationAsPaths(location);
 753         if (path == null)
 754             return null;
 755 
 756         for (Path file: path) {
 757             Archive a = archives.get(file);
 758             if (a == null) {
 759                 // archives are not created for directories or jrt: images
 760                 if (file == Locations.JRT_MARKER_FILE) {
 761                     JRTIndex.Entry e = getJRTIndex().getEntry(name.dirname());
 762                     if (symbolFileEnabled && e.ctSym.hidden)
 763                         continue;
 764                     Path p = e.files.get(name.basename());
 765                     if (p != null)
 766                         return PathFileObject.createJRTPathFileObject(this, p);
 767                     continue;
 768                 } else if (fsInfo.isDirectory(file)) {
 769                     try {
 770                         Path f = name.getFile(file);
 771                         if (Files.exists(f))
 772                             return new RegularFileObject(this, f);
 773                     } catch (InvalidPathException ignore) {
 774                     }
 775                     continue;
 776                 }
 777                 // Not a directory, create the archive
 778                 a = openArchive(file);
 779             }
 780             // Process the archive
 781             if (a.contains(name)) {
 782                 return a.getFileObject(name.dirname(), name.basename());
 783             }
 784         }
 785         return null;
 786     }
 787 
 788     @Override @DefinedBy(Api.COMPILER)
 789     public JavaFileObject getJavaFileForOutput(Location location,
 790                                                String className,
 791                                                JavaFileObject.Kind kind,
 792                                                FileObject sibling)
 793         throws IOException
 794     {
 795         nullCheck(location);
 796         // validateClassName(className);
 797         nullCheck(className);
 798         nullCheck(kind);
 799         if (!sourceOrClass.contains(kind))
 800             throw new IllegalArgumentException("Invalid kind: " + kind);
 801         return getFileForOutput(location, RelativeFile.forClass(className, kind), sibling);
 802     }
 803 
 804     @Override @DefinedBy(Api.COMPILER)
 805     public FileObject getFileForOutput(Location location,
 806                                        String packageName,
 807                                        String relativeName,
 808                                        FileObject sibling)
 809         throws IOException
 810     {
 811         nullCheck(location);
 812         // validatePackageName(packageName);
 813         nullCheck(packageName);
 814         if (!isRelativeUri(relativeName))
 815             throw new IllegalArgumentException("Invalid relative name: " + relativeName);
 816         RelativeFile name = packageName.length() == 0
 817             ? new RelativeFile(relativeName)
 818             : new RelativeFile(RelativeDirectory.forPackage(packageName), relativeName);
 819         return getFileForOutput(location, name, sibling);
 820     }
 821 
 822     private JavaFileObject getFileForOutput(Location location,
 823                                             RelativeFile fileName,
 824                                             FileObject sibling)
 825         throws IOException
 826     {
 827         Path dir;
 828         if (location == CLASS_OUTPUT) {
 829             if (getClassOutDir() != null) {
 830                 dir = getClassOutDir();
 831             } else {
 832                 Path siblingDir = null;
 833                 if (sibling != null && sibling instanceof RegularFileObject) {
 834                     siblingDir = ((RegularFileObject)sibling).file.getParent();
 835                 }
 836                 if (siblingDir == null)
 837                     return new RegularFileObject(this, Paths.get(fileName.basename()));
 838                 else
 839                     return new RegularFileObject(this, siblingDir.resolve(fileName.basename()));
 840             }
 841         } else if (location == SOURCE_OUTPUT) {
 842             dir = (getSourceOutDir() != null ? getSourceOutDir() : getClassOutDir());
 843         } else {
 844             Iterable<? extends Path> path = locations.getLocation(location);
 845             dir = null;
 846             for (Path f: path) {
 847                 dir = f;
 848                 break;
 849             }
 850         }
 851 
 852         try {
 853             Path file = fileName.getFile(dir); // null-safe
 854             return new RegularFileObject(this, file);
 855         } catch (InvalidPathException e) {
 856             throw new IOException("bad filename " + fileName, e);
 857         }
 858     }
 859 
 860     @Override @DefinedBy(Api.COMPILER)
 861     public Iterable<? extends JavaFileObject> getJavaFileObjectsFromFiles(
 862         Iterable<? extends File> files)
 863     {
 864         ArrayList<RegularFileObject> result;
 865         if (files instanceof Collection<?>)
 866             result = new ArrayList<>(((Collection<?>)files).size());
 867         else
 868             result = new ArrayList<>();
 869         for (File f: files)
 870             result.add(new RegularFileObject(this, nullCheck(f).toPath()));
 871         return result;
 872     }
 873 
 874     @Override @DefinedBy(Api.COMPILER)
 875     public Iterable<? extends JavaFileObject> getJavaFileObjectsFromPaths(
 876         Iterable<? extends Path> paths)
 877     {
 878         ArrayList<RegularFileObject> result;
 879         if (paths instanceof Collection<?>)
 880             result = new ArrayList<>(((Collection<?>)paths).size());
 881         else
 882             result = new ArrayList<>();
 883         for (Path p: paths)
 884             result.add(new RegularFileObject(this, nullCheck(p)));
 885         return result;
 886     }
 887 
 888     @Override @DefinedBy(Api.COMPILER)
 889     public Iterable<? extends JavaFileObject> getJavaFileObjects(File... files) {
 890         return getJavaFileObjectsFromFiles(Arrays.asList(nullCheck(files)));
 891     }
 892 
 893     @Override @DefinedBy(Api.COMPILER)
 894     public Iterable<? extends JavaFileObject> getJavaFileObjects(Path... paths) {
 895         return getJavaFileObjectsFromPaths(Arrays.asList(nullCheck(paths)));
 896     }
 897 
 898     @Override @DefinedBy(Api.COMPILER)
 899     public void setLocation(Location location,
 900                             Iterable<? extends File> searchpath)
 901         throws IOException
 902     {
 903         nullCheck(location);
 904         locations.setLocation(location, asPaths(searchpath));
 905     }
 906 
 907     @Override @DefinedBy(Api.COMPILER)
 908     public void setLocationFromPaths(Location location,
 909                             Iterable<? extends Path> searchpath)
 910         throws IOException
 911     {
 912         nullCheck(location);
 913         locations.setLocation(location, nullCheck(searchpath));
 914     }
 915 
 916     @Override @DefinedBy(Api.COMPILER)
 917     public Iterable<? extends File> getLocation(Location location) {
 918         nullCheck(location);
 919         return asFiles(locations.getLocation(location));
 920     }
 921 
 922     @Override @DefinedBy(Api.COMPILER)
 923     public Iterable<? extends Path> getLocationAsPaths(Location location) {
 924         nullCheck(location);
 925         return locations.getLocation(location);
 926     }
 927 
 928     private Path getClassOutDir() {
 929         return locations.getOutputLocation(CLASS_OUTPUT);
 930     }
 931 
 932     private Path getSourceOutDir() {
 933         return locations.getOutputLocation(SOURCE_OUTPUT);
 934     }
 935 
 936     @Override @DefinedBy(Api.COMPILER)
 937     public Path asPath(FileObject file) {
 938         if (file instanceof RegularFileObject) {
 939             return ((RegularFileObject) file).file;
 940         } else
 941             throw new IllegalArgumentException(file.getName());
 942     }
 943 
 944     /**
 945      * Enforces the specification of a "relative" name as used in
 946      * {@linkplain #getFileForInput(Location,String,String)
 947      * getFileForInput}.  This method must follow the rules defined in
 948      * that method, do not make any changes without consulting the
 949      * specification.
 950      */
 951     protected static boolean isRelativeUri(URI uri) {
 952         if (uri.isAbsolute())
 953             return false;
 954         String path = uri.normalize().getPath();
 955         if (path.length() == 0 /* isEmpty() is mustang API */)
 956             return false;
 957         if (!path.equals(uri.getPath())) // implicitly checks for embedded . and ..
 958             return false;
 959         if (path.startsWith("/") || path.startsWith("./") || path.startsWith("../"))
 960             return false;
 961         return true;
 962     }
 963 
 964     // Convenience method
 965     protected static boolean isRelativeUri(String u) {
 966         try {
 967             return isRelativeUri(new URI(u));
 968         } catch (URISyntaxException e) {
 969             return false;
 970         }
 971     }
 972 
 973     /**
 974      * Converts a relative file name to a relative URI.  This is
 975      * different from File.toURI as this method does not canonicalize
 976      * the file before creating the URI.  Furthermore, no schema is
 977      * used.
 978      * @param file a relative file name
 979      * @return a relative URI
 980      * @throws IllegalArgumentException if the file name is not
 981      * relative according to the definition given in {@link
 982      * javax.tools.JavaFileManager#getFileForInput}
 983      */
 984     public static String getRelativeName(File file) {
 985         if (!file.isAbsolute()) {
 986             String result = file.getPath().replace(File.separatorChar, '/');
 987             if (isRelativeUri(result))
 988                 return result;
 989         }
 990         throw new IllegalArgumentException("Invalid relative path: " + file);
 991     }
 992 
 993     /**
 994      * Get a detail message from an IOException.
 995      * Most, but not all, instances of IOException provide a non-null result
 996      * for getLocalizedMessage().  But some instances return null: in these
 997      * cases, fallover to getMessage(), and if even that is null, return the
 998      * name of the exception itself.
 999      * @param e an IOException
1000      * @return a string to include in a compiler diagnostic
1001      */
1002     public static String getMessage(IOException e) {
1003         String s = e.getLocalizedMessage();
1004         if (s != null)
1005             return s;
1006         s = e.getMessage();
1007         if (s != null)
1008             return s;
1009         return e.toString();
1010     }
1011 
1012     /* Converters between files and paths.
1013      * These are temporary until we can update the StandardJavaFileManager API.
1014      */
1015 
1016     private static Iterable<Path> asPaths(final Iterable<? extends File> files) {
1017         if (files == null)
1018             return null;
1019 
1020         return () -> new Iterator<Path>() {
1021             Iterator<? extends File> iter = files.iterator();
1022 
1023             @Override
1024             public boolean hasNext() {
1025                 return iter.hasNext();
1026             }
1027 
1028             @Override
1029             public Path next() {
1030                 return iter.next().toPath();
1031             }
1032         };
1033     }
1034 
1035     private static Iterable<File> asFiles(final Iterable<? extends Path> paths) {
1036         if (paths == null)
1037             return null;
1038 
1039         return () -> new Iterator<File>() {
1040             Iterator<? extends Path> iter = paths.iterator();
1041 
1042             @Override
1043             public boolean hasNext() {
1044                 return iter.hasNext();
1045             }
1046 
1047             @Override
1048             public File next() {
1049                 try {
1050                     return iter.next().toFile();
1051                 } catch (UnsupportedOperationException e) {
1052                     throw new IllegalStateException(e);
1053                 }
1054             }
1055         };
1056     }
1057 }