Simpler ResourceOracleImpl.

Review by: amitmanjhi

git-svn-id: https://google-web-toolkit.googlecode.com/svn/releases/1.6@4534 8db76d5a-ed1c-0410-87a9-c151d255dfc7
diff --git a/dev/core/src/com/google/gwt/dev/resource/impl/PathPrefix.java b/dev/core/src/com/google/gwt/dev/resource/impl/PathPrefix.java
index c844d86..096dfb1 100644
--- a/dev/core/src/com/google/gwt/dev/resource/impl/PathPrefix.java
+++ b/dev/core/src/com/google/gwt/dev/resource/impl/PathPrefix.java
@@ -33,6 +33,7 @@
 
   private final ResourceFilter filter;
   private final String prefix;
+  private int priority = -1;
   private final boolean shouldReroot;
 
   /**
@@ -140,6 +141,15 @@
     return prefix + (shouldReroot ? "**" : "*") + (filter == null ? "" : "?");
   }
 
+  int getPriority() {
+    return priority;
+  }
+
+  void setPriority(int priority) {
+    assert (this.priority == -1);
+    this.priority = priority;
+  }
+
   private void assertValidPrefix(String prefix) {
     assert (prefix != null);
     assert ("".equals(prefix) || (!prefix.startsWith("/") && prefix.endsWith("/"))) : "malformed prefix";
diff --git a/dev/core/src/com/google/gwt/dev/resource/impl/PathPrefixSet.java b/dev/core/src/com/google/gwt/dev/resource/impl/PathPrefixSet.java
index cbb2a9a..0220db6 100644
--- a/dev/core/src/com/google/gwt/dev/resource/impl/PathPrefixSet.java
+++ b/dev/core/src/com/google/gwt/dev/resource/impl/PathPrefixSet.java
@@ -15,9 +15,11 @@
  */
 package com.google.gwt.dev.resource.impl;
 
+import java.util.ArrayList;
 import java.util.Collection;
 import java.util.Collections;
 import java.util.HashMap;
+import java.util.List;
 import java.util.Map;
 
 /**
@@ -91,15 +93,11 @@
   }
 
   /**
-   * Map of pathPrefix => the sequence number when the pathPrefix was added.
+   * List of all path prefixes in priority order.
    */
-  private final Map<PathPrefix, Integer> prefixes = new HashMap<PathPrefix, Integer>();
+  private final List<PathPrefix> prefixes = new ArrayList<PathPrefix>();
 
   private final TrieNode rootTrieNode = new TrieNode("/");
-  /**
-   * The sequence number in which the PathPrefix was added.
-   */
-  private int size = 0;
 
   /**
    * @param prefix the prefix to add
@@ -109,13 +107,14 @@
    *         wins)
    */
   public boolean add(PathPrefix prefix) {
-    ++size;
-    prefixes.put(prefix, size);
+    prefix.setPriority(prefixes.size());
+    prefixes.add(prefix);
+
     String pathPrefix = prefix.getPrefix();
 
     /*
-     * An empty prefix means we have no prefix requirement, but we do attached
-     * the prefix to the root so that we can apply the filter.
+     * An empty prefix means we have no prefix requirement, but we do attach the
+     * prefix to the root so that we can apply the filter.
      */
     if ("".equals(pathPrefix)) {
       rootTrieNode.setPathPrefix(prefix);
@@ -144,7 +143,7 @@
   }
 
   public int getSize() {
-    return size;
+    return prefixes.size();
   }
 
   /**
@@ -262,30 +261,13 @@
     return mostSpecificPrefix;
   }
 
-  /**
-   * Returns true if the first pathPrefix is inserted into the PathPrefixSet
-   * after the second pathPrefix. Also, rereooting PathPrefixes take priority
-   * over non-rerooting ones (ie, super-source).
-   */
-  public boolean secondPrefixOverridesFirst(PathPrefix prefix1,
-      PathPrefix prefix2) {
-    if (prefix1.shouldReroot() != prefix2.shouldReroot()) {
-      return prefix2.shouldReroot();
-    }
-    int rank1 = prefixes.get(prefix1);
-    assert rank1 > 0;
-    int rank2 = prefixes.get(prefix2);
-    assert rank2 > 0;
-    return rank2 > rank1;
-  }
-
   @Override
   public String toString() {
     return rootTrieNode.toString();
   }
 
   public Collection<PathPrefix> values() {
-    return Collections.unmodifiableCollection(prefixes.keySet());
+    return Collections.unmodifiableCollection(prefixes);
   }
 
   private void assertValidAbstractDirectoryPathName(String name) {
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 13c569a..3629a14 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
@@ -31,11 +31,11 @@
 import java.util.Collections;
 import java.util.HashMap;
 import java.util.HashSet;
-import java.util.IdentityHashMap;
 import java.util.LinkedHashMap;
 import java.util.List;
 import java.util.Map;
 import java.util.Set;
+import java.util.Map.Entry;
 import java.util.jar.JarFile;
 import java.util.zip.ZipFile;
 
@@ -71,17 +71,14 @@
   }
 
   /**
-   * Used by rebasing {@link ResourceOracle ResourceOracles} to map from a full
-   * classpath-based abstract path to an abstract path within a logical package.
-   * 
-   * @see ResourceOracleImpl#shouldRebasePaths()
+   * Wrapper object around a resource to change its path when it is rerooted.
    */
-  private static class ResourceWrapper extends AbstractResource {
+  private static class RerootedResource extends AbstractResource {
     private final String path;
     private final AbstractResource resource;
 
-    public ResourceWrapper(String path, AbstractResource resource) {
-      this.path = path;
+    public RerootedResource(AbstractResource resource, PathPrefix pathPrefix) {
+      this.path = pathPrefix.getRerootedPath(resource.getPath());
       this.resource = resource;
     }
 
@@ -126,6 +123,31 @@
     }
   }
 
+  private static class ResourceData implements Comparable<ResourceData> {
+    public final PathPrefix pathPrefix;
+    public final AbstractResource resource;
+
+    public ResourceData(AbstractResource resource, PathPrefix pathPrefix) {
+      this.resource = pathPrefix.shouldReroot() ? new RerootedResource(
+          resource, pathPrefix) : resource;
+      this.pathPrefix = pathPrefix;
+    }
+
+    public int compareTo(ResourceData other) {
+      // Rerooted takes precedence over not rerooted.
+      if (this.resource.wasRerooted() != other.resource.wasRerooted()) {
+        return this.resource.wasRerooted() ? 1 : -1;
+      }
+      // Compare priorities of the path prefixes, high number == high priority.
+      return this.pathPrefix.getPriority() - other.pathPrefix.getPriority();
+    }
+
+    @Override
+    public String toString() {
+      return "{" + resource + "," + pathPrefix + "}";
+    }
+  }
+
   public static ClassPathEntry createEntryForUrl(TreeLogger logger, URL url)
       throws URISyntaxException, IOException {
     if (url.getProtocol().equals("file")) {
@@ -199,7 +221,7 @@
 
   private Set<Resource> exposedResources = Collections.emptySet();
 
-  private Map<String, AbstractResource> internalMap = Collections.emptyMap();
+  private Map<String, ResourceData> internalMap = Collections.emptyMap();
 
   private PathPrefixSet pathPrefixSet = new PathPrefixSet();
 
@@ -258,7 +280,7 @@
      * "new identity for the collections if anything changes" guarantee. Use a
      * LinkedHashMap because we do not want the order to change.
      */
-    final Map<String, AbstractResource> newInternalMap = new LinkedHashMap<String, AbstractResource>();
+    Map<String, ResourceData> newInternalMap = new LinkedHashMap<String, ResourceData>();
 
     /*
      * Walk across path roots (i.e. classpath entries) in priority order. This
@@ -266,87 +288,71 @@
      * a resource that has already been added to the new map under construction
      * to create the effect that resources founder earlier on the classpath take
      * precedence.
+     * 
+     * Exceptions: super has priority over non-super; and if there are two super
+     * resources with the same path, the one with the higher-priority path
+     * prefix wins.
      */
-    int changeCount = 0;
-    /**
-     * A map from AbstractResources (across all the ClassPathEntry) to their
-     * corresponding PathPrefixes. When old resources are used, an entry is
-     * added to this map from the old resource to the PathPrefix that matches
-     * the resource that the old resource supersedes.
-     */
-    Map<AbstractResource, PathPrefix> globalResourceToPrefixMap = new IdentityHashMap<AbstractResource, PathPrefix>();
     for (ClassPathEntry pathRoot : classPath) {
       TreeLogger branchForClassPathEntry = Messages.EXAMINING_PATH_ROOT.branch(
           refreshBranch, pathRoot.getLocation(), null);
 
-      int prevChangeCount = changeCount;
       Map<AbstractResource, PathPrefix> resourceToPrefixMap = pathRoot.findApplicableResources(
           branchForClassPathEntry, pathPrefixSet);
-      globalResourceToPrefixMap.putAll(resourceToPrefixMap);
-      for (AbstractResource newResource : resourceToPrefixMap.keySet()) {
-        String resourcePath = newResource.getPath();
-
-        /*
-         * Check if we have a resource by this name in the newInternalMap and if
-         * the matching pathPrefix overrides the pathPrefix for the previous
-         * resource
-         */
-        AbstractResource existingNewResource = newInternalMap.get(resourcePath);
-        if (existingNewResource != null
-            && !pathPrefixSet.secondPrefixOverridesFirst(
-                globalResourceToPrefixMap.get(existingNewResource),
-                resourceToPrefixMap.get(newResource))) {
+      for (Entry<AbstractResource, PathPrefix> entry : resourceToPrefixMap.entrySet()) {
+        ResourceData newCpeData = new ResourceData(entry.getKey(),
+            entry.getValue());
+        String resourcePath = newCpeData.resource.getPath();
+        ResourceData oldCpeData = newInternalMap.get(resourcePath);
+        // Old wins unless the new resource has higher priority.
+        if (oldCpeData == null || oldCpeData.compareTo(newCpeData) < 0) {
+          newInternalMap.put(resourcePath, newCpeData);
+        } else {
           Messages.IGNORING_SHADOWED_RESOURCE.log(branchForClassPathEntry,
               resourcePath, null);
-          continue;
         }
-
-        AbstractResource oldResource = internalMap.get(resourcePath);
-        if (shouldUseNewResource(branchForClassPathEntry, oldResource,
-            newResource)) {
-          newInternalMap.put(resourcePath, newResource);
-          ++changeCount;
-        } else {
-          assert oldResource != null;
-          /*
-           * Nothing changed, so carry the identity of the old one forward. Add
-           * the path-prefix to the global prefix map so that future resources
-           * can be compared.
-           */
-          newInternalMap.put(resourcePath, oldResource);
-          globalResourceToPrefixMap.put(oldResource,
-              resourceToPrefixMap.get(newResource));
-        }
-      }
-      if (changeCount == prevChangeCount) {
-        Messages.NO_RESOURCES_CHANGED.log(branchForClassPathEntry, null);
       }
     }
 
-    if (changeCount == 0) {
-      /*
-       * Nothing was added or modified, but we still have to be sure we didn't
-       * lose any resources.
-       */
-      if (newInternalMap.size() == internalMap.size()) {
-        /*
-         * Exit without changing the current exposed collections to maintain the
-         * identity requirements described in the spec for ResourceOracle.
-         */
-        return;
+    /*
+     * Update the newInternalMap to preserve identity for any resources that
+     * have not changed; also record whether or not there are ANY changes.
+     * 
+     * There's definitely a change if the sizes don't match; even if the sizes
+     * do match, every new resource must match an old resource for there to be
+     * no changes.
+     */
+    boolean didChange = internalMap.size() != newInternalMap.size();
+    for (String resourcePath : newInternalMap.keySet()) {
+      ResourceData newData = newInternalMap.get(resourcePath);
+      ResourceData oldData = internalMap.get(resourcePath);
+      if (shouldUseNewResource(logger, oldData, newData)) {
+        didChange = true;
+      } else {
+        if (oldData.resource != newData.resource) {
+          newInternalMap.put(resourcePath, oldData);
+        }
       }
     }
 
+    if (!didChange) {
+      // Nothing to do, keep the same identities.
+      return;
+    }
+
     internalMap = newInternalMap;
-    Map<String, Resource> externalMap = rerootResourcePaths(newInternalMap,
-        globalResourceToPrefixMap);
 
-    // Create a constant-time set for resources.
-    Set<Resource> newResources = new HashSet<Resource>(externalMap.values());
-    assert (newResources.size() == externalMap.size());
+    Map<String, Resource> externalMap = new HashMap<String, Resource>();
+    Set<Resource> externalSet = new HashSet<Resource>();
+    for (Entry<String, ResourceData> entry : internalMap.entrySet()) {
+      String path = entry.getKey();
+      ResourceData data = entry.getValue();
+      externalMap.put(path, data.resource);
+      externalSet.add(data.resource);
+    }
 
-    // Update the gettable fields with the new (unmodifiable) data structures.
-    exposedResources = Collections.unmodifiableSet(newResources);
+    // Update exposed collections with new (unmodifiable) data structures.
+    exposedResources = Collections.unmodifiableSet(externalSet);
     exposedResourceMap = Collections.unmodifiableMap(externalMap);
     exposedPathNames = Collections.unmodifiableSet(externalMap.keySet());
   }
@@ -355,63 +361,13 @@
     this.pathPrefixSet = pathPrefixSet;
   }
 
-  private Map<String, Resource> rerootResourcePaths(
-      Map<String, AbstractResource> newInternalMap,
-      Map<AbstractResource, PathPrefix> resourceToPrefixMap) {
-
-    // Create an external map with rebased path names.
-    Map<String, Resource> externalMap = new HashMap<String, Resource>();
-    for (AbstractResource resource : newInternalMap.values()) {
-      String path = resource.getPath();
-      assert resourceToPrefixMap.get(resource) != null;
-      if (externalMap.get(path) instanceof ResourceWrapper) {
-        // A rerooted resource can block any other resource at this path.
-        ResourceWrapper existingWrapper = (ResourceWrapper) externalMap.get(path);
-        if (!pathPrefixSet.secondPrefixOverridesFirst(
-            resourceToPrefixMap.get(existingWrapper.resource),
-            resourceToPrefixMap.get(resource))) {
-          continue;
-        }
-      }
-
-      PathPrefix pathPrefix = resourceToPrefixMap.get(resource);
-      assert pathPrefix != null;
-      if (pathPrefix.shouldReroot()) {
-        String rerootedPath = pathPrefix.getRerootedPath(path);
-        if (externalMap.get(rerootedPath) instanceof ResourceWrapper) {
-          ResourceWrapper existingWrapper = (ResourceWrapper) externalMap.get(rerootedPath);
-          if (!pathPrefixSet.secondPrefixOverridesFirst(
-              resourceToPrefixMap.get(existingWrapper.resource),
-              resourceToPrefixMap.get(resource))) {
-            // A rerooted resource blocks any other resource at this path.
-            continue;
-          }
-        }
-
-        // Try to reuse the same wrapper.
-        Resource exposed = exposedResourceMap.get(rerootedPath);
-        if (exposed instanceof ResourceWrapper) {
-          ResourceWrapper exposedWrapper = (ResourceWrapper) exposed;
-          if (exposedWrapper.resource == resource) {
-            externalMap.put(rerootedPath, exposedWrapper);
-            continue;
-          }
-        }
-        // Just create a new wrapper.
-        AbstractResource wrapper = new ResourceWrapper(rerootedPath, resource);
-        externalMap.put(rerootedPath, wrapper);
-      } else {
-        externalMap.put(path, resource);
-      }
-    }
-    return externalMap;
-  }
-
-  private boolean shouldUseNewResource(TreeLogger logger,
-      AbstractResource oldResource, AbstractResource newResource) {
+  private boolean shouldUseNewResource(TreeLogger logger, ResourceData oldData,
+      ResourceData newData) {
+    AbstractResource newResource = newData.resource;
     String resourcePath = newResource.getPath();
-    if (oldResource != null) {
+    if (oldData != null) {
       // Test 1: Is the resource found in a different location than before?
+      AbstractResource oldResource = oldData.resource;
       if (oldResource.getClassPathEntry() == newResource.getClassPathEntry()) {
         // Test 2: Has the resource changed since we last found it?
         if (!oldResource.isStale()) {