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