Mon, 11 Apr 2011 16:31:22 -0700
Merge
1 /*
2 * Copyright (c) 2009, 2011, 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 */
26 package com.sun.tools.javac.nio;
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;
60 import static java.nio.file.FileVisitOption.*;
61 import static javax.tools.StandardLocation.*;
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;
69 import static com.sun.tools.javac.main.OptionName.*;
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
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.
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;
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 }
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 }
133 @Override
134 public FileSystem getDefaultFileSystem() {
135 if (defaultFileSystem == null)
136 defaultFileSystem = FileSystems.getDefault();
137 return defaultFileSystem;
138 }
140 @Override
141 public void setDefaultFileSystem(FileSystem fs) {
142 defaultFileSystem = fs;
143 }
145 @Override
146 public void flush() throws IOException {
147 contentCache.clear();
148 }
150 @Override
151 public void close() throws IOException {
152 for (FileSystem fs: fileSystems.values())
153 fs.close();
154 }
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 }
171 return getClassLoader(lb.toArray(new URL[lb.size()]));
172 }
174 @Override
175 public boolean isDefaultBootClassPath() {
176 return searchPaths.isDefaultBootClassPath();
177 }
179 // <editor-fold defaultstate="collapsed" desc="Location handling">
181 public boolean hasLocation(Location location) {
182 return (getLocation(location) != null);
183 }
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 }
196 private Path getOutputLocation(Location location) {
197 Iterable<? extends Path> paths = getLocation(location);
198 return (paths == null ? null : paths.iterator().next());
199 }
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 }
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 }
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 }
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 }
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;
274 private Map<Location, PathsForLocation> pathsForLocation;
275 private Paths searchPaths;
277 private static class PathsForLocation extends LinkedHashSet<Path> {
278 private static final long serialVersionUID = 6788510222394486733L;
279 }
281 // </editor-fold>
283 // <editor-fold defaultstate="collapsed" desc="FileObject handling">
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 }
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 }
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);
312 Iterable<? extends Path> paths = getLocation(location);
313 if (paths == null)
314 return List.nil();
315 ListBuffer<JavaFileObject> results = new ListBuffer<JavaFileObject>();
317 for (Path path : paths)
318 list(path, packageName, kinds, recurse, results);
320 return results.toList();
321 }
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;
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;
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 }
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 }
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 }
409 @Override
410 public Iterable<? extends JavaFileObject> getJavaFileObjects(Path... paths) {
411 return getJavaFileObjectsFromPaths(Arrays.asList(nullCheck(paths)));
412 }
414 @Override
415 public JavaFileObject getJavaFileForInput(Location location,
416 String className, Kind kind) throws IOException {
417 return getFileForInput(location, getRelativePath(className, kind));
418 }
420 @Override
421 public FileObject getFileForInput(Location location,
422 String packageName, String relativeName) throws IOException {
423 return getFileForInput(location, getRelativePath(packageName, relativeName));
424 }
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 }
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 }
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 }
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 }
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 }
484 }
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 }
495 if (!(fo instanceof PathFileObject))
496 throw new IllegalArgumentException(fo.getClass().getName());
498 return ((PathFileObject) fo).inferBinaryName(paths);
499 }
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 }
510 private Map<Path,FileSystem> fileSystems;
512 // </editor-fold>
514 // <editor-fold defaultstate="collapsed" desc="Utility methods">
516 private static String getRelativePath(String className, Kind kind) {
517 return className.replace(".", "/") + kind.extension;
518 }
520 private static String getRelativePath(String packageName, String relativeName) {
521 return packageName.replace(".", "/") + relativeName;
522 }
524 private static String getBaseName(String relativePath) {
525 int lastSep = relativePath.lastIndexOf("/");
526 return relativePath.substring(lastSep + 1); // safe if "/" not found
527 }
529 private static boolean isDirectory(Path path) throws IOException {
530 BasicFileAttributes attrs = Files.readAttributes(path, BasicFileAttributes.class);
531 return attrs.isDirectory();
532 }
534 private static Path getPath(FileSystem fs, String relativePath) {
535 return fs.getPath(relativePath.replace("/", fs.getSeparator()));
536 }
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 }
544 // </editor-fold>
546 }