1 /* 2 * Copyright (c) 2003, 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 */ 25 26 package com.sun.tools.javac.file; 27 28 import java.io.File; 29 import java.io.IOException; 30 import java.net.MalformedURLException; 31 import java.net.URL; 32 import java.util.HashMap; 33 import java.util.HashSet; 34 import java.util.Map; 35 import java.util.Set; 36 import java.util.Collection; 37 import java.util.Collections; 38 import java.util.LinkedHashSet; 39 import java.util.StringTokenizer; 40 import java.util.zip.ZipFile; 41 import javax.tools.JavaFileManager.Location; 42 43 import com.sun.tools.javac.code.Lint; 44 import com.sun.tools.javac.util.Context; 45 import com.sun.tools.javac.util.ListBuffer; 46 import com.sun.tools.javac.util.Log; 47 import com.sun.tools.javac.util.Options; 48 49 import static javax.tools.StandardLocation.*; 50 import static com.sun.tools.javac.main.OptionName.*; 51 52 /** This class converts command line arguments, environment variables 53 * and system properties (in File.pathSeparator-separated String form) 54 * into a boot class path, user class path, and source path (in 55 * Collection<String> form). 56 * 57 * <p><b>This is NOT part of any supported API. 58 * If you write code that depends on this, you do so at your own risk. 59 * This code and its internal interfaces are subject to change or 60 * deletion without notice.</b> 61 */ 62 public class Paths { 63 64 /** The context key for the todo list */ 65 protected static final Context.Key<Paths> pathsKey = 66 new Context.Key<Paths>(); 67 68 /** Get the Paths instance for this context. 69 * @param context the context 70 * @return the Paths instance for this context 71 */ 72 public static Paths instance(Context context) { 73 Paths instance = context.get(pathsKey); 74 if (instance == null) 75 instance = new Paths(context); 76 return instance; 77 } 78 79 /** The log to use for warning output */ 80 private Log log; 81 82 /** Collection of command-line options */ 83 private Options options; 84 85 /** Handler for -Xlint options */ 86 private Lint lint; 87 88 /** Access to (possibly cached) file info */ 89 private FSInfo fsInfo; 90 91 protected Paths(Context context) { 92 context.put(pathsKey, this); 93 pathsForLocation = new HashMap<Location,Path>(16); 94 setContext(context); 95 } 96 97 void setContext(Context context) { 98 log = Log.instance(context); 99 options = Options.instance(context); 100 lint = Lint.instance(context); 101 fsInfo = FSInfo.instance(context); 102 } 103 104 /** Whether to warn about non-existent path elements */ 105 private boolean warn; 106 107 private Map<Location, Path> pathsForLocation; 108 109 private boolean inited = false; // TODO? caching bad? 110 111 /** 112 * rt.jar as found on the default bootclass path. If the user specified a 113 * bootclasspath, null is used. 114 */ 115 private File defaultBootClassPathRtJar = null; 116 117 /** 118 * Is bootclasspath the default? 119 */ 120 private boolean isDefaultBootClassPath; 121 122 Path getPathForLocation(Location location) { 123 Path path = pathsForLocation.get(location); 124 if (path == null) 125 setPathForLocation(location, null); 126 return pathsForLocation.get(location); 127 } 128 129 void setPathForLocation(Location location, Iterable<? extends File> path) { 130 // TODO? if (inited) throw new IllegalStateException 131 // TODO: otherwise reset sourceSearchPath, classSearchPath as needed 132 Path p; 133 if (path == null) { 134 if (location == CLASS_PATH) 135 p = computeUserClassPath(); 136 else if (location == PLATFORM_CLASS_PATH) 137 p = computeBootClassPath(); // sets isDefaultBootClassPath 138 else if (location == ANNOTATION_PROCESSOR_PATH) 139 p = computeAnnotationProcessorPath(); 140 else if (location == SOURCE_PATH) 141 p = computeSourcePath(); 142 else 143 // no defaults for other paths 144 p = null; 145 } else { 146 if (location == PLATFORM_CLASS_PATH) { 147 defaultBootClassPathRtJar = null; 148 isDefaultBootClassPath = false; 149 } 150 p = new Path(); 151 for (File f: path) 152 p.addFile(f, warn); // TODO: is use of warn appropriate? 153 } 154 pathsForLocation.put(location, p); 155 } 156 157 public boolean isDefaultBootClassPath() { 158 lazy(); 159 return isDefaultBootClassPath; 160 } 161 162 protected void lazy() { 163 if (!inited) { 164 warn = lint.isEnabled(Lint.LintCategory.PATH); 165 166 pathsForLocation.put(PLATFORM_CLASS_PATH, computeBootClassPath()); 167 pathsForLocation.put(CLASS_PATH, computeUserClassPath()); 168 pathsForLocation.put(SOURCE_PATH, computeSourcePath()); 169 170 inited = true; 171 } 172 } 173 174 public Collection<File> bootClassPath() { 175 lazy(); 176 return Collections.unmodifiableCollection(getPathForLocation(PLATFORM_CLASS_PATH)); 177 } 178 public Collection<File> userClassPath() { 179 lazy(); 180 return Collections.unmodifiableCollection(getPathForLocation(CLASS_PATH)); 181 } 182 public Collection<File> sourcePath() { 183 lazy(); 184 Path p = getPathForLocation(SOURCE_PATH); 185 return p == null || p.size() == 0 186 ? null 187 : Collections.unmodifiableCollection(p); 188 } 189 190 boolean isDefaultBootClassPathRtJar(File file) { 191 return file.equals(defaultBootClassPathRtJar); 192 } 193 194 /** 195 * Split a path into its elements. Empty path elements will be ignored. 196 * @param path The path to be split 197 * @return The elements of the path 198 */ 199 private static Iterable<File> getPathEntries(String path) { 200 return getPathEntries(path, null); 201 } 202 203 /** 204 * Split a path into its elements. If emptyPathDefault is not null, all 205 * empty elements in the path, including empty elements at either end of 206 * the path, will be replaced with the value of emptyPathDefault. 207 * @param path The path to be split 208 * @param emptyPathDefault The value to substitute for empty path elements, 209 * or null, to ignore empty path elements 210 * @return The elements of the path 211 */ 212 private static Iterable<File> getPathEntries(String path, File emptyPathDefault) { 213 ListBuffer<File> entries = new ListBuffer<File>(); 214 int start = 0; 215 while (start <= path.length()) { 216 int sep = path.indexOf(File.pathSeparatorChar, start); 217 if (sep == -1) 218 sep = path.length(); 219 if (start < sep) 220 entries.add(new File(path.substring(start, sep))); 221 else if (emptyPathDefault != null) 222 entries.add(emptyPathDefault); 223 start = sep + 1; 224 } 225 return entries; 226 } 227 228 private class Path extends LinkedHashSet<File> { 229 private static final long serialVersionUID = 0; 230 231 private boolean expandJarClassPaths = false; 232 private Set<File> canonicalValues = new HashSet<File>(); 233 234 public Path expandJarClassPaths(boolean x) { 235 expandJarClassPaths = x; 236 return this; 237 } 238 239 /** What to use when path element is the empty string */ 240 private File emptyPathDefault = null; 241 242 public Path emptyPathDefault(File x) { 243 emptyPathDefault = x; 244 return this; 245 } 246 247 public Path() { super(); } 248 249 public Path addDirectories(String dirs, boolean warn) { 250 boolean prev = expandJarClassPaths; 251 expandJarClassPaths = true; 252 try { 253 if (dirs != null) 254 for (File dir : getPathEntries(dirs)) 255 addDirectory(dir, warn); 256 return this; 257 } finally { 258 expandJarClassPaths = prev; 259 } 260 } 261 262 public Path addDirectories(String dirs) { 263 return addDirectories(dirs, warn); 264 } 265 266 private void addDirectory(File dir, boolean warn) { 267 if (!dir.isDirectory()) { 268 if (warn) 269 log.warning(Lint.LintCategory.PATH, 270 "dir.path.element.not.found", dir); 271 return; 272 } 273 274 File[] files = dir.listFiles(); 275 if (files == null) 276 return; 277 278 for (File direntry : files) { 279 if (isArchive(direntry)) 280 addFile(direntry, warn); 281 } 282 } 283 284 public Path addFiles(String files, boolean warn) { 285 if (files != null) { 286 for (File file : getPathEntries(files, emptyPathDefault)) 287 addFile(file, warn); 288 } 289 return this; 290 } 291 292 public Path addFiles(String files) { 293 return addFiles(files, warn); 294 } 295 296 public void addFile(File file, boolean warn) { 297 if (contains(file)) { 298 // discard duplicates 299 return; 300 } 301 302 if (! fsInfo.exists(file)) { 303 /* No such file or directory exists */ 304 if (warn) { 305 log.warning(Lint.LintCategory.PATH, 306 "path.element.not.found", file); 307 } 308 super.add(file); 309 return; 310 } 311 312 File canonFile = fsInfo.getCanonicalFile(file); 313 if (canonicalValues.contains(canonFile)) { 314 /* Discard duplicates and avoid infinite recursion */ 315 return; 316 } 317 318 if (fsInfo.isFile(file)) { 319 /* File is an ordinary file. */ 320 if (!isArchive(file)) { 321 /* Not a recognized extension; open it to see if 322 it looks like a valid zip file. */ 323 try { 324 ZipFile z = new ZipFile(file); 325 z.close(); 326 if (warn) { 327 log.warning(Lint.LintCategory.PATH, 328 "unexpected.archive.file", file); 329 } 330 } catch (IOException e) { 331 // FIXME: include e.getLocalizedMessage in warning 332 if (warn) { 333 log.warning(Lint.LintCategory.PATH, 334 "invalid.archive.file", file); 335 } 336 return; 337 } 338 } 339 } 340 341 /* Now what we have left is either a directory or a file name 342 conforming to archive naming convention */ 343 super.add(file); 344 canonicalValues.add(canonFile); 345 346 if (expandJarClassPaths && fsInfo.isFile(file)) 347 addJarClassPath(file, warn); 348 } 349 350 // Adds referenced classpath elements from a jar's Class-Path 351 // Manifest entry. In some future release, we may want to 352 // update this code to recognize URLs rather than simple 353 // filenames, but if we do, we should redo all path-related code. 354 private void addJarClassPath(File jarFile, boolean warn) { 355 try { 356 for (File f: fsInfo.getJarClassPath(jarFile)) { 357 addFile(f, warn); 358 } 359 } catch (IOException e) { 360 log.error("error.reading.file", jarFile, JavacFileManager.getMessage(e)); 361 } 362 } 363 } 364 365 private Path computeBootClassPath() { 366 defaultBootClassPathRtJar = null; 367 Path path = new Path(); 368 369 String bootclasspathOpt = options.get(BOOTCLASSPATH); 370 String endorseddirsOpt = options.get(ENDORSEDDIRS); 371 String extdirsOpt = options.get(EXTDIRS); 372 String xbootclasspathPrependOpt = options.get(XBOOTCLASSPATH_PREPEND); 373 String xbootclasspathAppendOpt = options.get(XBOOTCLASSPATH_APPEND); 374 375 path.addFiles(xbootclasspathPrependOpt); 376 377 if (endorseddirsOpt != null) 378 path.addDirectories(endorseddirsOpt); 379 else 380 path.addDirectories(System.getProperty("java.endorsed.dirs"), false); 381 382 if (bootclasspathOpt != null) { 383 path.addFiles(bootclasspathOpt); 384 } else { 385 // Standard system classes for this compiler's release. 386 String files = System.getProperty("sun.boot.class.path"); 387 path.addFiles(files, false); 388 File rt_jar = new File("rt.jar"); 389 for (File file : getPathEntries(files)) { 390 if (new File(file.getName()).equals(rt_jar)) 391 defaultBootClassPathRtJar = file; 392 } 393 } 394 395 path.addFiles(xbootclasspathAppendOpt); 396 397 // Strictly speaking, standard extensions are not bootstrap 398 // classes, but we treat them identically, so we'll pretend 399 // that they are. 400 if (extdirsOpt != null) 401 path.addDirectories(extdirsOpt); 402 else 403 path.addDirectories(System.getProperty("java.ext.dirs"), false); 404 405 isDefaultBootClassPath = 406 (xbootclasspathPrependOpt == null) && 407 (bootclasspathOpt == null) && 408 (xbootclasspathAppendOpt == null); 409 410 return path; 411 } 412 413 private Path computeUserClassPath() { 414 String cp = options.get(CLASSPATH); 415 416 // CLASSPATH environment variable when run from `javac'. 417 if (cp == null) cp = System.getProperty("env.class.path"); 418 419 // If invoked via a java VM (not the javac launcher), use the 420 // platform class path 421 if (cp == null && System.getProperty("application.home") == null) 422 cp = System.getProperty("java.class.path"); 423 424 // Default to current working directory. 425 if (cp == null) cp = "."; 426 427 return new Path() 428 .expandJarClassPaths(true) // Only search user jars for Class-Paths 429 .emptyPathDefault(new File(".")) // Empty path elt ==> current directory 430 .addFiles(cp); 431 } 432 433 private Path computeSourcePath() { 434 String sourcePathArg = options.get(SOURCEPATH); 435 if (sourcePathArg == null) 436 return null; 437 438 return new Path().addFiles(sourcePathArg); 439 } 440 441 private Path computeAnnotationProcessorPath() { 442 String processorPathArg = options.get(PROCESSORPATH); 443 if (processorPathArg == null) 444 return null; 445 446 return new Path().addFiles(processorPathArg); 447 } 448 449 /** The actual effective locations searched for sources */ 450 private Path sourceSearchPath; 451 452 public Collection<File> sourceSearchPath() { 453 if (sourceSearchPath == null) { 454 lazy(); 455 Path sourcePath = getPathForLocation(SOURCE_PATH); 456 Path userClassPath = getPathForLocation(CLASS_PATH); 457 sourceSearchPath = sourcePath != null ? sourcePath : userClassPath; 458 } 459 return Collections.unmodifiableCollection(sourceSearchPath); 460 } 461 462 /** The actual effective locations searched for classes */ 463 private Path classSearchPath; 464 465 public Collection<File> classSearchPath() { 466 if (classSearchPath == null) { 467 lazy(); 468 Path bootClassPath = getPathForLocation(PLATFORM_CLASS_PATH); 469 Path userClassPath = getPathForLocation(CLASS_PATH); 470 classSearchPath = new Path(); 471 classSearchPath.addAll(bootClassPath); 472 classSearchPath.addAll(userClassPath); 473 } 474 return Collections.unmodifiableCollection(classSearchPath); 475 } 476 477 /** The actual effective locations for non-source, non-class files */ 478 private Path otherSearchPath; 479 480 Collection<File> otherSearchPath() { 481 if (otherSearchPath == null) { 482 lazy(); 483 Path userClassPath = getPathForLocation(CLASS_PATH); 484 Path sourcePath = getPathForLocation(SOURCE_PATH); 485 if (sourcePath == null) 486 otherSearchPath = userClassPath; 487 else { 488 otherSearchPath = new Path(); 489 otherSearchPath.addAll(userClassPath); 490 otherSearchPath.addAll(sourcePath); 491 } 492 } 493 return Collections.unmodifiableCollection(otherSearchPath); 494 } 495 496 /** Is this the name of an archive file? */ 497 private boolean isArchive(File file) { 498 String n = file.getName().toLowerCase(); 499 return fsInfo.isFile(file) 500 && (n.endsWith(".jar") || n.endsWith(".zip")); 501 } 502 503 /** 504 * Utility method for converting a search path string to an array 505 * of directory and JAR file URLs. 506 * 507 * Note that this method is called by apt and the DocletInvoker. 508 * 509 * @param path the search path string 510 * @return the resulting array of directory and JAR file URLs 511 */ 512 public static URL[] pathToURLs(String path) { 513 StringTokenizer st = new StringTokenizer(path, File.pathSeparator); 514 URL[] urls = new URL[st.countTokens()]; 515 int count = 0; 516 while (st.hasMoreTokens()) { 517 URL url = fileToURL(new File(st.nextToken())); 518 if (url != null) { 519 urls[count++] = url; 520 } 521 } 522 if (urls.length != count) { 523 URL[] tmp = new URL[count]; 524 System.arraycopy(urls, 0, tmp, 0, count); 525 urls = tmp; 526 } 527 return urls; 528 } 529 530 /** 531 * Returns the directory or JAR file URL corresponding to the specified 532 * local file name. 533 * 534 * @param file the File object 535 * @return the resulting directory or JAR file URL, or null if unknown 536 */ 537 private static URL fileToURL(File file) { 538 String name; 539 try { 540 name = file.getCanonicalPath(); 541 } catch (IOException e) { 542 name = file.getAbsolutePath(); 543 } 544 name = name.replace(File.separatorChar, '/'); 545 if (!name.startsWith("/")) { 546 name = "/" + name; 547 } 548 // If the file does not exist, then assume that it's a directory 549 if (!file.isFile()) { 550 name = name + "/"; 551 } 552 try { 553 return new URL("file", "", name); 554 } catch (MalformedURLException e) { 555 throw new IllegalArgumentException(file.toString()); 556 } 557 } 558 }