All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.robolectric.res.ResourceRemapper Maven / Gradle / Ivy

There is a newer version: 4.13
Show newest version
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 - 2024 Weber Informatics LLC | Privacy Policy