Fix performance issues caused by 8d15a25dcbb23

The earlier commit would maintain a static list of styles, and every time
a style resource was added to a theme, it would stay there until the end
of time (or termination of JVM). When executing thousands of tests, this
would lead to massive slowdown of test execution - over 2x increase in
our company's case.

Now, the duplicate styles are removed and the new one is added, which keeps
the list short and performance reasonable.

Change-Id: Ib7ed9321c31938577c78aab52a1d8380bf1be4e6
diff --git a/robolectric-resources/src/main/java/org/robolectric/res/StyleData.java b/robolectric-resources/src/main/java/org/robolectric/res/StyleData.java
index 73cb1a6..6bf03ec 100644
--- a/robolectric-resources/src/main/java/org/robolectric/res/StyleData.java
+++ b/robolectric-resources/src/main/java/org/robolectric/res/StyleData.java
@@ -1,5 +1,7 @@
 package org.robolectric.res;
 
+import org.robolectric.util.Strings;
+
 import java.util.LinkedHashMap;
 import java.util.Map;
 
@@ -42,6 +44,29 @@
     return attribute;
   }
 
+  @Override
+  public boolean equals(Object obj) {
+    if (!(obj instanceof StyleData)) {
+      return false;
+    }
+    StyleData other = (StyleData) obj;
+
+    return Strings.equals(packageName, other.packageName)
+        && Strings.equals(name, other.name)
+        && Strings.equals(parent, other.parent)
+        && items.size() == other.items.size();
+  }
+
+  @Override
+  public int hashCode() {
+    int hashCode = 0;
+    hashCode = 31 * hashCode + Strings.nullToEmpty(packageName).hashCode();
+    hashCode = 31 * hashCode + Strings.nullToEmpty(name).hashCode();
+    hashCode = 31 * hashCode + Strings.nullToEmpty(parent).hashCode();
+    hashCode = 31 * hashCode + items.size();
+    return hashCode;
+  }
+
   @Override public String toString() {
     return "StyleData{" +
         "name='" + name + '\'' +
diff --git a/robolectric-shadows/shadows-core/src/main/resources/org/robolectric/shadows/ShadowAssetManager.java.vm b/robolectric-shadows/shadows-core/src/main/resources/org/robolectric/shadows/ShadowAssetManager.java.vm
index 5e00180..52de4d3 100644
--- a/robolectric-shadows/shadows-core/src/main/resources/org/robolectric/shadows/ShadowAssetManager.java.vm
+++ b/robolectric-shadows/shadows-core/src/main/resources/org/robolectric/shadows/ShadowAssetManager.java.vm
@@ -43,6 +43,7 @@
 import org.robolectric.res.StyleData;
 import org.robolectric.res.TypedResource;
 import org.robolectric.res.builder.XmlFileBuilder;
+import org.robolectric.util.Strings;
 
 import static org.robolectric.Shadows.shadowOf;
 import static org.robolectric.shadows.ShadowApplication.getInstance;
@@ -274,7 +275,15 @@
 
     Style style = resolveStyle(resourceLoader, null, resName, assetManager.getQualifiers());
 
-    APPLIED_STYLES.get(theme).add(new OverlayedStyle(style, force));
+    List<OverlayedStyle> overlayedStyleList = APPLIED_STYLES.get(theme);
+    OverlayedStyle styleToAdd = new OverlayedStyle(style, force);
+    for (int i = 0; i < overlayedStyleList.size(); ++i) {
+      if (styleToAdd.equals(overlayedStyleList.get(i))) {
+        overlayedStyleList.remove(i);
+        break;
+      }
+    }
+    overlayedStyleList.add(styleToAdd);
   }
 
   static List<OverlayedStyle> getOverlayThemeStyles($ptrClass themeResourceId) {
@@ -289,6 +298,20 @@
       this.style = style;
       this.force = force;
     }
+
+    @Override
+    public boolean equals(Object obj) {
+      if (!(obj instanceof OverlayedStyle)) {
+        return false;
+      }
+      OverlayedStyle overlayedStyle = (OverlayedStyle) obj;
+      return style.equals(overlayedStyle.style);
+    }
+
+    @Override
+    public int hashCode() {
+      return style.hashCode();
+    }
   }
 
   @HiddenApi @Implementation
@@ -510,6 +533,29 @@
       throw new RuntimeException("Found a " + attr + " but can't cast it :(");
     }
 
+    @Override
+    public boolean equals(Object obj) {
+      if (!(obj instanceof StyleResolver)) {
+        return false;
+      }
+      StyleResolver other = (StyleResolver) obj;
+
+      return ((theme == null && other.theme == null) || (theme != null && theme.equals(other.theme)))
+          && ((myResName == null && other.myResName == null)
+            || (myResName != null && myResName.equals(other.myResName)))
+          && Strings.equals(qualifiers, other.qualifiers);
+    }
+
+    @Override
+    public int hashCode() {
+      int hashCode = 0;
+      hashCode = 31 * hashCode + (theme != null ? theme.hashCode() : 0);
+      hashCode = 31 * hashCode + (myResName != null ? myResName.hashCode() : 0);
+      hashCode = 31 * hashCode + Strings.nullToEmpty(qualifiers).hashCode();
+      return hashCode;
+    }
+
+    @Override
     public String toString() {
       return "StyleResolver{"
           + "name=" + myResName
diff --git a/robolectric-utils/src/main/java/org/robolectric/util/Strings.java b/robolectric-utils/src/main/java/org/robolectric/util/Strings.java
index 9b61904..b9875d5 100644
--- a/robolectric-utils/src/main/java/org/robolectric/util/Strings.java
+++ b/robolectric-utils/src/main/java/org/robolectric/util/Strings.java
@@ -22,4 +22,8 @@
     }
     return a.equals(b);
   }
+
+  public static String nullToEmpty(String string) {
+    return string == null ? "" : string;
+  }
 }