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