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 }