ShadowAssetManager dereferences attrs better

ShadowAssetManager now looks in the full style tree (children and
themes) while dereferencing parent styles.

In order, ShadowAssetManager will search:
 - All known children in this style path
 - The Theme Style
 - The ResourceLoader

Improves the logging information when a parent style can't be found.
diff --git a/src/main/java/org/robolectric/res/AttrData.java b/src/main/java/org/robolectric/res/AttrData.java
index 3bec1d0..0ebc561 100644
--- a/src/main/java/org/robolectric/res/AttrData.java
+++ b/src/main/java/org/robolectric/res/AttrData.java
@@ -45,10 +45,23 @@
   }
 
   @Override public String toString() {
-  return "AttrData{" +
-    "name='" + name + '\'' +
-    ", format='" + format + '\'' +
-    '}';
+
+    StringBuilder builder = new StringBuilder("AttrData{name='")
+        .append(name)
+        .append("', format='")
+        .append(format)
+        .append('\'');
+    if (pairs != null) {
+      for (Pair p : pairs) {
+        builder.append(' ')
+            .append(p.name)
+            .append("='")
+            .append(p.value)
+            .append('\'');
+      }
+    }
+    builder.append('}');
+    return builder.toString();
   }
 
   public static class Pair {
diff --git a/src/main/java/org/robolectric/res/ResName.java b/src/main/java/org/robolectric/res/ResName.java
index c532b07..7196df7 100644
--- a/src/main/java/org/robolectric/res/ResName.java
+++ b/src/main/java/org/robolectric/res/ResName.java
@@ -127,7 +127,7 @@
 
   public void mustBe(String expectedType) {
     if (!type.equals(expectedType)) {
-      throw new RuntimeException("expected " + getFullyQualifiedName() + " to be a " + expectedType);
+      throw new RuntimeException("expected " + getFullyQualifiedName() + " to be a " + expectedType + ", is a " + type);
     }
   }
 }
diff --git a/src/main/java/org/robolectric/shadows/ShadowAssetManager.java b/src/main/java/org/robolectric/shadows/ShadowAssetManager.java
index 32d9a73..58ac810 100644
--- a/src/main/java/org/robolectric/shadows/ShadowAssetManager.java
+++ b/src/main/java/org/robolectric/shadows/ShadowAssetManager.java
@@ -13,6 +13,8 @@
 import java.util.LinkedList;
 import java.util.List;
 import java.util.Map;
+import java.util.List;
+import java.util.ArrayList;
 import org.jetbrains.annotations.NotNull;
 import org.robolectric.AndroidManifest;
 import org.robolectric.Robolectric;
@@ -123,7 +125,7 @@
     ResName themeStyleName = resourceIndex.getResName(shadowOf(theTheme).getStyleResourceId());
     if (themeStyleName == null) return false; // is this right?
 
-    Style themeStyle = resolveStyle(resourceLoader, themeStyleName, getQualifiers());
+    Style themeStyle = resolveStyle(resourceLoader, null, themeStyleName, getQualifiers());
 
     //// Load the theme attribute for the default style attributes. E.g., attr/buttonStyle
     //ResName defStyleName = resourceLoader.getResourceIndex().getResName(ident);
@@ -258,7 +260,7 @@
     ResourceIndex resourceIndex = resourceLoader.getResourceIndex();
     ResName resName = resourceIndex.getResName(styleRes);
 
-    Style style = resolveStyle(resourceLoader, resName, assertManager.getQualifiers());
+    Style style = resolveStyle(resourceLoader, null, resName, assertManager.getQualifiers());
 
     appliedThemeStyles.get(theme).add(new OverlayedStyle(style, force));
   }
@@ -292,11 +294,11 @@
     return themesById.get(internalThemeId);
   }
 
-  static Style resolveStyle(ResourceLoader resourceLoader, @NotNull ResName themeStyleName, String qualifiers) {
+  static Style resolveStyle(ResourceLoader resourceLoader, Style appTheme, @NotNull ResName themeStyleName, String qualifiers) {
     TypedResource themeStyleResource = resourceLoader.getValue(themeStyleName, qualifiers);
     if (themeStyleResource == null) return null;
     StyleData themeStyleData = (StyleData) themeStyleResource.getData();
-    return new StyleResolver(resourceLoader, themeStyleData, themeStyleName, qualifiers);
+    return new StyleResolver(resourceLoader, themeStyleData, appTheme, themeStyleName, qualifiers);
   }
 
   TypedResource getAndResolve(int resId, String qualifiers, boolean resolveRefs) {
@@ -362,41 +364,147 @@
 
   static class StyleResolver implements Style {
     private final ResourceLoader resourceLoader;
-    private final StyleData leafStyle;
+    private final List<StyleData> styles = new ArrayList<StyleData>();
+    private final Style theme;
     private final ResName myResName;
     private final String qualifiers;
 
-    public StyleResolver(ResourceLoader resourceLoader, StyleData styleData, ResName myResName, String qualifiers) {
+    public StyleResolver(ResourceLoader resourceLoader, StyleData styleData,
+        Style theme, ResName myResName, String qualifiers) {
       this.resourceLoader = resourceLoader;
-      this.leafStyle = styleData;
+      this.theme = theme;
       this.myResName = myResName;
       this.qualifiers = qualifiers;
+      styles.add(styleData);
     }
 
     @Override public Attribute getAttrValue(ResName resName) {
       resName.mustBe("attr");
-      StyleData currentStyle = leafStyle;
-      while (currentStyle != null) {
-        Attribute value = currentStyle.getAttrValue(resName);
+      for (StyleData style : styles) {
+        Attribute value = style.getAttrValue(resName);
         if (value != null) return value;
-        currentStyle = getParent(currentStyle);
       }
+      int initialSize = styles.size();
+      while (hasParent(styles.get(styles.size() - 1))) {
+        StyleData parent = getParent(styles.get(styles.size() - 1));
+        if (parent != null) {
+          styles.add(parent);
+        } else {
+          break;
+        }
+      }
+      for (int i = initialSize; i < styles.size(); i++) {
+        StyleData style = styles.get(i);
+        Attribute value = style.getAttrValue(resName);
+        if (value != null) return value;
+      }
+      if (theme != null) {
+        Attribute value = theme.getAttrValue(resName);
+        if (value != null) return value;
+      }
+
       return null;
     }
 
-    private StyleData getParent(StyleData currentStyle) {
-      String parent = currentStyle.getParent();
+    private static String getParentStyleName(StyleData style) {
+      if (style == null) {
+        return null;
+      }
+      String parent = style.getParent();
+      if (parent == null || parent.isEmpty()) {
+        parent = null;
+        String name = style.getName();
+        if (name.contains(".")) {
+          parent = name.substring(0, name.lastIndexOf('.'));
+          if (parent.isEmpty()) {
+            return null;
+          }
+        }
+      }
+      return parent;
+    }
 
-      if (parent == null || parent.isEmpty()) return null;
+    private static boolean hasParent(StyleData style) {
+      if (style == null) return false;
+      String parent = style.getParent();
+      return parent != null && !parent.isEmpty();
+    }
+
+    private StyleData getParent(StyleData style) {
+      String parent = getParentStyleName(style);
+
+      if (parent == null) return null;
 
       if (parent.startsWith("@")) parent = parent.substring(1);
 
-      ResName style = ResName.qualifyResName(parent, currentStyle.getPackageName(), "style");
-      TypedResource typedResource = resourceLoader.getValue(style, qualifiers);
+      ResName styleRef = ResName.qualifyResName(parent, style.getPackageName(), "style");
+
+      styleRef = dereferenceResName(styleRef);
+
+      TypedResource typedResource = resourceLoader.getValue(styleRef, qualifiers);
+
       if (typedResource == null) {
-        throw new RuntimeException("huh? can't find parent for " + currentStyle);
+        StringBuilder builder = new StringBuilder("Could not find any resource ")
+            .append(" from reference ").append(styleRef)
+            .append(" from style ").append(style)
+            .append(" with theme ").append(theme);
+        throw new RuntimeException(builder.toString());
       }
-      return (StyleData) typedResource.getData();
+
+      Object data = typedResource.getData();
+      if (data instanceof StyleData) {
+        return (StyleData) data;
+      } else {
+        StringBuilder builder = new StringBuilder(styleRef.toString())
+            .append(" does not resolve to a Style.")
+            .append(" got ").append(data).append(" instead. ")
+            .append(" from style ").append(style)
+            .append(" with theme ").append(theme);
+        throw new RuntimeException(builder.toString());
+      }
     }
+
+    private ResName dereferenceResName(ResName res) {
+      ResName styleRef = res;
+      boolean dereferencing = true;
+      while ("attr".equals(styleRef.type) && dereferencing) {
+        dereferencing = false;
+        for (StyleData parentStyle : styles) {
+          Attribute value = parentStyle.getAttrValue(styleRef);
+          if (value != null) {
+            styleRef = dereferenceAttr(value);
+            dereferencing = true;
+            break;
+          }
+        }
+        if (!dereferencing && theme != null) {
+          Attribute value = theme.getAttrValue(styleRef);
+          if (value != null) {
+            styleRef = dereferenceAttr(value);
+            dereferencing = true;
+          }
+        }
+      }
+
+      return styleRef;
+    }
+
+    private ResName dereferenceAttr(Attribute attr) {
+      if (attr.isResourceReference()) {
+        return attr.getResourceReference();
+      } else if (attr.isStyleReference()) {
+        return attr.getStyleReference();
+      }
+      throw new RuntimeException("Found a " + attr + " but can't cast it :(");
+    }
+
+    public String toString() {
+      return "StyleResolver{"
+          + "name=" + myResName
+          + ", of=" + styles.get(0)
+          + "}";
+    }
+
   }
+
 }
diff --git a/src/main/java/org/robolectric/shadows/ShadowResources.java b/src/main/java/org/robolectric/shadows/ShadowResources.java
index f698026..348c4f2 100644
--- a/src/main/java/org/robolectric/shadows/ShadowResources.java
+++ b/src/main/java/org/robolectric/shadows/ShadowResources.java
@@ -123,7 +123,7 @@
       ResName themeStyleName = getResName(themeResourceId);
       if (DEBUG) System.out.println("themeStyleName = " + themeStyleName);
 
-      theme = ShadowAssetManager.resolveStyle(resourceLoader, themeStyleName, shadowAssetManager.getQualifiers());
+      theme = ShadowAssetManager.resolveStyle(resourceLoader, null, themeStyleName, shadowAssetManager.getQualifiers());
 
       if (defStyleAttr != 0) {
         // Load the theme attribute for the default style attributes. E.g., attr/buttonStyle
@@ -145,7 +145,7 @@
 
           if (defStyleAttribute.isResourceReference()) {
             ResName defStyleResName = defStyleAttribute.getResourceReference();
-            defStyleFromAttr = ShadowAssetManager.resolveStyle(resourceLoader, defStyleResName, shadowAssetManager.getQualifiers());
+            defStyleFromAttr = ShadowAssetManager.resolveStyle(resourceLoader, theme, defStyleResName, shadowAssetManager.getQualifiers());
           }
         }
       }
@@ -162,7 +162,7 @@
           styleAttributeResName = attrValue.getStyleReference();
         }
       }
-      styleAttrStyle = ShadowAssetManager.resolveStyle(resourceLoader, styleAttributeResName, shadowAssetManager.getQualifiers());
+      styleAttrStyle = ShadowAssetManager.resolveStyle(resourceLoader, theme, styleAttributeResName, shadowAssetManager.getQualifiers());
     }
 
     if (defStyleRes != 0) {
@@ -177,7 +177,7 @@
           }
         }
       }
-      defStyleFromRes = ShadowAssetManager.resolveStyle(resourceLoader, resName, shadowAssetManager.getQualifiers());
+      defStyleFromRes = ShadowAssetManager.resolveStyle(resourceLoader, theme, resName, shadowAssetManager.getQualifiers());
     }
 
     List<Attribute> attributes = new ArrayList<Attribute>();
diff --git a/src/test/java/org/robolectric/R.java b/src/test/java/org/robolectric/R.java
index 32db3cc..5397554 100644
--- a/src/test/java/org/robolectric/R.java
+++ b/src/test/java/org/robolectric/R.java
@@ -330,6 +330,7 @@
     public static final int Theme_ThirdTheme = 0x11008;
     public static final int MyBlackTheme = 0x11009;
     public static final int MyBlueTheme = 0x1100a;
+    public static final int IndirectButtonStyle = 0x1100b;
   }
 
   public static final class fraction {
diff --git a/src/test/java/org/robolectric/shadows/ThemeTest.java b/src/test/java/org/robolectric/shadows/ThemeTest.java
index 47a872d..2228167 100644
--- a/src/test/java/org/robolectric/shadows/ThemeTest.java
+++ b/src/test/java/org/robolectric/shadows/ThemeTest.java
@@ -90,6 +90,7 @@
     TestActivity activity = buildActivity(TestActivityWithAnotherTheme.class).create().get();
     ResourceLoader resourceLoader = Robolectric.shadowOf(activity.getResources()).getResourceLoader();
     Style style = ShadowAssetManager.resolveStyle(resourceLoader,
+        null,
         new ResName(TestUtil.TEST_PACKAGE, "style", "Widget.AnotherTheme.Button.Blarf"), "");
     assertThat(style.getAttrValue(new ResName("android", "attr", "background")).value)
         .isEqualTo("#ffff0000");
@@ -99,10 +100,23 @@
     TestActivity activity = buildActivity(TestActivityWithAnotherTheme.class).create().get();
     ResourceLoader resourceLoader = Robolectric.shadowOf(activity.getResources()).getResourceLoader();
     Style style = ShadowAssetManager.resolveStyle(resourceLoader,
+        null,
         new ResName(TestUtil.TEST_PACKAGE, "style", "Theme.MyTheme"), "");
     assertThat(style.getAttrValue(new ResName("android", "attr", "background"))).isNull();
   }
 
+
+  @Test public void shouldApplyParentStylesFromAttrs() throws Exception {
+    TestActivity activity = buildActivity(TestActivityWithAnotherTheme.class).create().get();
+    ResourceLoader resourceLoader = Robolectric.shadowOf(activity.getResources()).getResourceLoader();
+    Style theme = ShadowAssetManager.resolveStyle(resourceLoader, null,
+        new ResName(TestUtil.TEST_PACKAGE, "style", "Theme.AnotherTheme"), "");
+    Style style = ShadowAssetManager.resolveStyle(resourceLoader, theme,
+        new ResName(TestUtil.TEST_PACKAGE, "style", "IndirectButtonStyle"), "");
+    assertThat(style.getAttrValue(new ResName("android", "attr", "background")).value)
+        .isEqualTo("#ffff0000");
+  }
+
   public static class TestActivity extends Activity {
     @Override protected void onCreate(Bundle savedInstanceState) {
       super.onCreate(savedInstanceState);
diff --git a/src/test/resources/res/values/themes.xml b/src/test/resources/res/values/themes.xml
index 88e9b4d..e30f4af 100644
--- a/src/test/resources/res/values/themes.xml
+++ b/src/test/resources/res/values/themes.xml
@@ -54,4 +54,9 @@
     <item name="android:windowBackground">@color/blue</item>
     <item name="android:textColor">@color/white</item>
   </style>
+
+  <style name="IndirectButtonStyle" parent="@android:attr/buttonStyle">
+    <item name="android:minHeight">12dp</item>
+  </style>
+
 </resources>