package org.robolectric.res; import org.robolectric.util.Logger; import java.lang.reflect.Field; import java.lang.reflect.Modifier; public class ResourceTableFactory { /** * Builds an Android framework resource table in the "android" package space. */ public PackageResourceTable newFrameworkResourceTable(ResourcePath resourcePath) { PackageResourceTable resourceTable = new PackageResourceTable("android"); if (resourcePath.getRClass() != null) { addRClassValues(resourceTable, resourcePath.getRClass()); addMissingStyleableAttributes(resourceTable, resourcePath.getRClass()); } if (resourcePath.getInternalRClass() != null) { addRClassValues(resourceTable, resourcePath.getInternalRClass()); addMissingStyleableAttributes(resourceTable, resourcePath.getInternalRClass()); } parseResourceFiles(resourcePath, resourceTable); return resourceTable; } /** * Creates an application resource table which can be constructed with multiple resources paths representing * overlayed resource libraries. */ public PackageResourceTable newResourceTable(String packageName, ResourcePath... resourcePaths) { PackageResourceTable resourceTable = new PackageResourceTable(packageName); for (ResourcePath resourcePath : resourcePaths) { if (resourcePath.getRClass() != null) { addRClassValues(resourceTable, resourcePath.getRClass()); } } for (ResourcePath resourcePath : resourcePaths) { parseResourceFiles(resourcePath, resourceTable); } return resourceTable; } private void addRClassValues(PackageResourceTable resourceTable, Class<?> rClass) { for (Class innerClass : rClass.getClasses()) { String resourceType = innerClass.getSimpleName(); if (!resourceType.equals("styleable")) { for (Field field : innerClass.getDeclaredFields()) { if (field.getType().equals(Integer.TYPE) && Modifier.isStatic(field.getModifiers())) { int id; try { id = field.getInt(null); } catch (IllegalAccessException e) { throw new RuntimeException(e); } String resourceName = field.getName(); resourceTable.addResource(id, resourceType, resourceName); } } } } } /** * Check the stylable elements. Not for aapt generated R files but for framework R files it is possible to * have attributes in the styleable array for which there is no corresponding R.attr field. */ private void addMissingStyleableAttributes(PackageResourceTable resourceTable, Class<?> rClass) { for (Class innerClass : rClass.getClasses()) { if (innerClass.getSimpleName().equals("styleable")) { String styleableName = null; // Current styleable name int[] styleableArray = null; // Current styleable value array or references for (Field field : innerClass.getDeclaredFields()) { if (field.getType().equals(int[].class) && Modifier.isStatic(field.getModifiers())) { styleableName = field.getName(); try { styleableArray = (int[]) (field.get(null)); } catch (IllegalAccessException e) { throw new RuntimeException(e); } } else if (field.getType().equals(Integer.TYPE) && Modifier.isStatic(field.getModifiers())) { String attributeName = field.getName().substring(styleableName.length() + 1); try { int styleableIndex = field.getInt(null); int attributeResId = styleableArray[styleableIndex]; resourceTable.addResource(attributeResId, "attr", attributeName); } catch (IllegalAccessException e) { throw new RuntimeException(e); } } } } } } private void parseResourceFiles(ResourcePath resourcePath, PackageResourceTable resourceTable) { if (!resourcePath.hasResources()) { Logger.debug("No resources for %s", resourceTable.getPackageName()); return; } Logger.debug("Loading resources for %s from %s...", resourceTable.getPackageName(), resourcePath.getResourceBase()); try { new StaxDocumentLoader(resourceTable.getPackageName(), resourcePath.getResourceBase(), new NodeHandler() .addHandler("resources", new NodeHandler() .addHandler("bool", new StaxValueLoader(resourceTable, "bool", ResType.BOOLEAN)) .addHandler("item[@type='bool']", new StaxValueLoader(resourceTable, "bool", ResType.BOOLEAN)) .addHandler("color", new StaxValueLoader(resourceTable, "color", ResType.COLOR)) .addHandler("drawable", new StaxValueLoader(resourceTable, "drawable", ResType.DRAWABLE)) .addHandler("item[@type='color']", new StaxValueLoader(resourceTable, "color", ResType.COLOR)) .addHandler("item[@type='drawable']", new StaxValueLoader(resourceTable, "drawable", ResType.DRAWABLE)) .addHandler("dimen", new StaxValueLoader(resourceTable, "dimen", ResType.DIMEN)) .addHandler("item[@type='dimen']", new StaxValueLoader(resourceTable, "dimen", ResType.DIMEN)) .addHandler("integer", new StaxValueLoader(resourceTable, "integer", ResType.INTEGER)) .addHandler("item[@type='integer']", new StaxValueLoader(resourceTable, "integer", ResType.INTEGER)) .addHandler("integer-array", new StaxArrayLoader(resourceTable, "array", ResType.INTEGER_ARRAY, ResType.INTEGER)) .addHandler("fraction", new StaxValueLoader(resourceTable, "fraction", ResType.FRACTION)) .addHandler("item[@type='fraction']", new StaxValueLoader(resourceTable, "fraction", ResType.FRACTION)) .addHandler("item[@type='layout']", new StaxValueLoader(resourceTable, "layout", ResType.LAYOUT)) .addHandler("plurals", new StaxPluralsLoader(resourceTable, "plurals", ResType.CHAR_SEQUENCE)) .addHandler("string", new StaxValueLoader(resourceTable, "string", ResType.CHAR_SEQUENCE)) .addHandler("item[@type='string']", new StaxValueLoader(resourceTable, "string", ResType.CHAR_SEQUENCE)) .addHandler("string-array", new StaxArrayLoader(resourceTable, "array", ResType.CHAR_SEQUENCE_ARRAY, ResType.CHAR_SEQUENCE)) .addHandler("array", new StaxArrayLoader(resourceTable, "array", ResType.TYPED_ARRAY, null)) .addHandler("id", new StaxValueLoader(resourceTable, "id", ResType.CHAR_SEQUENCE)) .addHandler("item[@type='id']", new StaxValueLoader(resourceTable, "id", ResType.CHAR_SEQUENCE)) .addHandler("attr", new StaxAttrLoader(resourceTable, "attr", ResType.ATTR_DATA)) .addHandler("declare-styleable", new NodeHandler() .addHandler("attr", new StaxAttrLoader(resourceTable, "attr", ResType.ATTR_DATA)) ) .addHandler("style", new StaxStyleLoader(resourceTable, "style", ResType.STYLE)) )).load("values"); loadOpaque(resourcePath, resourceTable, "layout", ResType.LAYOUT); loadOpaque(resourcePath, resourceTable, "menu", ResType.LAYOUT); loadOpaque(resourcePath, resourceTable, "drawable", ResType.DRAWABLE); loadOpaque(resourcePath, resourceTable, "anim", ResType.LAYOUT); loadOpaque(resourcePath, resourceTable, "animator", ResType.LAYOUT); loadOpaque(resourcePath, resourceTable, "color", ResType.COLOR_STATE_LIST); loadOpaque(resourcePath, resourceTable, "xml", ResType.LAYOUT); loadOpaque(resourcePath, resourceTable, "transition", ResType.LAYOUT); loadOpaque(resourcePath, resourceTable, "interpolator", ResType.LAYOUT); new DrawableResourceLoader(resourceTable).findDrawableResources(resourcePath); new RawResourceLoader(resourcePath).loadTo(resourceTable); } catch (Exception e) { throw new RuntimeException(e); } } private void loadOpaque(ResourcePath resourcePath, final PackageResourceTable resourceTable, final String type, final ResType resType) { new DocumentLoader(resourceTable.getPackageName(), resourcePath.getResourceBase()) { @Override protected void loadResourceXmlFile(XmlContext xmlContext) { resourceTable.addResource(type, xmlContext.getXmlFile().getBaseName(), new FileTypedResource(xmlContext.getXmlFile(), resType, xmlContext)); } }.load(type); } }