1 /* 2 * Copyright 2003-2008 Sun Microsystems, Inc. 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. Sun designates this 8 * particular file as subject to the "Classpath" exception as provided 9 * by Sun 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 Sun Microsystems, Inc., 4150 Network Circle, Santa Clara, 22 * CA 95054 USA or visit www.sun.com if you need additional information or 23 * have any 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 API supported by Sun Microsystems. If 58 * 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 bootClassPathRtJar = null; 116 117 Path getPathForLocation(Location location) { 118 Path path = pathsForLocation.get(location); 119 if (path == null) 120 setPathForLocation(location, null); 121 return pathsForLocation.get(location); 122 } 123 124 void setPathForLocation(Location location, Iterable<? extends File> path) { 125 // TODO? if (inited) throw new IllegalStateException 126 // TODO: otherwise reset sourceSearchPath, classSearchPath as needed 127 Path p; 128 if (path == null) { 129 if (location == CLASS_PATH) 130 p = computeUserClassPath(); 131 else if (location == PLATFORM_CLASS_PATH) 132 p = computeBootClassPath(); 133 else if (location == ANNOTATION_PROCESSOR_PATH) 134 p = computeAnnotationProcessorPath(); 135 else if (location == SOURCE_PATH) 136 p = computeSourcePath(); 137 else 138 // no defaults for other paths 139 p = null; 140 } else { 141 p = new Path(); 142 for (File f: path) 143 p.addFile(f, warn); // TODO: is use of warn appropriate? 144 } 145 pathsForLocation.put(location, p); 146 } 147 148 protected void lazy() { 149 if (!inited) { 150 warn = lint.isEnabled(Lint.LintCategory.PATH); 151 152 pathsForLocation.put(PLATFORM_CLASS_PATH, computeBootClassPath()); 153 pathsForLocation.put(CLASS_PATH, computeUserClassPath()); 154 pathsForLocation.put(SOURCE_PATH, computeSourcePath()); 155 156 inited = true; 157 } 158 } 159 160 public Collection<File> bootClassPath() { 161 lazy(); 162 return Collections.unmodifiableCollection(getPathForLocation(PLATFORM_CLASS_PATH)); 163 } 164 public Collection<File> userClassPath() { 165 lazy(); 166 return Collections.unmodifiableCollection(getPathForLocation(CLASS_PATH)); 167 } 168 public Collection<File> sourcePath() { 169 lazy(); 170 Path p = getPathForLocation(SOURCE_PATH); 171 return p == null || p.size() == 0 172 ? null 173 : Collections.unmodifiableCollection(p); 174 } 175 176 boolean isBootClassPathRtJar(File file) { 177 return file.equals(bootClassPathRtJar); 178 } 179 180 /** 181 * Split a path into its elements. Empty path elements will be ignored. 182 * @param path The path to be split 183 * @return The elements of the path 184 */ 185 private static Iterable<File> getPathEntries(String path) { 186 return getPathEntries(path, null); 187 } 188 189 /** 190 * Split a path into its elements. If emptyPathDefault is not null, all 191 * empty elements in the path, including empty elements at either end of 192 * the path, will be replaced with the value of emptyPathDefault. 193 * @param path The path to be split 194 * @param emptyPathDefault The value to substitute for empty path elements, 195 * or null, to ignore empty path elements 196 * @return The elements of the path 197 */ 198 private static Iterable<File> getPathEntries(String path, File emptyPathDefault) { 199 ListBuffer<File> entries = new ListBuffer<File>(); 200 int start = 0; 201 while (start <= path.length()) { 202 int sep = path.indexOf(File.pathSeparatorChar, start); 203 if (sep == -1) 204 sep = path.length(); 205 if (start < sep) 206 entries.add(new File(path.substring(start, sep))); 207 else if (emptyPathDefault != null) 208 entries.add(emptyPathDefault); 209 start = sep + 1; 210 } 211 return entries; 212 } 213 214 private class Path extends LinkedHashSet<File> { 215 private static final long serialVersionUID = 0; 216 217 private boolean expandJarClassPaths = false; 218 private Set<File> canonicalValues = new HashSet<File>(); 219 220 public Path expandJarClassPaths(boolean x) { 221 expandJarClassPaths = x; 222 return this; 223 } 224 225 /** What to use when path element is the empty string */ 226 private File emptyPathDefault = null; 227 228 public Path emptyPathDefault(File x) { 229 emptyPathDefault = x; 230 return this; 231 } 232 233 public Path() { super(); } 234 235 public Path addDirectories(String dirs, boolean warn) { 236 if (dirs != null) 237 for (File dir : getPathEntries(dirs)) 238 addDirectory(dir, warn); 239 return this; 240 } 241 242 public Path addDirectories(String dirs) { 243 return addDirectories(dirs, warn); 244 } 245 246 private void addDirectory(File dir, boolean warn) { 247 if (!dir.isDirectory()) { 248 if (warn) 249 log.warning("dir.path.element.not.found", dir); 250 return; 251 } 252 253 File[] files = dir.listFiles(); 254 if (files == null) 255 return; 256 257 for (File direntry : files) { 258 if (isArchive(direntry)) 259 addFile(direntry, warn); 260 } 261 } 262 263 public Path addFiles(String files, boolean warn) { 264 if (files != null) 265 for (File file : getPathEntries(files, emptyPathDefault)) 266 addFile(file, warn); 267 return this; 268 } 269 270 public Path addFiles(String files) { 271 return addFiles(files, warn); 272 } 273 274 public void addFile(File file, boolean warn) { 275 File canonFile = fsInfo.getCanonicalFile(file); 276 if (contains(file) || canonicalValues.contains(canonFile)) { 277 /* Discard duplicates and avoid infinite recursion */ 278 return; 279 } 280 281 if (! fsInfo.exists(file)) { 282 /* No such file or directory exists */ 283 if (warn) 284 log.warning("path.element.not.found", file); 285 } else if (fsInfo.isFile(file)) { 286 /* File is an ordinary file. */ 287 if (!isArchive(file)) { 288 /* Not a recognized extension; open it to see if 289 it looks like a valid zip file. */ 290 try { 291 ZipFile z = new ZipFile(file); 292 z.close(); 293 if (warn) 294 log.warning("unexpected.archive.file", file); 295 } catch (IOException e) { 296 // FIXME: include e.getLocalizedMessage in warning 297 if (warn) 298 log.warning("invalid.archive.file", file); 299 return; 300 } 301 } 302 } 303 304 /* Now what we have left is either a directory or a file name 305 confirming to archive naming convention */ 306 super.add(file); 307 canonicalValues.add(canonFile); 308 309 if (expandJarClassPaths && fsInfo.exists(file) && fsInfo.isFile(file)) 310 addJarClassPath(file, warn); 311 } 312 313 // Adds referenced classpath elements from a jar's Class-Path 314 // Manifest entry. In some future release, we may want to 315 // update this code to recognize URLs rather than simple 316 // filenames, but if we do, we should redo all path-related code. 317 private void addJarClassPath(File jarFile, boolean warn) { 318 try { 319 for (File f: fsInfo.getJarClassPath(jarFile)) { 320 addFile(f, warn); 321 } 322 } catch (IOException e) { 323 log.error("error.reading.file", jarFile, e.getLocalizedMessage()); 324 } 325 } 326 } 327 328 private Path computeBootClassPath() { 329 bootClassPathRtJar = null; 330 String optionValue; 331 Path path = new Path(); 332 333 path.addFiles(options.get(XBOOTCLASSPATH_PREPEND)); 334 335 if ((optionValue = options.get(ENDORSEDDIRS)) != null) 336 path.addDirectories(optionValue); 337 else 338 path.addDirectories(System.getProperty("java.endorsed.dirs"), false); 339 340 if ((optionValue = options.get(BOOTCLASSPATH)) != null) { 341 path.addFiles(optionValue); 342 } else { 343 // Standard system classes for this compiler's release. 344 String files = System.getProperty("sun.boot.class.path"); 345 path.addFiles(files, false); 346 File rt_jar = new File("rt.jar"); 347 for (File file : getPathEntries(files)) { 348 if (new File(file.getName()).equals(rt_jar)) 349 bootClassPathRtJar = file; 350 } 351 } 352 353 path.addFiles(options.get(XBOOTCLASSPATH_APPEND)); 354 355 // Strictly speaking, standard extensions are not bootstrap 356 // classes, but we treat them identically, so we'll pretend 357 // that they are. 358 if ((optionValue = options.get(EXTDIRS)) != null) 359 path.addDirectories(optionValue); 360 else 361 path.addDirectories(System.getProperty("java.ext.dirs"), false); 362 363 return path; 364 } 365 366 private Path computeUserClassPath() { 367 String cp = options.get(CLASSPATH); 368 369 // CLASSPATH environment variable when run from `javac'. 370 if (cp == null) cp = System.getProperty("env.class.path"); 371 372 // If invoked via a java VM (not the javac launcher), use the 373 // platform class path 374 if (cp == null && System.getProperty("application.home") == null) 375 cp = System.getProperty("java.class.path"); 376 377 // Default to current working directory. 378 if (cp == null) cp = "."; 379 380 return new Path() 381 .expandJarClassPaths(true) // Only search user jars for Class-Paths 382 .emptyPathDefault(new File(".")) // Empty path elt ==> current directory 383 .addFiles(cp); 384 } 385 386 private Path computeSourcePath() { 387 String sourcePathArg = options.get(SOURCEPATH); 388 if (sourcePathArg == null) 389 return null; 390 391 return new Path().addFiles(sourcePathArg); 392 } 393 394 private Path computeAnnotationProcessorPath() { 395 String processorPathArg = options.get(PROCESSORPATH); 396 if (processorPathArg == null) 397 return null; 398 399 return new Path().addFiles(processorPathArg); 400 } 401 402 /** The actual effective locations searched for sources */ 403 private Path sourceSearchPath; 404 405 public Collection<File> sourceSearchPath() { 406 if (sourceSearchPath == null) { 407 lazy(); 408 Path sourcePath = getPathForLocation(SOURCE_PATH); 409 Path userClassPath = getPathForLocation(CLASS_PATH); 410 sourceSearchPath = sourcePath != null ? sourcePath : userClassPath; 411 } 412 return Collections.unmodifiableCollection(sourceSearchPath); 413 } 414 415 /** The actual effective locations searched for classes */ 416 private Path classSearchPath; 417 418 public Collection<File> classSearchPath() { 419 if (classSearchPath == null) { 420 lazy(); 421 Path bootClassPath = getPathForLocation(PLATFORM_CLASS_PATH); 422 Path userClassPath = getPathForLocation(CLASS_PATH); 423 classSearchPath = new Path(); 424 classSearchPath.addAll(bootClassPath); 425 classSearchPath.addAll(userClassPath); 426 } 427 return Collections.unmodifiableCollection(classSearchPath); 428 } 429 430 /** The actual effective locations for non-source, non-class files */ 431 private Path otherSearchPath; 432 433 Collection<File> otherSearchPath() { 434 if (otherSearchPath == null) { 435 lazy(); 436 Path userClassPath = getPathForLocation(CLASS_PATH); 437 Path sourcePath = getPathForLocation(SOURCE_PATH); 438 if (sourcePath == null) 439 otherSearchPath = userClassPath; 440 else { 441 otherSearchPath = new Path(); 442 otherSearchPath.addAll(userClassPath); 443 otherSearchPath.addAll(sourcePath); 444 } 445 } 446 return Collections.unmodifiableCollection(otherSearchPath); 447 } 448 449 /** Is this the name of an archive file? */ 450 private boolean isArchive(File file) { 451 String n = file.getName().toLowerCase(); 452 return fsInfo.isFile(file) 453 && (n.endsWith(".jar") || n.endsWith(".zip")); 454 } 455 456 /** 457 * Utility method for converting a search path string to an array 458 * of directory and JAR file URLs. 459 * 460 * Note that this method is called by apt and the DocletInvoker. 461 * 462 * @param path the search path string 463 * @return the resulting array of directory and JAR file URLs 464 */ 465 public static URL[] pathToURLs(String path) { 466 StringTokenizer st = new StringTokenizer(path, File.pathSeparator); 467 URL[] urls = new URL[st.countTokens()]; 468 int count = 0; 469 while (st.hasMoreTokens()) { 470 URL url = fileToURL(new File(st.nextToken())); 471 if (url != null) { 472 urls[count++] = url; 473 } 474 } 475 if (urls.length != count) { 476 URL[] tmp = new URL[count]; 477 System.arraycopy(urls, 0, tmp, 0, count); 478 urls = tmp; 479 } 480 return urls; 481 } 482 483 /** 484 * Returns the directory or JAR file URL corresponding to the specified 485 * local file name. 486 * 487 * @param file the File object 488 * @return the resulting directory or JAR file URL, or null if unknown 489 */ 490 private static URL fileToURL(File file) { 491 String name; 492 try { 493 name = file.getCanonicalPath(); 494 } catch (IOException e) { 495 name = file.getAbsolutePath(); 496 } 497 name = name.replace(File.separatorChar, '/'); 498 if (!name.startsWith("/")) { 499 name = "/" + name; 500 } 501 // If the file does not exist, then assume that it's a directory 502 if (!file.isFile()) { 503 name = name + "/"; 504 } 505 try { 506 return new URL("file", "", name); 507 } catch (MalformedURLException e) { 508 throw new IllegalArgumentException(file.toString()); 509 } 510 } 511 }