Home | History | Annotate | Download | only in shadows
      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 
     16   synchronized static int getNextStringCookie() {
     17     return nextStringCookie++;
     18   }
     19 
     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
     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 
     82   public CharSequence asCharSequence(TypedResource typedResource) {
     83     return typedResource.asString();
     84   }
     85 
     86   public int asInt(TypedResource typedResource) {
     87     throw cantDo("asInt");
     88   }
     89 
     90   public List<TypedResource> getItems(TypedResource typedResource) {
     91     return new ArrayList<>();
     92   }
     93 
     94   public boolean fillTypedValue(T data, TypedValue typedValue) {
     95     return false;
     96   }
     97 
     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
    104     public CharSequence asCharSequence(TypedResource typedResource) {
    105       return typedResource.asString();
    106     }
    107 
    108     @Override
    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
    117     public CharSequence asCharSequence(TypedResource typedResource) {
    118       return typedResource.asString().trim();
    119     }
    120 
    121     @Override
    122     public int asInt(TypedResource typedResource) {
    123       return convertInt(typedResource.asString().trim());
    124     }
    125 
    126     @Override
    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
    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
    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
    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
    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
    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
    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
    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
    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
    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
    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
    243     public boolean fillTypedValue(String data, TypedValue typedValue) {
    244       return ResourceHelper.parseFloatAttribute(null, data, typedValue, false);
    245     }
    246   }
    247 
    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 {
    262     public EnumConverter(AttrData attrData) {
    263       super(attrData);
    264     }
    265 
    266     @Override
    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 {
    285     public FlagConverter(AttrData attrData) {
    286       super(attrData);
    287     }
    288 
    289     @Override
    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 
    318     public EnumOrFlagConverter(AttrData attrData) {
    319       this.attrData = attrData;
    320     }
    321 
    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