1 /*
   2  * Copyright (c) 2009, 2010, 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.nio;
  27 
  28 
  29 import java.io.File;
  30 import java.io.FileNotFoundException;
  31 import java.io.IOException;
  32 import java.net.MalformedURLException;
  33 import java.net.URL;
  34 import java.nio.charset.Charset;
  35 import java.nio.file.Files;
  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.Path;
  41 import java.nio.file.SimpleFileVisitor;
  42 import java.nio.file.attribute.BasicFileAttributes;
  43 import java.util.ArrayList;
  44 import java.util.Arrays;
  45 import java.util.Collection;
  46 import java.util.Collections;
  47 import java.util.EnumSet;
  48 import java.util.HashMap;
  49 import java.util.Iterator;
  50 import java.util.LinkedHashSet;
  51 import java.util.Map;
  52 import java.util.Set;
  53 import javax.lang.model.SourceVersion;
  54 import javax.tools.FileObject;
  55 import javax.tools.JavaFileManager;
  56 import javax.tools.JavaFileObject;
  57 import javax.tools.JavaFileObject.Kind;
  58 import javax.tools.StandardLocation;
  59 
  60 import static java.nio.file.FileVisitOption.*;
  61 import static javax.tools.StandardLocation.*;
  62 
  63 import com.sun.tools.javac.file.Paths;
  64 import com.sun.tools.javac.util.BaseFileManager;
  65 import com.sun.tools.javac.util.Context;
  66 import com.sun.tools.javac.util.List;
  67 import com.sun.tools.javac.util.ListBuffer;
  68 
  69 import static com.sun.tools.javac.main.OptionName.*;
  70 
  71 
  72 // NOTE the imports carefully for this compilation unit.
  73 //
  74 // Path:  java.nio.file.Path -- the new NIO type for which this file manager exists
  75 //
  76 // Paths: com.sun.tools.javac.file.Paths -- legacy javac type for handling path options
  77 //      The other Paths (java.nio.file.Paths) is not used
  78 
  79 // NOTE this and related classes depend on new API in JDK 7.
  80 // This requires special handling while bootstrapping the JDK build,
  81 // when these classes might not yet have been compiled. To workaround
  82 // this, the build arranges to make stubs of these classes available
  83 // when compiling this and related classes. The set of stub files
  84 // is specified in make/build.properties.
  85 
  86 /**
  87  *  Implementation of PathFileManager: a JavaFileManager based on the use
  88  *  of java.nio.file.Path.
  89  *
  90  *  <p>Just as a Path is somewhat analagous to a File, so too is this
  91  *  JavacPathFileManager analogous to JavacFileManager, as it relates to the
  92  *  support of FileObjects based on File objects (i.e. just RegularFileObject,
  93  *  not ZipFileObject and its variants.)
  94  *
  95  *  <p>The default values for the standard locations supported by this file
  96  *  manager are the same as the default values provided by JavacFileManager --
  97  *  i.e. as determined by the javac.file.Paths class. To override these values,
  98  *  call {@link #setLocation}.
  99  *
 100  *  <p>To reduce confusion with Path objects, the locations such as "class path",
 101  *  "source path", etc, are generically referred to here as "search paths".
 102  *
 103  *  <p><b>This is NOT part of any supported API.
 104  *  If you write code that depends on this, you do so at your own risk.
 105  *  This code and its internal interfaces are subject to change or
 106  *  deletion without notice.</b>
 107  */
 108 public class JavacPathFileManager extends BaseFileManager implements PathFileManager {
 109     protected FileSystem defaultFileSystem;
 110 
 111     /**
 112      * Create a JavacPathFileManager using a given context, optionally registering
 113      * it as the JavaFileManager for that context.
 114      */
 115     public JavacPathFileManager(Context context, boolean register, Charset charset) {
 116         super(charset);
 117         if (register)
 118             context.put(JavaFileManager.class, this);
 119         pathsForLocation = new HashMap<Location, PathsForLocation>();
 120         fileSystems = new HashMap<Path,FileSystem>();
 121         setContext(context);
 122     }
 123 
 124     /**
 125      * Set the context for JavacPathFileManager.
 126      */
 127     @Override
 128     protected void setContext(Context context) {
 129         super.setContext(context);
 130         searchPaths = Paths.instance(context);
 131     }
 132 
 133     @Override
 134     public FileSystem getDefaultFileSystem() {
 135         if (defaultFileSystem == null)
 136             defaultFileSystem = FileSystems.getDefault();
 137         return defaultFileSystem;
 138     }
 139 
 140     @Override
 141     public void setDefaultFileSystem(FileSystem fs) {
 142         defaultFileSystem = fs;
 143     }
 144 
 145     @Override
 146     public void flush() throws IOException {
 147         contentCache.clear();
 148     }
 149 
 150     @Override
 151     public void close() throws IOException {
 152         for (FileSystem fs: fileSystems.values())
 153             fs.close();
 154     }
 155 
 156     @Override
 157     public ClassLoader getClassLoader(Location location) {
 158         nullCheck(location);
 159         Iterable<? extends Path> path = getLocation(location);
 160         if (path == null)
 161             return null;
 162         ListBuffer<URL> lb = new ListBuffer<URL>();
 163         for (Path p: path) {
 164             try {
 165                 lb.append(p.toUri().toURL());
 166             } catch (MalformedURLException e) {
 167                 throw new AssertionError(e);
 168             }
 169         }
 170 
 171         return getClassLoader(lb.toArray(new URL[lb.size()]));
 172     }
 173 
 174     @Override
 175     public boolean isDefaultBootClassPath() {
 176         return searchPaths.isDefaultBootClassPath();
 177     }
 178 
 179     // <editor-fold defaultstate="collapsed" desc="Location handling">
 180 
 181     public boolean hasLocation(Location location) {
 182         return (getLocation(location) != null);
 183     }
 184 
 185     public Iterable<? extends Path> getLocation(Location location) {
 186         nullCheck(location);
 187         lazyInitSearchPaths();
 188         PathsForLocation path = pathsForLocation.get(location);
 189         if (path == null && !pathsForLocation.containsKey(location)) {
 190             setDefaultForLocation(location);
 191             path = pathsForLocation.get(location);
 192         }
 193         return path;
 194     }
 195 
 196     private Path getOutputLocation(Location location) {
 197         Iterable<? extends Path> paths = getLocation(location);
 198         return (paths == null ? null : paths.iterator().next());
 199     }
 200 
 201     public void setLocation(Location location, Iterable<? extends Path> searchPath)
 202             throws IOException
 203     {
 204         nullCheck(location);
 205         lazyInitSearchPaths();
 206         if (searchPath == null) {
 207             setDefaultForLocation(location);
 208         } else {
 209             if (location.isOutputLocation())
 210                 checkOutputPath(searchPath);
 211             PathsForLocation pl = new PathsForLocation();
 212             for (Path p: searchPath)
 213                 pl.add(p);  // TODO -Xlint:path warn if path not found
 214             pathsForLocation.put(location, pl);
 215         }
 216     }
 217 
 218     private void checkOutputPath(Iterable<? extends Path> searchPath) throws IOException {
 219         Iterator<? extends Path> pathIter = searchPath.iterator();
 220         if (!pathIter.hasNext())
 221             throw new IllegalArgumentException("empty path for directory");
 222         Path path = pathIter.next();
 223         if (pathIter.hasNext())
 224             throw new IllegalArgumentException("path too long for directory");
 225         if (!isDirectory(path))
 226             throw new IOException(path + ": not a directory");
 227     }
 228 
 229     private void setDefaultForLocation(Location locn) {
 230         Collection<File> files = null;
 231         if (locn instanceof StandardLocation) {
 232             switch ((StandardLocation) locn) {
 233                 case CLASS_PATH:
 234                     files = searchPaths.userClassPath();
 235                     break;
 236                 case PLATFORM_CLASS_PATH:
 237                     files = searchPaths.bootClassPath();
 238                     break;
 239                 case SOURCE_PATH:
 240                     files = searchPaths.sourcePath();
 241                     break;
 242                 case CLASS_OUTPUT: {
 243                     String arg = options.get(D);
 244                     files = (arg == null ? null : Collections.singleton(new File(arg)));
 245                     break;
 246                 }
 247                 case SOURCE_OUTPUT: {
 248                     String arg = options.get(S);
 249                     files = (arg == null ? null : Collections.singleton(new File(arg)));
 250                     break;
 251                 }
 252             }
 253         }
 254 
 255         PathsForLocation pl = new PathsForLocation();
 256         if (files != null) {
 257             for (File f: files)
 258                 pl.add(f.toPath());
 259         }
 260         pathsForLocation.put(locn, pl);
 261     }
 262 
 263     private void lazyInitSearchPaths() {
 264         if (!inited) {
 265             setDefaultForLocation(PLATFORM_CLASS_PATH);
 266             setDefaultForLocation(CLASS_PATH);
 267             setDefaultForLocation(SOURCE_PATH);
 268             inited = true;
 269         }
 270     }
 271     // where
 272         private boolean inited = false;
 273 
 274     private Map<Location, PathsForLocation> pathsForLocation;
 275     private Paths searchPaths;
 276 
 277     private static class PathsForLocation extends LinkedHashSet<Path> {
 278         private static final long serialVersionUID = 6788510222394486733L;
 279     }
 280 
 281     // </editor-fold>
 282 
 283     // <editor-fold defaultstate="collapsed" desc="FileObject handling">
 284 
 285     @Override
 286     public Path getPath(FileObject fo) {
 287         nullCheck(fo);
 288         if (!(fo instanceof PathFileObject))
 289             throw new IllegalArgumentException();
 290         return ((PathFileObject) fo).getPath();
 291     }
 292 
 293     @Override
 294     public boolean isSameFile(FileObject a, FileObject b) {
 295         nullCheck(a);
 296         nullCheck(b);
 297         if (!(a instanceof PathFileObject))
 298             throw new IllegalArgumentException("Not supported: " + a);
 299         if (!(b instanceof PathFileObject))
 300             throw new IllegalArgumentException("Not supported: " + b);
 301         return ((PathFileObject) a).isSameFile((PathFileObject) b);
 302     }
 303 
 304     @Override
 305     public Iterable<JavaFileObject> list(Location location,
 306             String packageName, Set<Kind> kinds, boolean recurse)
 307             throws IOException {
 308         // validatePackageName(packageName);
 309         nullCheck(packageName);
 310         nullCheck(kinds);
 311 
 312         Iterable<? extends Path> paths = getLocation(location);
 313         if (paths == null)
 314             return List.nil();
 315         ListBuffer<JavaFileObject> results = new ListBuffer<JavaFileObject>();
 316 
 317         for (Path path : paths)
 318             list(path, packageName, kinds, recurse, results);
 319 
 320         return results.toList();
 321     }
 322 
 323     private void list(Path path, String packageName, final Set<Kind> kinds,
 324             boolean recurse, final ListBuffer<JavaFileObject> results)
 325             throws IOException {
 326         if (!Files.exists(path))
 327             return;
 328 
 329         final Path pathDir;
 330         if (isDirectory(path))
 331             pathDir = path;
 332         else {
 333             FileSystem fs = getFileSystem(path);
 334             if (fs == null)
 335                 return;
 336             pathDir = fs.getRootDirectories().iterator().next();
 337         }
 338         String sep = path.getFileSystem().getSeparator();
 339         Path packageDir = packageName.isEmpty() ? pathDir
 340                 : pathDir.resolve(packageName.replace(".", sep));
 341         if (!Files.exists(packageDir))
 342             return;
 343 
 344 /* Alternate impl of list, superceded by use of Files.walkFileTree */
 345 //        Deque<Path> queue = new LinkedList<Path>();
 346 //        queue.add(packageDir);
 347 //
 348 //        Path dir;
 349 //        while ((dir = queue.poll()) != null) {
 350 //            DirectoryStream<Path> ds = dir.newDirectoryStream();
 351 //            try {
 352 //                for (Path p: ds) {
 353 //                    String name = p.getFileName().toString();
 354 //                    if (isDirectory(p)) {
 355 //                        if (recurse && SourceVersion.isIdentifier(name)) {
 356 //                            queue.add(p);
 357 //                        }
 358 //                    } else {
 359 //                        if (kinds.contains(getKind(name))) {
 360 //                            JavaFileObject fe =
 361 //                                PathFileObject.createDirectoryPathFileObject(this, p, pathDir);
 362 //                            results.append(fe);
 363 //                        }
 364 //                    }
 365 //                }
 366 //            } finally {
 367 //                ds.close();
 368 //            }
 369 //        }
 370         int maxDepth = (recurse ? Integer.MAX_VALUE : 1);
 371         Set<FileVisitOption> opts = EnumSet.of(FOLLOW_LINKS);
 372         Files.walkFileTree(packageDir, opts, maxDepth,
 373                 new SimpleFileVisitor<Path>() {
 374             @Override
 375             public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) {
 376                 Path name = dir.getFileName();
 377                 if (name == null || SourceVersion.isIdentifier(name.toString())) // JSR 292?
 378                     return FileVisitResult.CONTINUE;
 379                 else
 380                     return FileVisitResult.SKIP_SUBTREE;
 381             }
 382 
 383             @Override
 384             public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) {
 385                 if (attrs.isRegularFile() && kinds.contains(getKind(file.getFileName().toString()))) {
 386                     JavaFileObject fe =
 387                         PathFileObject.createDirectoryPathFileObject(
 388                             JavacPathFileManager.this, file, pathDir);
 389                     results.append(fe);
 390                 }
 391                 return FileVisitResult.CONTINUE;
 392             }
 393         });
 394     }
 395 
 396     @Override
 397     public Iterable<? extends JavaFileObject> getJavaFileObjectsFromPaths(
 398         Iterable<? extends Path> paths) {
 399         ArrayList<PathFileObject> result;
 400         if (paths instanceof Collection<?>)
 401             result = new ArrayList<PathFileObject>(((Collection<?>)paths).size());
 402         else
 403             result = new ArrayList<PathFileObject>();
 404         for (Path p: paths)
 405             result.add(PathFileObject.createSimplePathFileObject(this, nullCheck(p)));
 406         return result;
 407     }
 408 
 409     @Override
 410     public Iterable<? extends JavaFileObject> getJavaFileObjects(Path... paths) {
 411         return getJavaFileObjectsFromPaths(Arrays.asList(nullCheck(paths)));
 412     }
 413 
 414     @Override
 415     public JavaFileObject getJavaFileForInput(Location location,
 416             String className, Kind kind) throws IOException {
 417         return getFileForInput(location, getRelativePath(className, kind));
 418     }
 419 
 420     @Override
 421     public FileObject getFileForInput(Location location,
 422             String packageName, String relativeName) throws IOException {
 423         return getFileForInput(location, getRelativePath(packageName, relativeName));
 424     }
 425 
 426     private JavaFileObject getFileForInput(Location location, String relativePath)
 427             throws IOException {
 428         for (Path p: getLocation(location)) {
 429             if (isDirectory(p)) {
 430                 Path f = resolve(p, relativePath);
 431                 if (Files.exists(f))
 432                     return PathFileObject.createDirectoryPathFileObject(this, f, p);
 433             } else {
 434                 FileSystem fs = getFileSystem(p);
 435                 if (fs != null) {
 436                     Path file = getPath(fs, relativePath);
 437                     if (Files.exists(file))
 438                         return PathFileObject.createJarPathFileObject(this, file);
 439                 }
 440             }
 441         }
 442         return null;
 443     }
 444 
 445     @Override
 446     public JavaFileObject getJavaFileForOutput(Location location,
 447             String className, Kind kind, FileObject sibling) throws IOException {
 448         return getFileForOutput(location, getRelativePath(className, kind), sibling);
 449     }
 450 
 451     @Override
 452     public FileObject getFileForOutput(Location location, String packageName,
 453             String relativeName, FileObject sibling)
 454             throws IOException {
 455         return getFileForOutput(location, getRelativePath(packageName, relativeName), sibling);
 456     }
 457 
 458     private JavaFileObject getFileForOutput(Location location,
 459             String relativePath, FileObject sibling) {
 460         Path dir = getOutputLocation(location);
 461         if (dir == null) {
 462             if (location == CLASS_OUTPUT) {
 463                 Path siblingDir = null;
 464                 if (sibling != null && sibling instanceof PathFileObject) {
 465                     siblingDir = ((PathFileObject) sibling).getPath().getParent();
 466                 }
 467                 return PathFileObject.createSiblingPathFileObject(this,
 468                         siblingDir.resolve(getBaseName(relativePath)),
 469                         relativePath);
 470             } else if (location == SOURCE_OUTPUT) {
 471                 dir = getOutputLocation(CLASS_OUTPUT);
 472             }
 473         }
 474 
 475         Path file;
 476         if (dir != null) {
 477             file = resolve(dir, relativePath);
 478             return PathFileObject.createDirectoryPathFileObject(this, file, dir);
 479         } else {
 480             file = getPath(getDefaultFileSystem(), relativePath);
 481             return PathFileObject.createSimplePathFileObject(this, file);
 482         }
 483 
 484     }
 485 
 486     @Override
 487     public String inferBinaryName(Location location, JavaFileObject fo) {
 488         nullCheck(fo);
 489         // Need to match the path semantics of list(location, ...)
 490         Iterable<? extends Path> paths = getLocation(location);
 491         if (paths == null) {
 492             return null;
 493         }
 494 
 495         if (!(fo instanceof PathFileObject))
 496             throw new IllegalArgumentException(fo.getClass().getName());
 497 
 498         return ((PathFileObject) fo).inferBinaryName(paths);
 499     }
 500 
 501     private FileSystem getFileSystem(Path p) throws IOException {
 502         FileSystem fs = fileSystems.get(p);
 503         if (fs == null) {
 504             fs = FileSystems.newFileSystem(p, null);
 505             fileSystems.put(p, fs);
 506         }
 507         return fs;
 508     }
 509 
 510     private Map<Path,FileSystem> fileSystems;
 511 
 512     // </editor-fold>
 513 
 514     // <editor-fold defaultstate="collapsed" desc="Utility methods">
 515 
 516     private static String getRelativePath(String className, Kind kind) {
 517         return className.replace(".", "/") + kind.extension;
 518     }
 519 
 520     private static String getRelativePath(String packageName, String relativeName) {
 521         return packageName.replace(".", "/") + relativeName;
 522     }
 523 
 524     private static String getBaseName(String relativePath) {
 525         int lastSep = relativePath.lastIndexOf("/");
 526         return relativePath.substring(lastSep + 1); // safe if "/" not found
 527     }
 528 
 529     private static boolean isDirectory(Path path) throws IOException {
 530         BasicFileAttributes attrs = Files.readAttributes(path, BasicFileAttributes.class);
 531         return attrs.isDirectory();
 532     }
 533 
 534     private static Path getPath(FileSystem fs, String relativePath) {
 535         return fs.getPath(relativePath.replace("/", fs.getSeparator()));
 536     }
 537 
 538     private static Path resolve(Path base, String relativePath) {
 539         FileSystem fs = base.getFileSystem();
 540         Path rp = fs.getPath(relativePath.replace("/", fs.getSeparator()));
 541         return base.resolve(rp);
 542     }
 543 
 544     // </editor-fold>
 545 
 546 }