• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 package org.robolectric.shadows;
2 
3 import android.content.res.Resources;
4 import android.util.TypedValue;
5 import java.util.ArrayList;
6 import java.util.List;
7 import org.robolectric.res.AttrData;
8 import org.robolectric.res.FsFile;
9 import org.robolectric.res.ResType;
10 import org.robolectric.res.TypedResource;
11 import org.robolectric.util.Util;
12 
13 public class Converter<T> {
14   private static int nextStringCookie = 0xbaaa5;
15 
getNextStringCookie()16   synchronized static int getNextStringCookie() {
17     return nextStringCookie++;
18   }
19 
getConverterFor(AttrData attrData, String type)20   static Converter getConverterFor(AttrData attrData, String type) {
21     switch (type) {
22       case "enum":
23         return new EnumConverter(attrData);
24       case "flag":
25       case "flags": // because {@link ResourceTable#gFormatFlags} uses "flags"
26         return new FlagConverter(attrData);
27       case "boolean":
28         return new FromBoolean();
29       case "color":
30         return new FromColor();
31       case "dimension":
32         return new FromDimen();
33       case "float":
34         return new FromFloat();
35       case "integer":
36         return new FromInt();
37       case "string":
38         return new FromCharSequence();
39       case "fraction":
40         return new FromFraction();
41       default:
42         throw new UnsupportedOperationException("Type not supported: " + type);
43     }
44   }
45 
46   // TODO: Handle 'anim' resources
getConverter(ResType resType)47   public static Converter getConverter(ResType resType) {
48     switch (resType) {
49       case ATTR_DATA:
50         return new FromAttrData();
51       case BOOLEAN:
52         return new FromBoolean();
53       case CHAR_SEQUENCE:
54         return new FromCharSequence();
55       case COLOR:
56       case DRAWABLE:
57         return new FromColor();
58       case COLOR_STATE_LIST:
59       case LAYOUT:
60         return new FromFilePath();
61       case DIMEN:
62         return new FromDimen();
63       case FILE:
64         return new FromFile();
65       case FLOAT:
66         return new FromFloat();
67       case INTEGER:
68         return new FromInt();
69       case FRACTION:
70         return new FromFraction();
71       case CHAR_SEQUENCE_ARRAY:
72       case INTEGER_ARRAY:
73       case TYPED_ARRAY:
74         return new FromArray();
75       case STYLE:
76         return new Converter();
77       default:
78         throw new UnsupportedOperationException("can't convert from " + resType.name());
79     }
80   }
81 
asCharSequence(TypedResource typedResource)82   public CharSequence asCharSequence(TypedResource typedResource) {
83     return typedResource.asString();
84   }
85 
asInt(TypedResource typedResource)86   public int asInt(TypedResource typedResource) {
87     throw cantDo("asInt");
88   }
89 
getItems(TypedResource typedResource)90   public List<TypedResource> getItems(TypedResource typedResource) {
91     return new ArrayList<>();
92   }
93 
fillTypedValue(T data, TypedValue typedValue)94   public boolean fillTypedValue(T data, TypedValue typedValue) {
95     return false;
96   }
97 
cantDo(String operation)98   private UnsupportedOperationException cantDo(String operation) {
99     return new UnsupportedOperationException(getClass().getName() + " doesn't support " + operation);
100   }
101 
102   public static class FromAttrData extends Converter<AttrData> {
103     @Override
asCharSequence(TypedResource typedResource)104     public CharSequence asCharSequence(TypedResource typedResource) {
105       return typedResource.asString();
106     }
107 
108     @Override
fillTypedValue(AttrData data, TypedValue typedValue)109     public boolean fillTypedValue(AttrData data, TypedValue typedValue) {
110       typedValue.type = TypedValue.TYPE_STRING;
111       return false;
112     }
113   }
114 
115   public static class FromCharSequence extends Converter<String> {
116     @Override
asCharSequence(TypedResource typedResource)117     public CharSequence asCharSequence(TypedResource typedResource) {
118       return typedResource.asString().trim();
119     }
120 
121     @Override
asInt(TypedResource typedResource)122     public int asInt(TypedResource typedResource) {
123       return convertInt(typedResource.asString().trim());
124     }
125 
126     @Override
fillTypedValue(String data, TypedValue typedValue)127     public boolean fillTypedValue(String data, TypedValue typedValue) {
128       typedValue.type = TypedValue.TYPE_STRING;
129       typedValue.data = 0;
130       typedValue.assetCookie = getNextStringCookie();
131       typedValue.string = data;
132       return true;
133     }
134   }
135 
136   public static class FromColor extends Converter<String> {
137     @Override
fillTypedValue(String data, TypedValue typedValue)138     public boolean fillTypedValue(String data, TypedValue typedValue) {
139       try {
140         typedValue.type =  ResourceHelper.getColorType(data);
141         typedValue.data = ResourceHelper.getColor(data);
142         typedValue.assetCookie = 0;
143         typedValue.string = null;
144         return true;
145       } catch (NumberFormatException nfe) {
146         return false;
147       }
148     }
149 
150     @Override
asInt(TypedResource typedResource)151     public int asInt(TypedResource typedResource) {
152       return ResourceHelper.getColor(typedResource.asString().trim());
153     }
154   }
155 
156   public static class FromFilePath extends Converter<String> {
157     @Override
fillTypedValue(String data, TypedValue typedValue)158     public boolean fillTypedValue(String data, TypedValue typedValue) {
159       typedValue.type = TypedValue.TYPE_STRING;
160       typedValue.data = 0;
161       typedValue.string = data;
162       typedValue.assetCookie = getNextStringCookie();
163       return true;
164     }
165   }
166 
167   public static class FromArray extends Converter {
168     @Override
getItems(TypedResource typedResource)169     public List<TypedResource> getItems(TypedResource typedResource) {
170       return (List<TypedResource>) typedResource.getData();
171     }
172   }
173 
174   private static class FromInt extends Converter<String> {
175     @Override
fillTypedValue(String data, TypedValue typedValue)176     public boolean fillTypedValue(String data, TypedValue typedValue) {
177       try {
178         if (data.startsWith("0x")) {
179           typedValue.type = data.startsWith("0x") ? TypedValue.TYPE_INT_HEX : TypedValue.TYPE_INT_DEC;
180         } else {
181           typedValue.type = TypedValue.TYPE_INT_DEC;
182         }
183         typedValue.data = convertInt(data);
184         typedValue.assetCookie = 0;
185         typedValue.string = null;
186         return true;
187       } catch (NumberFormatException nfe) {
188         return false;
189       }
190     }
191 
192     @Override
asInt(TypedResource typedResource)193     public int asInt(TypedResource typedResource) {
194       return convertInt(typedResource.asString().trim());
195     }
196   }
197 
198   private static class FromFraction extends Converter<String> {
199     @Override
fillTypedValue(String data, TypedValue typedValue)200     public boolean fillTypedValue(String data, TypedValue typedValue) {
201       return ResourceHelper.parseFloatAttribute(null, data, typedValue, false);
202     }
203   }
204 
205   private static class FromFile extends Converter<Object> {
206     @Override
fillTypedValue(Object data, TypedValue typedValue)207     public boolean fillTypedValue(Object data, TypedValue typedValue) {
208       typedValue.type = TypedValue.TYPE_STRING;
209       typedValue.data = 0;
210       typedValue.string = data instanceof FsFile ? ((FsFile) data).getPath() : (CharSequence) data;
211       typedValue.assetCookie = getNextStringCookie();
212       return true;
213     }
214   }
215 
216   private static class FromFloat extends Converter<String> {
217     @Override
fillTypedValue(String data, TypedValue typedValue)218     public boolean fillTypedValue(String data, TypedValue typedValue) {
219       return ResourceHelper.parseFloatAttribute(null, data, typedValue, false);
220     }
221   }
222 
223   private static class FromBoolean extends Converter<String> {
224     @Override
fillTypedValue(String data, TypedValue typedValue)225     public boolean fillTypedValue(String data, TypedValue typedValue) {
226       typedValue.type = TypedValue.TYPE_INT_BOOLEAN;
227       typedValue.assetCookie = 0;
228       typedValue.string = null;
229 
230       if ("true".equalsIgnoreCase(data)) {
231         typedValue.data = 1;
232         return true;
233       } else if ("false".equalsIgnoreCase(data)) {
234         typedValue.data = 0;
235         return true;
236       }
237       return false;
238     }
239   }
240 
241   private static class FromDimen extends Converter<String> {
242     @Override
fillTypedValue(String data, TypedValue typedValue)243     public boolean fillTypedValue(String data, TypedValue typedValue) {
244       return ResourceHelper.parseFloatAttribute(null, data, typedValue, false);
245     }
246   }
247 
convertInt(String rawValue)248   private static int convertInt(String rawValue) {
249     try {
250       // Decode into long, because there are some large hex values in the android resource files
251       // (e.g. config_notificationsBatteryLowARGB = 0xFFFF0000 in sdk 14).
252       // Integer.decode() does not support large, i.e. negative values in hex numbers.
253       // try parsing decimal number
254       return (int) Long.parseLong(rawValue);
255     } catch (NumberFormatException nfe) {
256       // try parsing hex number
257       return Long.decode(rawValue).intValue();
258     }
259   }
260 
261   private static class EnumConverter extends EnumOrFlagConverter {
EnumConverter(AttrData attrData)262     public EnumConverter(AttrData attrData) {
263       super(attrData);
264     }
265 
266     @Override
fillTypedValue(String data, TypedValue typedValue)267     public boolean fillTypedValue(String data, TypedValue typedValue) {
268       try {
269         typedValue.type = TypedValue.TYPE_INT_HEX;
270         try {
271           typedValue.data = findValueFor(data);
272         } catch (Resources.NotFoundException e) {
273           typedValue.data = convertInt(data);
274         }
275         typedValue.assetCookie = 0;
276         typedValue.string = null;
277         return true;
278       } catch (Exception e) {
279         return false;
280       }
281     }
282   }
283 
284   private static class FlagConverter extends EnumOrFlagConverter {
FlagConverter(AttrData attrData)285     public FlagConverter(AttrData attrData) {
286       super(attrData);
287     }
288 
289     @Override
fillTypedValue(String data, TypedValue typedValue)290     public boolean fillTypedValue(String data, TypedValue typedValue) {
291       int flags = 0;
292 
293       try {
294         for (String key : data.split("\\|", 0)) {
295           flags |= findValueFor(key);
296         }
297       } catch (Resources.NotFoundException e) {
298         try {
299           flags = Integer.decode(data);
300         } catch (NumberFormatException e1) {
301           return false;
302         }
303       } catch (Exception e) {
304         return false;
305       }
306 
307       typedValue.type = TypedValue.TYPE_INT_HEX;
308       typedValue.data = flags;
309       typedValue.assetCookie = 0;
310       typedValue.string = null;
311       return true;
312     }
313   }
314 
315   private static class EnumOrFlagConverter extends Converter<String> {
316     private final AttrData attrData;
317 
EnumOrFlagConverter(AttrData attrData)318     public EnumOrFlagConverter(AttrData attrData) {
319       this.attrData = attrData;
320     }
321 
findValueFor(String key)322     protected int findValueFor(String key) {
323       key = (key == null) ? null : key.trim();
324       String valueFor = attrData.getValueFor(key);
325       if (valueFor == null) {
326         // Maybe they have passed in the value directly, rather than the name.
327         if (attrData.isValue(key)) {
328           valueFor = key;
329         } else {
330           throw new Resources.NotFoundException("no value found for " + key);
331         }
332       }
333       return Util.parseInt(valueFor);
334     }
335   }
336 }
337