Fixes issue #3078; when the same logical resource occurs in more than one classpath entry, the classpath order must be honored.  Adds a test to verify this behavior.  Some minor cleanup, too.

Patch by: amitmanjhi
Review by: me


git-svn-id: https://google-web-toolkit.googlecode.com/svn/releases/1.6@4481 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 0245008..502e634 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,6 +31,7 @@
 import java.util.Collections;
 import java.util.HashMap;
 import java.util.HashSet;
+import java.util.LinkedHashMap;
 import java.util.List;
 import java.util.Map;
 import java.util.Set;
@@ -246,7 +247,6 @@
    * Rescans the associated paths to recompute the available resources.
    * 
    * @param logger status and error details are written here
-   * @throws UnableToCompleteException
    */
   public void refresh(TreeLogger logger) {
     TreeLogger refreshBranch = Messages.REFRESHING_RESOURCES.branch(logger,
@@ -254,9 +254,10 @@
 
     /*
      * Allocate fresh data structures in anticipation of needing to honor the
-     * "new identity for the collections if anything changes" guarantee.
+     * "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 HashMap<String, AbstractResource>();
+    final Map<String, AbstractResource> newInternalMap = new LinkedHashMap<String, AbstractResource>();
 
     /*
      * Walk across path roots (i.e. classpath entries) in priority order. This
@@ -348,6 +349,12 @@
           assert (path.startsWith(pathPrefix.getPrefix()));
           if (pathPrefix.shouldReroot()) {
             String rerootedPath = pathPrefix.getRerootedPath(path);
+            if (externalMap.get(rerootedPath) instanceof ResourceWrapper) {
+              // A rerooted resource blocks any other resource at this path.
+              ++hitCount;
+              break;
+            }
+
             // Try to reuse the same wrapper.
             Resource exposed = exposedResourceMap.get(rerootedPath);
             if (exposed instanceof ResourceWrapper) {
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 5871c12..489e571 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
@@ -132,12 +132,14 @@
   }
 
   protected void assertPathIncluded(Set<AbstractResource> resources, String path) {
-    assertNotNull(findResourceWithPath(resources, path));
+    assertNotNull("path = " + path + " should have been found in resources = "
+        + resources, findResourceWithPath(resources, path));
   }
 
   protected void assertPathNotIncluded(Set<AbstractResource> resources,
       String path) {
-    assertNull(findResourceWithPath(resources, path));
+    assertNull("path = " + path + " should not have been found in resources = "
+        + resources, findResourceWithPath(resources, path));
   }
 
   protected File findJarDirectory(String name) throws URISyntaxException {
diff --git a/dev/core/test/com/google/gwt/dev/resource/impl/ResourceOracleImplTest.java b/dev/core/test/com/google/gwt/dev/resource/impl/ResourceOracleImplTest.java
index c6fe7e0..46e166d 100644
--- a/dev/core/test/com/google/gwt/dev/resource/impl/ResourceOracleImplTest.java
+++ b/dev/core/test/com/google/gwt/dev/resource/impl/ResourceOracleImplTest.java
@@ -25,6 +25,7 @@
 import java.io.InputStreamReader;
 import java.net.URISyntaxException;
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.List;
 import java.util.Map;
 import java.util.Set;
@@ -59,9 +60,9 @@
   }
 
   private static class ResourceOracleSnapshot {
-    private final Set<Resource> resources;
-    private final Map<String, Resource> resourceMap;
     private final Set<String> pathNames;
+    private final Map<String, Resource> resourceMap;
+    private final Set<Resource> resources;
 
     public ResourceOracleSnapshot(ResourceOracleImpl oracle) {
       resources = oracle.getResources();
@@ -127,6 +128,34 @@
     }
   }
 
+  /**
+   * Test that ResourceOracleImpl preserves the order in which the same logical
+   * resource is occurs in multiple ClassPathEntries.
+   * 
+   * @throws URISyntaxException
+   * @throws IOException
+   */
+  public void testClassPathOrderIsHonored() throws IOException,
+      URISyntaxException {
+    TreeLogger logger = createTestTreeLogger();
+    ClassPathEntry cpe1jar = getClassPathEntry1AsJar();
+    ClassPathEntry cpe2jar = getClassPathEntry2AsJar();
+
+    ClassPathEntry[] cp12 = new ClassPathEntry[] {cpe1jar, cpe2jar};
+    ClassPathEntry[] cp21 = new ClassPathEntry[] {cpe2jar, cpe1jar};
+    String resKeyNormal = "org/example/bar/client/BarClient2.txt";
+    String resKeyReroot = "/BarClient2.txt";
+    PathPrefix pathPrefixNormal = new PathPrefix("org/example/bar/client",
+        null, false);
+    PathPrefix pathPrefixReroot = new PathPrefix("org/example/bar/client",
+        null, true);
+
+    testClassPathOrderIsHonored(logger, resKeyNormal, cp12, pathPrefixNormal);
+    testClassPathOrderIsHonored(logger, resKeyReroot, cp12, pathPrefixReroot);
+    testClassPathOrderIsHonored(logger, resKeyNormal, cp21, pathPrefixNormal);
+    testClassPathOrderIsHonored(logger, resKeyReroot, cp21, pathPrefixReroot);
+  }
+
   public void testNoClassPathEntries() {
     TreeLogger logger = createTestTreeLogger();
     ResourceOracleImpl oracle = createResourceOracle(new MOCK_CPE0());
@@ -139,7 +168,6 @@
    * 
    * @throws URISyntaxException
    * @throws IOException
-   * @throws UnableToCompleteException
    */
   public void testReadingResource() throws IOException, URISyntaxException {
     ClassPathEntry cpe1jar = getClassPathEntry1AsJar();
@@ -229,9 +257,6 @@
   /**
    * Creates an array of class path entries, setting up each one with a
    * well-known set of client prefixes.
-   * 
-   * @param entries
-   * @return
    */
   private ResourceOracleImpl createResourceOracle(ClassPathEntry... entries) {
     PathPrefixSet pps = new PathPrefixSet();
@@ -255,14 +280,22 @@
     return new ResourceOracleSnapshot(oracle);
   }
 
+  private void testClassPathOrderIsHonored(TreeLogger logger,
+      String resourceKey, ClassPathEntry[] classPath, PathPrefix pathPrefix) {
+    PathPrefixSet pps = new PathPrefixSet();
+    pps.add(pathPrefix);
+    ResourceOracleImpl oracle = new ResourceOracleImpl(Arrays.asList(classPath));
+    oracle.setPathPrefixes(pps);
+    ResourceOracleSnapshot s = refreshAndSnapshot(logger, oracle);
+    s.assertPathIncluded(resourceKey, classPath[0]);
+  }
+
   private void testReadingResource(ClassPathEntry cpe1, ClassPathEntry cpe2)
       throws IOException {
     TreeLogger logger = createTestTreeLogger();
 
     ResourceOracleImpl oracle = createResourceOracle(cpe1, cpe2);
-
-    oracle.refresh(logger);
-    ResourceOracleSnapshot s = new ResourceOracleSnapshot(oracle);
+    ResourceOracleSnapshot s = refreshAndSnapshot(logger, oracle);
     s.assertCollectionsConsistent(9);
     s.assertPathIncluded("com/google/gwt/user/client/Command.java", cpe1);
     s.assertPathIncluded("com/google/gwt/i18n/client/Messages.java", cpe2);
@@ -440,8 +473,7 @@
       /*
        * Baseline assumptions about the set of resources present by default.
        */
-      oracle.refresh(logger);
-      ResourceOracleSnapshot s = new ResourceOracleSnapshot(oracle);
+      ResourceOracleSnapshot s = refreshAndSnapshot(logger, oracle);
       s.assertPathIncluded("com/google/gwt/user/client/Command.java", cpe1);
       s.assertPathIncluded("com/google/gwt/user/client/Timer.java", cpe1);
     }
@@ -454,8 +486,7 @@
       /*
        * Ensure that the dups have the effect we expect.
        */
-      oracle.refresh(logger);
-      ResourceOracleSnapshot s = new ResourceOracleSnapshot(oracle);
+      ResourceOracleSnapshot s = refreshAndSnapshot(logger, oracle);
       s.assertPathIncluded("com/google/gwt/user/client/Command.java", cpe0);
       s.assertPathIncluded("com/google/gwt/user/client/Timer.java", cpe1);
     }