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