Home | History | Annotate | Download | only in res
      1 package org.robolectric.res;
      2 
      3 import java.lang.reflect.Field;
      4 import java.lang.reflect.Modifier;
      5 import org.robolectric.util.Logger;
      6 import org.robolectric.util.PerfStatsCollector;
      7 
      8 public class ResourceTableFactory {
      9   /** Builds an Android framework resource table in the "android" package space. */
     10   public PackageResourceTable newFrameworkResourceTable(ResourcePath resourcePath) {
     11     return PerfStatsCollector.getInstance()
     12         .measure(
     13             "load legacy framework resources",
     14             () -> {
     15               PackageResourceTable resourceTable = new PackageResourceTable("android");
     16 
     17               if (resourcePath.getRClass() != null) {
     18                 addRClassValues(resourceTable, resourcePath.getRClass());
     19                 addMissingStyleableAttributes(resourceTable, resourcePath.getRClass());
     20               }
     21               if (resourcePath.getInternalRClass() != null) {
     22                 addRClassValues(resourceTable, resourcePath.getInternalRClass());
     23                 addMissingStyleableAttributes(resourceTable, resourcePath.getInternalRClass());
     24               }
     25 
     26               parseResourceFiles(resourcePath, resourceTable);
     27 
     28               return resourceTable;
     29             });
     30   }
     31 
     32   /**
     33    * Creates an application resource table which can be constructed with multiple resources paths
     34    * representing overlayed resource libraries.
     35    */
     36   public PackageResourceTable newResourceTable(String packageName, ResourcePath... resourcePaths) {
     37     return PerfStatsCollector.getInstance()
     38         .measure(
     39             "load legacy app resources",
     40             () -> {
     41               PackageResourceTable resourceTable = new PackageResourceTable(packageName);
     42 
     43               for (ResourcePath resourcePath : resourcePaths) {
     44                 if (resourcePath.getRClass() != null) {
     45                   addRClassValues(resourceTable, resourcePath.getRClass());
     46                 }
     47               }
     48 
     49               for (ResourcePath resourcePath : resourcePaths) {
     50                 parseResourceFiles(resourcePath, resourceTable);
     51               }
     52 
     53               return resourceTable;
     54             });
     55   }
     56 
     57   private void addRClassValues(PackageResourceTable resourceTable, Class<?> rClass) {
     58     for (Class innerClass : rClass.getClasses()) {
     59       String resourceType = innerClass.getSimpleName();
     60       if (!resourceType.equals("styleable")) {
     61         for (Field field : innerClass.getDeclaredFields()) {
     62           if (field.getType().equals(Integer.TYPE) && Modifier.isStatic(field.getModifiers())) {
     63             int id;
     64             try {
     65               id = field.getInt(null);
     66             } catch (IllegalAccessException e) {
     67               throw new RuntimeException(e);
     68             }
     69 
     70             String resourceName = field.getName();
     71             resourceTable.addResource(id, resourceType, resourceName);
     72           }
     73         }
     74       }
     75     }
     76   }
     77 
     78   /**
     79    * Check the stylable elements. Not for aapt generated R files but for framework R files it is possible to
     80    * have attributes in the styleable array for which there is no corresponding R.attr field.
     81    */
     82   private void addMissingStyleableAttributes(PackageResourceTable resourceTable, Class<?> rClass) {
     83     for (Class innerClass : rClass.getClasses()) {
     84       if (innerClass.getSimpleName().equals("styleable")) {
     85         String styleableName = null; // Current styleable name
     86         int[] styleableArray = null; // Current styleable value array or references
     87         for (Field field : innerClass.getDeclaredFields()) {
     88           if (field.getType().equals(int[].class) && Modifier.isStatic(field.getModifiers())) {
     89             styleableName = field.getName();
     90             try {
     91               styleableArray = (int[]) (field.get(null));
     92             } catch (IllegalAccessException e) {
     93               throw new RuntimeException(e);
     94             }
     95           } else if (field.getType().equals(Integer.TYPE) && Modifier.isStatic(field.getModifiers())) {
     96             String attributeName = field.getName().substring(styleableName.length() + 1);
     97             try {
     98               int styleableIndex = field.getInt(null);
     99               int attributeResId = styleableArray[styleableIndex];
    100               resourceTable.addResource(attributeResId, "attr", attributeName);
    101             } catch (IllegalAccessException e) {
    102               throw new RuntimeException(e);
    103             }
    104           }
    105         }
    106       }
    107     }
    108   }
    109 
    110   private void parseResourceFiles(ResourcePath resourcePath, PackageResourceTable resourceTable) {
    111     if (!resourcePath.hasResources()) {
    112       Logger.debug("No resources for %s", resourceTable.getPackageName());
    113       return;
    114     }
    115 
    116     Logger.debug("Loading resources for %s from %s...", resourceTable.getPackageName(), resourcePath.getResourceBase());
    117 
    118     try {
    119       new StaxDocumentLoader(resourceTable.getPackageName(), resourcePath.getResourceBase(),
    120           new NodeHandler()
    121               .addHandler("resources", new NodeHandler()
    122                   .addHandler("bool", new StaxValueLoader(resourceTable, "bool", ResType.BOOLEAN))
    123                   .addHandler("item[@type='bool']", new StaxValueLoader(resourceTable, "bool", ResType.BOOLEAN))
    124                   .addHandler("color", new StaxValueLoader(resourceTable, "color", ResType.COLOR))
    125                   .addHandler("item[@type='color']", new StaxValueLoader(resourceTable, "color", ResType.COLOR))
    126                   .addHandler("drawable", new StaxValueLoader(resourceTable, "drawable", ResType.DRAWABLE))
    127                   .addHandler("item[@type='drawable']", new StaxValueLoader(resourceTable, "drawable", ResType.DRAWABLE))
    128                   .addHandler("item[@type='mipmap']", new StaxValueLoader(resourceTable, "mipmap", ResType.DRAWABLE))
    129                   .addHandler("dimen", new StaxValueLoader(resourceTable, "dimen", ResType.DIMEN))
    130                   .addHandler("item[@type='dimen']", new StaxValueLoader(resourceTable, "dimen", ResType.DIMEN))
    131                   .addHandler("integer", new StaxValueLoader(resourceTable, "integer", ResType.INTEGER))
    132                   .addHandler("item[@type='integer']", new StaxValueLoader(resourceTable, "integer", ResType.INTEGER))
    133                   .addHandler("integer-array", new StaxArrayLoader(resourceTable, "array", ResType.INTEGER_ARRAY, ResType.INTEGER))
    134                   .addHandler("fraction", new StaxValueLoader(resourceTable, "fraction", ResType.FRACTION))
    135                   .addHandler("item[@type='fraction']", new StaxValueLoader(resourceTable, "fraction", ResType.FRACTION))
    136                   .addHandler("item[@type='layout']", new StaxValueLoader(resourceTable, "layout", ResType.LAYOUT))
    137                   .addHandler("plurals", new StaxPluralsLoader(resourceTable, "plurals", ResType.CHAR_SEQUENCE))
    138                   .addHandler("string", new StaxValueLoader(resourceTable, "string", ResType.CHAR_SEQUENCE))
    139                   .addHandler("item[@type='string']", new StaxValueLoader(resourceTable, "string", ResType.CHAR_SEQUENCE))
    140                   .addHandler("string-array", new StaxArrayLoader(resourceTable, "array", ResType.CHAR_SEQUENCE_ARRAY, ResType.CHAR_SEQUENCE))
    141                   .addHandler("array", new StaxArrayLoader(resourceTable, "array", ResType.TYPED_ARRAY, null))
    142                   .addHandler("id", new StaxValueLoader(resourceTable, "id", ResType.CHAR_SEQUENCE))
    143                   .addHandler("item[@type='id']", new StaxValueLoader(resourceTable, "id", ResType.CHAR_SEQUENCE))
    144                   .addHandler("attr", new StaxAttrLoader(resourceTable, "attr", ResType.ATTR_DATA))
    145                   .addHandler("declare-styleable", new NodeHandler()
    146                       .addHandler("attr", new StaxAttrLoader(resourceTable, "attr", ResType.ATTR_DATA))
    147                   )
    148                   .addHandler("style", new StaxStyleLoader(resourceTable, "style", ResType.STYLE))
    149               )).load("values");
    150 
    151       loadOpaque(resourcePath, resourceTable, "layout", ResType.LAYOUT);
    152       loadOpaque(resourcePath, resourceTable, "menu", ResType.LAYOUT);
    153       loadOpaque(resourcePath, resourceTable, "drawable", ResType.DRAWABLE);
    154       loadOpaque(resourcePath, resourceTable, "mipmap", ResType.DRAWABLE);
    155       loadOpaque(resourcePath, resourceTable, "anim", ResType.LAYOUT);
    156       loadOpaque(resourcePath, resourceTable, "animator", ResType.LAYOUT);
    157       loadOpaque(resourcePath, resourceTable, "color", ResType.COLOR_STATE_LIST);
    158       loadOpaque(resourcePath, resourceTable, "xml", ResType.LAYOUT);
    159       loadOpaque(resourcePath, resourceTable, "transition", ResType.LAYOUT);
    160       loadOpaque(resourcePath, resourceTable, "interpolator", ResType.LAYOUT);
    161 
    162       new DrawableResourceLoader(resourceTable).findDrawableResources(resourcePath);
    163       new RawResourceLoader(resourcePath).loadTo(resourceTable);
    164     } catch (Exception e) {
    165       throw new RuntimeException(e);
    166     }
    167   }
    168 
    169   private void loadOpaque(ResourcePath resourcePath, final PackageResourceTable resourceTable, final String type, final ResType resType) {
    170     new DocumentLoader(resourceTable.getPackageName(), resourcePath.getResourceBase()) {
    171       @Override
    172       protected void loadResourceXmlFile(XmlContext xmlContext) {
    173         resourceTable.addResource(type, xmlContext.getXmlFile().getBaseName(),
    174             new FileTypedResource(xmlContext.getXmlFile(), resType, xmlContext));
    175       }
    176     }.load(type);
    177   }
    178 }
    179