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