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. */
newFrameworkResourceTable(ResourcePath resourcePath)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    */
newResourceTable(String packageName, ResourcePath... resourcePaths)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