package org.robolectric.shadows; import android.util.TypedValue; import org.robolectric.res.AttrData; import org.robolectric.res.FsFile; import org.robolectric.res.ResType; import org.robolectric.res.TypedResource; import org.robolectric.util.Util; import java.util.List; public class Converter<T> { private static int nextStringCookie = 0xbaaa5; synchronized static int getNextStringCookie() { return nextStringCookie++; } static Converter getConverterFor(AttrData attrData, String type) { switch (type) { case "enum": return new EnumConverter(attrData); case "flag": return new FlagConverter(attrData); case "boolean": return new FromBoolean(); case "color": return new FromColor(); case "dimension": return new FromDimen(); case "float": return new FromFloat(); case "integer": return new FromInt(); case "string": return new FromCharSequence(); case "fraction": return new FromFraction(); default: throw new UnsupportedOperationException("Type not supported: " + type); } } // TODO: Handle 'anim' resources public static Converter getConverter(ResType resType) { switch (resType) { case ATTR_DATA: return new FromAttrData(); case BOOLEAN: return new FromBoolean(); case CHAR_SEQUENCE: return new FromCharSequence(); case COLOR: case DRAWABLE: return new FromColor(); case COLOR_STATE_LIST: case LAYOUT: return new FromFilePath(); case DIMEN: return new FromDimen(); case FILE: return new FromFile(); case FLOAT: return new FromFloat(); case INTEGER: return new FromInt(); case FRACTION: return new FromFraction(); case CHAR_SEQUENCE_ARRAY: case INTEGER_ARRAY: case TYPED_ARRAY: return new FromArray(); case STYLE: return new Converter(); default: throw new UnsupportedOperationException("can't convert from " + resType.name()); } } public CharSequence asCharSequence(TypedResource typedResource) { return typedResource.asString(); } public int asInt(TypedResource typedResource) { throw cantDo("asInt"); } public List<TypedResource> getItems(TypedResource typedResource) { throw cantDo("getItems"); } public boolean fillTypedValue(T data, TypedValue typedValue) { return false; } private UnsupportedOperationException cantDo(String operation) { return new UnsupportedOperationException(getClass().getName() + " doesn't support " + operation); } public static class FromAttrData extends Converter<AttrData> { @Override public CharSequence asCharSequence(TypedResource typedResource) { return typedResource.asString(); } @Override public boolean fillTypedValue(AttrData data, TypedValue typedValue) { typedValue.type = TypedValue.TYPE_STRING; return false; } } public static class FromCharSequence extends Converter<String> { @Override public CharSequence asCharSequence(TypedResource typedResource) { return typedResource.asString().trim(); } @Override public int asInt(TypedResource typedResource) { return convertInt(typedResource.asString().trim()); } @Override public boolean fillTypedValue(String data, TypedValue typedValue) { typedValue.type = TypedValue.TYPE_STRING; typedValue.data = 0; typedValue.assetCookie = getNextStringCookie(); typedValue.string = data; return true; } } public static class FromColor extends Converter<String> { @Override public boolean fillTypedValue(String data, TypedValue typedValue) { try { typedValue.type = TypedValue.TYPE_INT_COLOR_ARGB8; typedValue.data = ResourceHelper.getColor(data); typedValue.assetCookie = 0; typedValue.string = null; return true; } catch (NumberFormatException nfe) { return false; } } @Override public int asInt(TypedResource typedResource) { return ResourceHelper.getColor(typedResource.asString().trim()); } } public static class FromFilePath extends Converter<String> { @Override public boolean fillTypedValue(String data, TypedValue typedValue) { typedValue.type = TypedValue.TYPE_STRING; typedValue.data = 0; typedValue.string = data; typedValue.assetCookie = getNextStringCookie(); return true; } } public static class FromArray extends Converter { @Override public List<TypedResource> getItems(TypedResource typedResource) { return (List<TypedResource>) typedResource.getData(); } } private static class FromInt extends Converter<String> { @Override public boolean fillTypedValue(String data, TypedValue typedValue) { try { typedValue.type = TypedValue.TYPE_INT_HEX; typedValue.data = convertInt(data); typedValue.assetCookie = 0; typedValue.string = null; return true; } catch (NumberFormatException nfe) { return false; } } @Override public int asInt(TypedResource typedResource) { return convertInt(typedResource.asString().trim()); } } private static class FromFraction extends Converter<String> { @Override public boolean fillTypedValue(String data, TypedValue typedValue) { return ResourceHelper.parseFloatAttribute(null, data, typedValue, false); } } private static class FromFile extends Converter<FsFile> { @Override public boolean fillTypedValue(FsFile data, TypedValue typedValue) { typedValue.type = TypedValue.TYPE_STRING; typedValue.data = 0; typedValue.string = data.getPath(); typedValue.assetCookie = getNextStringCookie(); return true; } } private static class FromFloat extends Converter<String> { @Override public boolean fillTypedValue(String data, TypedValue typedValue) { return ResourceHelper.parseFloatAttribute(null, data, typedValue, false); } } private static class FromBoolean extends Converter<String> { @Override public boolean fillTypedValue(String data, TypedValue typedValue) { typedValue.type = TypedValue.TYPE_INT_BOOLEAN; typedValue.assetCookie = 0; typedValue.string = null; if ("true".equalsIgnoreCase(data)) { typedValue.data = 1; } else if ("false".equalsIgnoreCase(data)) { typedValue.data = 0; } else { try { int intValue = Integer.parseInt(data); typedValue.data = intValue == 0 ? 0 : 1; } catch (NumberFormatException e) { return false; } } return true; } } private static class FromDimen extends Converter<String> { @Override public boolean fillTypedValue(String data, TypedValue typedValue) { return ResourceHelper.parseFloatAttribute(null, data, typedValue, false); } } private static int convertInt(String rawValue) { try { // Decode into long, because there are some large hex values in the android resource files // (e.g. config_notificationsBatteryLowARGB = 0xFFFF0000 in sdk 14). // Integer.decode() does not support large, i.e. negative values in hex numbers. // try parsing decimal number return (int) Long.parseLong(rawValue); } catch (NumberFormatException nfe) { // try parsing hex number return Long.decode(rawValue).intValue(); } } private static class EnumConverter extends EnumOrFlagConverter { public EnumConverter(AttrData attrData) { super(attrData); } @Override public boolean fillTypedValue(String data, TypedValue typedValue) { try { typedValue.type = TypedValue.TYPE_INT_HEX; typedValue.data = findValueFor(data); typedValue.assetCookie = 0; typedValue.string = null; return true; } catch (Exception e) { return false; } } } private static class FlagConverter extends EnumOrFlagConverter { public FlagConverter(AttrData attrData) { super(attrData); } @Override public boolean fillTypedValue(String data, TypedValue typedValue) { try { int flags = 0; for (String key : data.split("\\|")) { flags |= findValueFor(key); } typedValue.type = TypedValue.TYPE_INT_HEX; typedValue.data = flags; typedValue.assetCookie = 0; typedValue.string = null; return true; } catch (Exception e) { return false; } } } private static class EnumOrFlagConverter extends Converter<String> { private final AttrData attrData; public EnumOrFlagConverter(AttrData attrData) { this.attrData = attrData; } protected int findValueFor(String key) { String valueFor = attrData.getValueFor(key); if (valueFor == null) { // Maybe they have passed in the value directly, rather than the name. if (attrData.isValue(key)) { valueFor = key; } else { throw new RuntimeException("no value found for " + key); } } return Util.parseInt(valueFor); } } }