• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 package org.robolectric.res;
2 
3 import java.util.ArrayList;
4 import java.util.List;
5 import java.util.Objects;
6 import org.robolectric.res.android.ResTable_config;
7 
8 public class StyleResolver implements Style {
9   private final List<StyleData> styles = new ArrayList<>();
10   private final ResourceTable appResourceTable;
11   private final ResourceTable systemResourceTable;
12   private final Style theme;
13   private final ResName myResName;
14   private final ResTable_config config;
15 
StyleResolver(ResourceTable appResourceTable, ResourceTable systemResourceTable, StyleData styleData, Style theme, ResName myResName, ResTable_config config)16   public StyleResolver(ResourceTable appResourceTable, ResourceTable systemResourceTable, StyleData styleData,
17                        Style theme, ResName myResName, ResTable_config config) {
18     this.appResourceTable = appResourceTable;
19     this.systemResourceTable = systemResourceTable;
20     this.theme = theme;
21     this.myResName = myResName;
22     this.config = config;
23     styles.add(styleData);
24   }
25 
getAttrValue(ResName resName)26   @Override public AttributeResource getAttrValue(ResName resName) {
27     for (StyleData style : styles) {
28       AttributeResource value = style.getAttrValue(resName);
29       if (value != null) return value;
30     }
31     int initialSize = styles.size();
32     while (hasParent(styles.get(styles.size() - 1))) {
33       StyleData parent = getParent(styles.get(styles.size() - 1));
34       if (parent != null) {
35         styles.add(parent);
36       } else {
37         break;
38       }
39     }
40     for (int i = initialSize; i < styles.size(); i++) {
41       StyleData style = styles.get(i);
42       AttributeResource value = style.getAttrValue(resName);
43       if (value != null) return value;
44     }
45 
46     // todo: is this tested?
47     if (theme != null) {
48       AttributeResource value = theme.getAttrValue(resName);
49       if (value != null) return value;
50     }
51 
52     return null;
53   }
54 
getParentStyleName(StyleData style)55   private static String getParentStyleName(StyleData style) {
56     if (style == null) {
57       return null;
58     }
59     String parent = style.getParent();
60     if (parent == null || parent.isEmpty()) {
61       parent = null;
62       String name = style.getName();
63       if (name.contains(".")) {
64         parent = name.substring(0, name.lastIndexOf('.'));
65         if (parent.isEmpty()) {
66           return null;
67         }
68       }
69     }
70     return parent;
71   }
72 
hasParent(StyleData style)73   private static boolean hasParent(StyleData style) {
74     if (style == null) return false;
75     String parent = style.getParent();
76     return parent != null && !parent.isEmpty();
77   }
78 
getParent(StyleData style)79   private StyleData getParent(StyleData style) {
80     String parent = getParentStyleName(style);
81 
82     if (parent == null) return null;
83 
84     if (parent.startsWith("@")) parent = parent.substring(1);
85 
86     ResName styleRef = ResName.qualifyResName(parent, style.getPackageName(), "style");
87 
88     styleRef = dereferenceResName(styleRef);
89 
90     // TODO: Refactor this to a ResourceLoaderChooser
91     ResourceTable resourceProvider = "android".equals(styleRef.packageName) ? systemResourceTable : appResourceTable;
92     TypedResource typedResource = resourceProvider.getValue(styleRef, config);
93 
94     if (typedResource == null) {
95       StringBuilder builder = new StringBuilder("Could not find any resource")
96           .append(" from reference ").append(styleRef)
97           .append(" from ").append(style)
98           .append(" with ").append(theme);
99       throw new RuntimeException(builder.toString());
100     }
101 
102     Object data = typedResource.getData();
103     if (data instanceof StyleData) {
104       return (StyleData) data;
105     } else {
106       StringBuilder builder = new StringBuilder(styleRef.toString())
107           .append(" does not resolve to a Style.")
108           .append(" got ").append(data).append(" instead. ")
109           .append(" from ").append(style)
110           .append(" with ").append(theme);
111       throw new RuntimeException(builder.toString());
112     }
113   }
114 
dereferenceResName(ResName res)115   private ResName dereferenceResName(ResName res) {
116     ResName styleRef = res;
117     boolean dereferencing = true;
118     while ("attr".equals(styleRef.type) && dereferencing) {
119       dereferencing = false;
120       for (StyleData parentStyle : styles) {
121         AttributeResource value = parentStyle.getAttrValue(styleRef);
122         if (value != null) {
123           styleRef = dereferenceAttr(value);
124           dereferencing = true;
125           break;
126         }
127       }
128       if (!dereferencing && theme != null) {
129         AttributeResource value = theme.getAttrValue(styleRef);
130         if (value != null) {
131           styleRef = dereferenceAttr(value);
132           dereferencing = true;
133         }
134       }
135     }
136 
137     return styleRef;
138   }
139 
dereferenceAttr(AttributeResource attr)140   private ResName dereferenceAttr(AttributeResource attr) {
141     if (attr.isResourceReference()) {
142       return attr.getResourceReference();
143     } else if (attr.isStyleReference()) {
144       return attr.getStyleReference();
145     }
146     throw new RuntimeException("Found a " + attr + " but can't cast it :(");
147   }
148 
149   @Override
equals(Object obj)150   public boolean equals(Object obj) {
151     if (!(obj instanceof StyleResolver)) {
152       return false;
153     }
154     StyleResolver other = (StyleResolver) obj;
155 
156     return ((theme == null && other.theme == null) || (theme != null && theme.equals(other.theme)))
157         && ((myResName == null && other.myResName == null)
158             || (myResName != null && myResName.equals(other.myResName)))
159         && Objects.equals(config, other.config);
160   }
161 
162   @Override
hashCode()163   public int hashCode() {
164     int hashCode = 0;
165     hashCode = 31 * hashCode + (theme != null ? theme.hashCode() : 0);
166     hashCode = 31 * hashCode + (myResName != null ? myResName.hashCode() : 0);
167     hashCode = 31 * hashCode + (config != null ? config.hashCode() : 0);
168     return hashCode;
169   }
170 
171   @Override
toString()172   public String toString() {
173     return styles.get(0) + " (and parents)";
174   }
175 
176 }