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 }