Home | History | Annotate | Download | only in shadows
      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