Support jars and zips on the classpath.

Suggested by: tobyr
Review by: tobyr (TBR)


git-svn-id: https://google-web-toolkit.googlecode.com/svn/trunk@2860 8db76d5a-ed1c-0410-87a9-c151d255dfc7
diff --git a/dev/core/src/com/google/gwt/dev/resource/impl/ResourceOracleImpl.java b/dev/core/src/com/google/gwt/dev/resource/impl/ResourceOracleImpl.java
index 7bd1d93..70bef30 100644
--- a/dev/core/src/com/google/gwt/dev/resource/impl/ResourceOracleImpl.java
+++ b/dev/core/src/com/google/gwt/dev/resource/impl/ResourceOracleImpl.java
@@ -36,6 +36,7 @@
 import java.util.Map;
 import java.util.Set;
 import java.util.jar.JarFile;
+import java.util.zip.ZipFile;
 
 /**
  * The normal implementation of {@link ResourceOracle}.
@@ -120,13 +121,16 @@
     if (url.getProtocol().equals("file")) {
       URI uri = new URI(urlString);
       File f = new File(uri);
+      String lowerCaseFileName = f.getName().toLowerCase();
       if (f.isDirectory()) {
         return new DirectoryClassPathEntry(f);
-      } else if (f.isFile() && f.getName().endsWith(".jar")) {
-        return new JarFileClassPathEntry(new JarFile(f));
+      } else if (f.isFile() && lowerCaseFileName.endsWith(".jar")) {
+        return new ZipFileClassPathEntry(new JarFile(f));
+      } else if (f.isFile() && lowerCaseFileName.endsWith(".zip")) {
+        return new ZipFileClassPathEntry(new ZipFile(f));
       } else {
         logger.log(TreeLogger.TRACE, "Unexpected entry in classpath; " + f
-            + " is neither a directory nor a jar");
+            + " is neither a directory nor an archive (.jar or .zip)");
         return null;
       }
     } else {
diff --git a/dev/core/src/com/google/gwt/dev/resource/impl/JarFileClassPathEntry.java b/dev/core/src/com/google/gwt/dev/resource/impl/ZipFileClassPathEntry.java
similarity index 67%
rename from dev/core/src/com/google/gwt/dev/resource/impl/JarFileClassPathEntry.java
rename to dev/core/src/com/google/gwt/dev/resource/impl/ZipFileClassPathEntry.java
index 7e7de4b..666b1e6 100644
--- a/dev/core/src/com/google/gwt/dev/resource/impl/JarFileClassPathEntry.java
+++ b/dev/core/src/com/google/gwt/dev/resource/impl/ZipFileClassPathEntry.java
@@ -22,20 +22,20 @@
 import java.util.Enumeration;
 import java.util.HashSet;
 import java.util.Set;
-import java.util.jar.JarEntry;
-import java.util.jar.JarFile;
+import java.util.zip.ZipEntry;
+import java.util.zip.ZipFile;
 
 /**
- * A classpath entry that is a jar file.
+ * A classpath entry that is a jar or zip file.
  */
-public class JarFileClassPathEntry extends ClassPathEntry {
+public class ZipFileClassPathEntry extends ClassPathEntry {
 
   /**
    * Logger messages related to this class.
    */
   private static class Messages {
     static final Message1String BUILDING_INDEX = new Message1String(
-        TreeLogger.TRACE, "Indexing jar file: $0");
+        TreeLogger.TRACE, "Indexing zip file: $0");
 
     static final Message1String EXCLUDING_RESOURCE = new Message1String(
         TreeLogger.DEBUG, "Excluding $0");
@@ -46,28 +46,28 @@
     static final Message1String INCLUDING_RESOURCE = new Message1String(
         TreeLogger.DEBUG, "Including $0");
 
-    static final Message1String READ_JAR_ENTRY = new Message1String(
+    static final Message1String READ_ZIP_ENTRY = new Message1String(
         TreeLogger.DEBUG, "$0");
   }
 
-  private Set<JarFileResource> allJarFileResources;
+  private Set<ZipFileResource> allZipFileResources;
   private Set<AbstractResource> cachedAnswers;
-  private final JarFile jarFile;
   private PathPrefixSet lastPrefixSet;
+  private final ZipFile zipFile;
 
-  public JarFileClassPathEntry(JarFile jarFile) {
-    this.jarFile = jarFile;
+  public ZipFileClassPathEntry(ZipFile zipFile) {
+    this.zipFile = zipFile;
   }
 
   /**
-   * Indexes the jar file on-demand, and only once over the life of the process.
+   * Indexes the zip file on-demand, and only once over the life of the process.
    */
   @Override
   public Set<AbstractResource> findApplicableResources(TreeLogger logger,
       PathPrefixSet pathPrefixSet) {
     // Never re-index.
-    if (allJarFileResources == null) {
-      allJarFileResources = buildIndex(logger);
+    if (allZipFileResources == null) {
+      allZipFileResources = buildIndex(logger);
     }
 
     if (cachedAnswers == null || lastPrefixSet != pathPrefixSet
@@ -78,33 +78,33 @@
     return cachedAnswers;
   }
 
-  public JarFile getJarFile() {
-    return jarFile;
-  }
-
   @Override
   public String getLocation() {
-    return new File(jarFile.getName()).toURI().toString();
+    return new File(zipFile.getName()).toURI().toString();
   }
 
-  private Set<JarFileResource> buildIndex(TreeLogger logger) {
-    logger = Messages.BUILDING_INDEX.branch(logger, jarFile.getName(), null);
+  public ZipFile getZipFile() {
+    return zipFile;
+  }
 
-    HashSet<JarFileResource> results = new HashSet<JarFileResource>();
-    Enumeration<JarEntry> e = jarFile.entries();
+  private Set<ZipFileResource> buildIndex(TreeLogger logger) {
+    logger = Messages.BUILDING_INDEX.branch(logger, zipFile.getName(), null);
+
+    HashSet<ZipFileResource> results = new HashSet<ZipFileResource>();
+    Enumeration<? extends ZipEntry> e = zipFile.entries();
     while (e.hasMoreElements()) {
-      JarEntry jarEntry = e.nextElement();
-      if (jarEntry.isDirectory()) {
+      ZipEntry zipEntry = e.nextElement();
+      if (zipEntry.isDirectory()) {
         // Skip directories.
         continue;
       }
-      if (jarEntry.getName().startsWith("META-INF/")) {
+      if (zipEntry.getName().startsWith("META-INF/")) {
         // Skip META-INF since classloaders normally make this invisible.
         continue;
       }
-      JarFileResource jarResource = new JarFileResource(this, jarEntry);
-      results.add(jarResource);
-      Messages.READ_JAR_ENTRY.log(logger, jarEntry.getName(), null);
+      ZipFileResource zipResource = new ZipFileResource(this, zipEntry);
+      results.add(zipResource);
+      Messages.READ_ZIP_ENTRY.log(logger, zipEntry.getName(), null);
     }
     return results;
   }
@@ -112,10 +112,10 @@
   private Set<AbstractResource> computeApplicableResources(TreeLogger logger,
       PathPrefixSet pathPrefixSet) {
     logger = Messages.FINDING_INCLUDED_RESOURCES.branch(logger,
-        jarFile.getName(), null);
+        zipFile.getName(), null);
 
     Set<AbstractResource> results = new HashSet<AbstractResource>();
-    for (JarFileResource r : allJarFileResources) {
+    for (ZipFileResource r : allZipFileResources) {
       String path = r.getPath();
       if (pathPrefixSet.includesResource(path)) {
         Messages.INCLUDING_RESOURCE.log(logger, path, null);
diff --git a/dev/core/src/com/google/gwt/dev/resource/impl/JarFileResource.java b/dev/core/src/com/google/gwt/dev/resource/impl/ZipFileResource.java
similarity index 66%
rename from dev/core/src/com/google/gwt/dev/resource/impl/JarFileResource.java
rename to dev/core/src/com/google/gwt/dev/resource/impl/ZipFileResource.java
index 650d053..87df4fd 100644
--- a/dev/core/src/com/google/gwt/dev/resource/impl/JarFileResource.java
+++ b/dev/core/src/com/google/gwt/dev/resource/impl/ZipFileResource.java
@@ -20,37 +20,35 @@
 import java.net.MalformedURLException;
 import java.net.URL;
 import java.util.jar.JarEntry;
+import java.util.zip.ZipEntry;
 
 /**
- * Represents a resource contained in a jar file.
+ * Represents a resource contained in a jar or zip file.
  */
-public class JarFileResource extends AbstractResource {
+public class ZipFileResource extends AbstractResource {
 
-  private final JarFileClassPathEntry classPathEntry;
-  private final JarEntry jarEntry;
+  private final ZipFileClassPathEntry classPathEntry;
+  private final ZipEntry zipEntry;
 
-  public JarFileResource(JarFileClassPathEntry classPathEntry, JarEntry jarEntry) {
+  public ZipFileResource(ZipFileClassPathEntry classPathEntry, ZipEntry zipEntry) {
     this.classPathEntry = classPathEntry;
-    this.jarEntry = jarEntry;
+    this.zipEntry = zipEntry;
   }
 
   @Override
-  public JarFileClassPathEntry getClassPathEntry() {
+  public ZipFileClassPathEntry getClassPathEntry() {
     return classPathEntry;
   }
 
-  public JarEntry getJarEntry() {
-    return jarEntry;
-  }
-
   @Override
   public String getLocation() {
-    return "jar:" + classPathEntry.getLocation() + "!/" + getPath();
+    String proto = zipEntry instanceof JarEntry ? "jar:" : "zip:";
+    return proto + classPathEntry.getLocation() + "!/" + getPath();
   }
 
   @Override
   public String getPath() {
-    return jarEntry.getName();
+    return zipEntry.getName();
   }
 
   @Override
@@ -62,8 +60,12 @@
     }
   }
 
+  public ZipEntry getZipEntry() {
+    return zipEntry;
+  }
+
   /**
-   * Since we don't dynamically reload jars during a run, jar-based resources
+   * Since we don't dynamically reload zips during a run, zip-based resources
    * cannot become stale.
    */
   @Override
@@ -74,7 +76,7 @@
   @Override
   public InputStream openContents() {
     try {
-      return classPathEntry.getJarFile().getInputStream(jarEntry);
+      return classPathEntry.getZipFile().getInputStream(zipEntry);
     } catch (IOException e) {
       // The spec for this method says it can return null.
       return null;
diff --git a/dev/core/src/com/google/gwt/dev/shell/tomcat/EmbeddedTomcatServer.java b/dev/core/src/com/google/gwt/dev/shell/tomcat/EmbeddedTomcatServer.java
index 723a431..8ee3cd6 100644
--- a/dev/core/src/com/google/gwt/dev/shell/tomcat/EmbeddedTomcatServer.java
+++ b/dev/core/src/com/google/gwt/dev/shell/tomcat/EmbeddedTomcatServer.java
@@ -365,7 +365,12 @@
     String prefix = "";
     String urlString = url.toString();
     if (urlString.startsWith("jar:")) {
-      assert urlString.contains(".jar!/" + tomcatEtcDir);
+      assert urlString.toLowerCase().contains(".jar!/" + tomcatEtcDir);
+      urlString = urlString.substring(4, urlString.indexOf('!'));
+      url = new URL(urlString);
+      prefix = tomcatEtcDir;
+    } else if (urlString.startsWith("zip:")) {
+      assert urlString.toLowerCase().contains(".zip!/" + tomcatEtcDir);
       urlString = urlString.substring(4, urlString.indexOf('!'));
       url = new URL(urlString);
       prefix = tomcatEtcDir;
diff --git a/dev/core/src/com/google/gwt/dev/util/Util.java b/dev/core/src/com/google/gwt/dev/util/Util.java
index 237a3c3..b2047f7 100644
--- a/dev/core/src/com/google/gwt/dev/util/Util.java
+++ b/dev/core/src/com/google/gwt/dev/util/Util.java
@@ -53,7 +53,6 @@
 import java.net.MalformedURLException;
 import java.net.URL;
 import java.net.URLConnection;
-import java.net.URLDecoder;
 import java.security.MessageDigest;
 import java.security.NoSuchAlgorithmException;
 import java.util.Arrays;
@@ -63,7 +62,6 @@
 import java.util.List;
 import java.util.SortedSet;
 import java.util.TreeSet;
-import java.util.jar.JarEntry;
 
 /**
  * A smattering of useful methods. Methods in this class are candidates for
@@ -255,38 +253,6 @@
     return escaped;
   }
 
-  /**
-   * Converts a URL "jar:file:aaa.jar!bbb" to the filename "aaa.jar". This also
-   * does URLDecoding if needed.
-   * 
-   * @param location the URL pointing at a jar location
-   * @return the path to the jar file
-   */
-  public static String findFileName(String location) {
-    String newLocation = location;
-    if ((location.startsWith("zip:file:"))
-        || (location.startsWith("jar:file:"))) {
-      int lastIndexOfExclam = newLocation.lastIndexOf('!');
-      // This file lives in a jar or zipfile
-      // we pretend the location is the jar or zip file
-      if (lastIndexOfExclam != -1) {
-        newLocation = newLocation.substring(0, lastIndexOfExclam);
-      }
-      newLocation = newLocation.substring(9);
-      // If the file does not exist that may be because the path is
-      // URLEncoded. Therefore, decode it.
-      if (!new File(newLocation).exists()) {
-        try {
-          newLocation = URLDecoder.decode(newLocation, DEFAULT_ENCODING);
-        } catch (UnsupportedEncodingException e) {
-          // An unsupported encoding indicates confusion; do nothing.
-          return location;
-        }
-      }
-    }
-    return newLocation;
-  }
-
   public static URL findSourceInClassPath(ClassLoader cl, String sourceTypeName) {
     String toTry = sourceTypeName.replace('.', '/') + ".java";
     URL foundURL = cl.getResource(toTry);
@@ -389,7 +355,8 @@
 
       URL url = new URL(loc);
       String s = url.toExternalForm();
-      if (s.startsWith("file:") || s.startsWith("jar:")) {
+      if (s.startsWith("file:") || s.startsWith("jar:file:")
+          || s.startsWith("zip:file:")) {
         return true;
       }
     } catch (MalformedURLException e) {
@@ -858,18 +825,6 @@
     }
   }
 
-  public static URL toURL(URL jarUrl, JarEntry jarEntry) {
-    return toURL(jarUrl, jarEntry.toString());
-  }
-
-  public static URL toURL(URL jarUrl, String path) {
-    try {
-      return new URL("jar" + ":" + jarUrl.toString() + "!/" + path);
-    } catch (MalformedURLException e) {
-      throw new RuntimeException("Failed to convert a jar path to a URL", e);
-    }
-  }
-
   public static String toXml(Document doc) {
     Throwable caught = null;
     try {
diff --git a/dev/core/test/com/google/gwt/dev/resource/impl/AbstractResourceOrientedTestBase.java b/dev/core/test/com/google/gwt/dev/resource/impl/AbstractResourceOrientedTestBase.java
index 6d5e3ee..5871c12 100644
--- a/dev/core/test/com/google/gwt/dev/resource/impl/AbstractResourceOrientedTestBase.java
+++ b/dev/core/test/com/google/gwt/dev/resource/impl/AbstractResourceOrientedTestBase.java
@@ -181,7 +181,7 @@
   protected ClassPathEntry getClassPathEntry1AsJar() throws IOException,
       URISyntaxException {
     File file = findJarFile("com/google/gwt/dev/resource/impl/testdata/cpe1.jar");
-    return new ExcludeSvnClassPathEntry(new JarFileClassPathEntry(new JarFile(
+    return new ExcludeSvnClassPathEntry(new ZipFileClassPathEntry(new JarFile(
         file)));
   }
 
@@ -198,7 +198,7 @@
   protected ClassPathEntry getClassPathEntry2AsJar() throws URISyntaxException,
       IOException {
     File file = findJarFile("com/google/gwt/dev/resource/impl/testdata/cpe2.jar");
-    return new ExcludeSvnClassPathEntry(new JarFileClassPathEntry(new JarFile(
+    return new ExcludeSvnClassPathEntry(new ZipFileClassPathEntry(new JarFile(
         file)));
   }