src/share/classes/com/sun/tools/javac/util/Paths.java

Sat, 01 Dec 2007 00:00:00 +0000

author
duke
date
Sat, 01 Dec 2007 00:00:00 +0000
changeset 1
9a66ca7c79fa
child 14
58039502942e
permissions
-rw-r--r--

Initial load

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

mercurial