Home | History | Annotate | Download | only in res
      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 
     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 
     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 
     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 
     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 
     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 
    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 
    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
    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
    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
    172   public String toString() {
    173     return styles.get(0) + " (and parents)";
    174   }
    175 
    176 }