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