org.robolectric.res.ResourceRemapper Maven / Gradle / Ivy
package org.robolectric.res;
import com.google.common.collect.BiMap;
import com.google.common.collect.HashBiMap;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.util.HashMap;
import java.util.Map;
/**
* This class rewrites application R class resource values from multiple input R classes to all have unique values
* existing within the same ID space, i.e: no resource collisions. This replicates the behaviour of AAPT when building
* the final APK.
*
* IDs are in the format:-
*
* 0x PPTTEEEE
*
* where:
*
* P is unique for the package
* T is unique for the type
* E is the entry within that type.
*/
class ResourceRemapper {
private BiMap resIds = HashBiMap.create();
private ResourceIdGenerator resourceIdGenerator = new ResourceIdGenerator(0x7F);
/**
* @param primaryRClass - An R class (usually the applications) that can be assumed to have a complete set of IDs. If
* this is provided then use the values from this class for re-writting all values in follow up
* calls to {@link #remapRClass(Class)}. If it is not provided the ResourceRemapper will generate
* its own unique non-conflicting IDs.
*/
ResourceRemapper(Class> primaryRClass) {
if (primaryRClass != null) {
remapRClass(true, primaryRClass);
}
}
void remapRClass(Class> rClass) {
remapRClass(false, rClass);
}
/**
* @param isPrimary - Only one R class can allow final values and that is the final R class for the application
* that has had its resource id values generated to include all libraries in its dependency graph
* and therefore will be the only R file with the complete set of IDs in a unique ID space so we
* can assume to use the values from this class only. All other R files are partial R files for each
* library and on non-Android aware build systems like Maven where library R files are not re-written
* with the final R values we need to rewrite them ourselves.
*/
private void remapRClass(boolean isPrimary, Class> rClass) {
// Collect all the local attribute id -> name mappings. These are used when processing the stylables to look up
// the reassigned values.
Map localAttributeIds = new HashMap<>();
for (Class> aClass : rClass.getClasses()) {
if (aClass.getSimpleName().equals("attr")) {
for (Field field : aClass.getFields()) {
try {
localAttributeIds.put(field.getInt(null), field.getName());
} catch (IllegalAccessException e) {
throw new RuntimeException("Could not read attr value for " + field.getName(), e);
}
}
}
}
for (Class> innerClass : rClass.getClasses()) {
String resourceType = innerClass.getSimpleName();
if (!resourceType.startsWith("styleable")) {
for (Field field : innerClass.getFields()) {
try {
if (!isPrimary && Modifier.isFinal(field.getModifiers())) {
throw new IllegalArgumentException(rClass + " contains final fields, these will be inlined by the compiler and cannot be remapped.");
}
String resourceName = resourceType + "/" + field.getName();
Integer value = resIds.get(resourceName);
if (value != null) {
field.setAccessible(true);
field.setInt(null, value);
resourceIdGenerator.record(field.getInt(null), resourceType, field.getName());
} else if (resIds.containsValue(field.getInt(null))) {
int remappedValue = resourceIdGenerator.generate(resourceType, field.getName());
field.setInt(null, remappedValue);
resIds.put(resourceName, remappedValue);
} else {
if (isPrimary) {
resourceIdGenerator.record(field.getInt(null), resourceType, field.getName());
resIds.put(resourceName, field.getInt(null));
} else {
int remappedValue = resourceIdGenerator.generate(resourceType, field.getName());
field.setInt(null, remappedValue);
resIds.put(resourceName, remappedValue);
}
}
} catch (IllegalAccessException e) {
throw new IllegalStateException(e);
}
}
}
}
// Reassign the ids in the style arrays accordingly.
for (Class> innerClass : rClass.getClasses()) {
String resourceType = innerClass.getSimpleName();
if (resourceType.startsWith("styleable")) {
for (Field field : innerClass.getFields()) {
if (field.getType().equals(int[].class)) {
try {
int[] styleableArray = (int[]) (field.get(null));
for (int k = 0; k < styleableArray.length; k++) {
Integer value = resIds.get("attr/" + localAttributeIds.get(styleableArray[k]));
if (value != null) {
styleableArray[k] = value;
}
}
} catch (IllegalAccessException e) {
throw new IllegalStateException(e);
}
}
}
}
}
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy