• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 package org.robolectric.manifest;
2 
3 import java.util.LinkedHashMap;
4 import java.util.List;
5 import java.util.Map;
6 import org.robolectric.res.ResName;
7 import org.robolectric.res.ResourceTable;
8 import org.robolectric.res.TypedResource;
9 import org.robolectric.res.android.ResTable_config;
10 import org.w3c.dom.NamedNodeMap;
11 import org.w3c.dom.Node;
12 
13 public final class MetaData {
14   private final Map<String, Object> valueMap = new LinkedHashMap<>();
15   private final Map<String, VALUE_TYPE> typeMap = new LinkedHashMap<>();
16   private boolean initialised;
17 
MetaData(List<Node> nodes)18   public MetaData(List<Node> nodes) {
19     for (Node metaNode : nodes) {
20       NamedNodeMap attributes = metaNode.getAttributes();
21       Node nameAttr = attributes.getNamedItem("android:name");
22       Node valueAttr = attributes.getNamedItem("android:value");
23       Node resourceAttr = attributes.getNamedItem("android:resource");
24 
25       if (valueAttr != null) {
26         valueMap.put(nameAttr.getNodeValue(), valueAttr.getNodeValue());
27         typeMap.put(nameAttr.getNodeValue(), VALUE_TYPE.VALUE);
28       } else if (resourceAttr != null) {
29         valueMap.put(nameAttr.getNodeValue(), resourceAttr.getNodeValue());
30         typeMap.put(nameAttr.getNodeValue(), VALUE_TYPE.RESOURCE);
31       }
32     }
33   }
34 
init(ResourceTable resourceTable, String packageName)35   public void init(ResourceTable resourceTable, String packageName) throws RoboNotFoundException {
36     if (!initialised) {
37       for (Map.Entry<String,VALUE_TYPE> entry : typeMap.entrySet()) {
38         String value = valueMap.get(entry.getKey()).toString();
39         if (value.startsWith("@")) {
40           ResName resName = ResName.qualifyResName(value.substring(1), packageName, null);
41 
42           switch (entry.getValue()) {
43             case RESOURCE:
44               // Was provided by resource attribute, store resource ID
45               valueMap.put(entry.getKey(), resourceTable.getResourceId(resName));
46               break;
47             case VALUE:
48               // Was provided by value attribute, need to inferFromValue it
49               TypedResource<?> typedRes = resourceTable.getValue(resName, new ResTable_config());
50               // The typed resource's data is always a String, so need to inferFromValue the value.
51               if (typedRes == null) {
52                 throw new RoboNotFoundException(resName.getFullyQualifiedName());
53               }
54               switch (typedRes.getResType()) {
55                 case BOOLEAN: case COLOR: case INTEGER: case FLOAT:
56                   valueMap.put(entry.getKey(), parseValue(typedRes.getData().toString()));
57                   break;
58                 default:
59                   valueMap.put(entry.getKey(),typedRes.getData());
60               }
61               break;
62           }
63         } else if (entry.getValue() == VALUE_TYPE.VALUE) {
64           // Raw value, so inferFromValue it in to the appropriate type and store it
65           valueMap.put(entry.getKey(), parseValue(value));
66         }
67       }
68       // Finished parsing, mark as initialised
69       initialised = true;
70     }
71   }
72 
getValueMap()73   public Map<String, Object> getValueMap() {
74     return valueMap;
75   }
76 
77   private enum VALUE_TYPE {
78     RESOURCE,
79     VALUE
80   }
81 
parseValue(String value)82   private Object parseValue(String value) {
83     if (value == null) {
84       return null;
85     } else if ("true".equals(value)) {
86       return true;
87     } else if ("false".equals(value)) {
88       return false;
89     } else if (value.startsWith("#")) {
90       // if it's a color, add it and continue
91       try {
92         return getColor(value);
93       } catch (NumberFormatException e) {
94             /* Not a color */
95       }
96     } else if (value.contains(".")) {
97       // most likely a float
98       try {
99         return Float.parseFloat(value);
100       } catch (NumberFormatException e) {
101         // Not a float
102       }
103     } else {
104       // if it's an int, add it and continue
105       try {
106         return Integer.parseInt(value);
107       } catch (NumberFormatException ei) {
108         // Not an int
109       }
110     }
111 
112     // Not one of the above types, keep as String
113     return value;
114   }
115 
116   // todo: this is copied from ResourceHelper, dedupe
117   /**
118    * Returns the color value represented by the given string value
119    * @param value the color value
120    * @return the color as an int
121    * @throws NumberFormatException if the conversion failed.
122    */
getColor(String value)123   public static int getColor(String value) {
124     if (value != null) {
125       if (value.startsWith("#") == false) {
126         throw new NumberFormatException(
127             String.format("Color value '%s' must start with #", value));
128       }
129 
130       value = value.substring(1);
131 
132       // make sure it's not longer than 32bit
133       if (value.length() > 8) {
134         throw new NumberFormatException(String.format(
135             "Color value '%s' is too long. Format is either" +
136                 "#AARRGGBB, #RRGGBB, #RGB, or #ARGB",
137             value));
138       }
139 
140       if (value.length() == 3) { // RGB format
141         char[] color = new char[8];
142         color[0] = color[1] = 'F';
143         color[2] = color[3] = value.charAt(0);
144         color[4] = color[5] = value.charAt(1);
145         color[6] = color[7] = value.charAt(2);
146         value = new String(color);
147       } else if (value.length() == 4) { // ARGB format
148         char[] color = new char[8];
149         color[0] = color[1] = value.charAt(0);
150         color[2] = color[3] = value.charAt(1);
151         color[4] = color[5] = value.charAt(2);
152         color[6] = color[7] = value.charAt(3);
153         value = new String(color);
154       } else if (value.length() == 6) {
155         value = "FF" + value;
156       }
157 
158       // this is a RRGGBB or AARRGGBB value
159 
160       // Integer.parseInt will fail to inferFromValue strings like "ff191919", so we use
161       // a Long, but cast the result back into an int, since we know that we're only
162       // dealing with 32 bit values.
163       return (int)Long.parseLong(value, 16);
164     }
165 
166     throw new NumberFormatException();
167   }
168 
169 }
170