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