aboutsummaryrefslogtreecommitdiffhomepage
path: root/src/tools/android/java/com/google/devtools/build/android/AndroidResourceClassWriter.java
diff options
context:
space:
mode:
authorGravatar corysmith <corysmith@google.com>2017-05-15 16:56:01 +0200
committerGravatar Dmitry Lomov <dslomov@google.com>2017-05-15 19:51:22 +0200
commitda6bbce1ee317f3b4d7ca245058049ab2a032848 (patch)
treec32d28b144454f8ad1ed6c96ab7cebe2f89bb836 /src/tools/android/java/com/google/devtools/build/android/AndroidResourceClassWriter.java
parent296cd4913d40b756e29fbe0aa6055addf228da6d (diff)
Further Refactoring/Yak Shaving
* Extract the FieldInitializer with placeholder ids from the AndroidResourceClassWriter * Extract a resource sink interface from the AndroidResourceClassWriter (a little renaming, the change isn't actually that big.) RELNOTES: None PiperOrigin-RevId: 156053478
Diffstat (limited to 'src/tools/android/java/com/google/devtools/build/android/AndroidResourceClassWriter.java')
-rw-r--r--src/tools/android/java/com/google/devtools/build/android/AndroidResourceClassWriter.java454
1 files changed, 28 insertions, 426 deletions
diff --git a/src/tools/android/java/com/google/devtools/build/android/AndroidResourceClassWriter.java b/src/tools/android/java/com/google/devtools/build/android/AndroidResourceClassWriter.java
index 1f0360c85b..2fcbe8e93c 100644
--- a/src/tools/android/java/com/google/devtools/build/android/AndroidResourceClassWriter.java
+++ b/src/tools/android/java/com/google/devtools/build/android/AndroidResourceClassWriter.java
@@ -19,77 +19,49 @@ import com.android.SdkConstants;
import com.android.resources.ResourceType;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Optional;
-import com.google.common.base.Preconditions;
-import com.google.common.collect.ImmutableList;
-import com.google.common.collect.ImmutableMap;
-import com.google.common.collect.ImmutableSet;
-import com.google.common.collect.Maps;
-import com.google.common.collect.Ordering;
import com.google.devtools.build.android.AndroidFrameworkAttrIdProvider.AttrLookupException;
import com.google.devtools.build.android.resources.FieldInitializer;
-import com.google.devtools.build.android.resources.IntArrayFieldInitializer;
-import com.google.devtools.build.android.resources.IntFieldInitializer;
import com.google.devtools.build.android.resources.RClassGenerator;
import java.io.BufferedWriter;
import java.io.Flushable;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
-import java.util.Collection;
-import java.util.EnumMap;
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
-import java.util.Set;
-import java.util.SortedMap;
-import java.util.TreeMap;
-import java.util.TreeSet;
-import java.util.logging.Logger;
/**
* Generates the R class for an android_library with made up field initializers for the ids. The
* real ids will be assigned when we build the android_binary.
*
- * Collects the R class fields from the merged resource maps, and then writes out the resource class
- * files.
+ * <p>Collects the R class fields from the merged resource maps, and then writes out the resource
+ * class files.
*/
-public class AndroidResourceClassWriter implements Flushable {
+public class AndroidResourceClassWriter implements Flushable, AndroidResourceSymbolSink {
/** Create a new class writer. */
public static AndroidResourceClassWriter createWith(
Path androidJar, Path out, String javaPackage) {
- return new AndroidResourceClassWriter(
- new AndroidFrameworkAttrIdJar(androidJar), out, javaPackage);
+ return of(new AndroidFrameworkAttrIdJar(androidJar), out, javaPackage);
}
@VisibleForTesting
public static AndroidResourceClassWriter of(
AndroidFrameworkAttrIdProvider androidIdProvider, Path outputBasePath, String packageName) {
- return new AndroidResourceClassWriter(androidIdProvider, outputBasePath, packageName);
+ return new AndroidResourceClassWriter(
+ new PlaceholderIdFieldInitializerBuilder(androidIdProvider), outputBasePath, packageName);
}
-
- private static final Logger logger =
- Logger.getLogger(AndroidResourceClassWriter.class.getName());
- private static final int APP_PACKAGE_MASK = 0x7f000000;
- private static final int ATTR_TYPE_ID = 1;
- private final AndroidFrameworkAttrIdProvider androidIdProvider;
+
private final Path outputBasePath;
private final String packageName;
private boolean includeClassFile = true;
private boolean includeJavaFile = true;
- private final Map<ResourceType, Set<String>> innerClasses = new EnumMap<>(ResourceType.class);
- private final Map<String, Map<String, Boolean>> styleableAttrs = new HashMap<>();
- private final Map<ResourceType, SortedMap<String, Optional<Integer>>> publicIds =
- new EnumMap<>(ResourceType.class);
-
- private static final String NORMALIZED_ANDROID_PREFIX = "android_";
+ private final PlaceholderIdFieldInitializerBuilder generator;
private AndroidResourceClassWriter(
- AndroidFrameworkAttrIdProvider androidIdProvider, Path outputBasePath, String packageName) {
- this.androidIdProvider = androidIdProvider;
+ PlaceholderIdFieldInitializerBuilder generator, Path outputBasePath, String packageName) {
+ this.generator = generator;
this.outputBasePath = outputBasePath;
this.packageName = packageName;
}
@@ -102,346 +74,36 @@ public class AndroidResourceClassWriter implements Flushable {
this.includeJavaFile = include;
}
- public void writeSimpleResource(ResourceType type, String name) {
- Set<String> fields = innerClasses.get(type);
- if (fields == null) {
- fields = new HashSet<>();
- innerClasses.put(type, fields);
- }
- fields.add(normalizeName(name));
+ @Override
+ public void acceptSimpleResource(ResourceType type, String name) {
+ generator.addSimpleResource(type, name);
}
- public void writePublicValue(ResourceType type, String name, Optional<Integer> value) {
- SortedMap<String, Optional<Integer>> publicMappings = publicIds.get(type);
- if (publicMappings == null) {
- publicMappings = new TreeMap<>();
- publicIds.put(type, publicMappings);
- }
- Optional<Integer> oldValue = publicMappings.put(name, value);
- // AAPT should issue an error, but do a bit of sanity checking here just in case.
- if (oldValue != null && !oldValue.equals(value)) {
- // Enforce a consistent ordering on the warning message.
- Integer lower = oldValue.orNull();
- Integer higher = value.orNull();
- if (Ordering.natural().compare(oldValue.orNull(), value.orNull()) > 0) {
- lower = higher;
- higher = oldValue.orNull();
- }
- logger.warning(
- String.format(
- "resource %s/%s has conflicting public identifiers (0x%x vs 0x%x)",
- type, name, lower, higher));
- }
+ @Override
+ public void acceptPublicResource(ResourceType type, String name, Optional<Integer> value) {
+ generator.addPublicResource(type, name, value);
}
- public void writeStyleableResource(FullyQualifiedName key,
- Map<FullyQualifiedName, Boolean> attrs) {
- ResourceType type = ResourceType.STYLEABLE;
- // The configuration can play a role in sorting, but that isn't modeled yet.
- String normalizedStyleableName = normalizeName(key.name());
- writeSimpleResource(type, normalizedStyleableName);
- // We should have merged styleables, so there should only be one definition per configuration.
- // However, we don't combine across configurations, so there can be a pre-existing definition.
- Map<String, Boolean> normalizedAttrs = styleableAttrs.get(normalizedStyleableName);
- if (normalizedAttrs == null) {
- // We need to maintain the original order of the attrs.
- normalizedAttrs = new LinkedHashMap<>();
- styleableAttrs.put(normalizedStyleableName, normalizedAttrs);
- }
- for (Map.Entry<FullyQualifiedName, Boolean> attrEntry : attrs.entrySet()) {
- String normalizedAttrName = normalizeAttrName(attrEntry.getKey().name());
- normalizedAttrs.put(normalizedAttrName, attrEntry.getValue());
- }
+ @Override
+ public void acceptStyleableResource(
+ FullyQualifiedName key, Map<FullyQualifiedName, Boolean> attrs) {
+ generator.addStyleableResource(key, attrs);
}
@Override
public void flush() throws IOException {
- Map<ResourceType, List<FieldInitializer>> initializers = new EnumMap<>(ResourceType.class);
try {
- fillInitializers(initializers);
- } catch (AttrLookupException e) {
- throw new IOException(e);
- }
-
- if (includeClassFile) {
- writeAsClass(initializers);
- }
- if (includeJavaFile) {
- writeAsJava(initializers);
- }
- }
-
- /**
- * Determine the TT portion of the resource ID (PPTTEEEE) that aapt would have assigned. This not
- * at all alphabetical. It depends on the order in which the types are processed, and whether or
- * not previous types are present (compact). See the code in aapt Resource.cpp:buildResources().
- * There are several seemingly arbitrary and different processing orders in the function, but the
- * ordering is determined specifically by the portion at: <a
- * href="https://android.googlesource.com/platform/frameworks/base.git/+/marshmallow-release/tools/aapt/Resource.cpp#1254">
- * Resource.cpp:buildResources() </a>
- *
- * <p>where it does:
- *
- * <pre>
- * if (drawables != NULL) { ... }
- * if (mipmaps != NULL) { ... }
- * if (layouts != NULL) { ... }
- * </pre>
- *
- * Numbering starts at 1 instead of 0, and ResourceType.ATTR comes before the rest.
- * ResourceType.STYLEABLE doesn't actually need a resource ID, so that is skipped. We encode the
- * ordering in the following list.
- */
- private static final ImmutableList<ResourceType> AAPT_TYPE_ORDERING =
- ImmutableList.of(
- ResourceType.DRAWABLE,
- ResourceType.MIPMAP,
- ResourceType.LAYOUT,
- ResourceType.ANIM,
- ResourceType.ANIMATOR,
- ResourceType.TRANSITION,
- ResourceType.INTERPOLATOR,
- ResourceType.XML,
- ResourceType.RAW,
- // Begin VALUES portion
- // Technically, aapt just assigns according to declaration order in the source value.xml files
- // so it isn't really deterministic. However, the Gradle merger sorts the values.xml file
- // before invoking aapt, so assume that is also done.
- ResourceType.ARRAY,
- ResourceType.BOOL,
- ResourceType.COLOR,
- ResourceType.DIMEN,
- ResourceType.FRACTION,
- ResourceType.ID,
- ResourceType.INTEGER,
- ResourceType.PLURALS,
- ResourceType.STRING,
- ResourceType.STYLE,
- // End VALUES portion
- // Technically, file-based COLOR resources come next. If we care about complete equivalence
- // we should separate the file-based resources from value-based resources so that we can
- // number them the same way.
- ResourceType.MENU);
-
- private Map<ResourceType, Integer> chooseTypeIds() {
- // Go through public entries. Those may have forced certain type assignments, so take those
- // into account first.
- Map<ResourceType, Integer> allocatedTypeIds = assignTypeIdsForPublic();
- Set<Integer> reservedTypeSlots = ImmutableSet.copyOf(allocatedTypeIds.values());
- // ATTR always takes up slot #1, even if it isn't present.
- allocatedTypeIds.put(ResourceType.ATTR, ATTR_TYPE_ID);
- // The rest are packed after that.
- int nextTypeId = nextFreeId(ATTR_TYPE_ID + 1, reservedTypeSlots);
- for (ResourceType t : AAPT_TYPE_ORDERING) {
- if (innerClasses.containsKey(t) && !allocatedTypeIds.containsKey(t)) {
- allocatedTypeIds.put(t, nextTypeId);
- nextTypeId = nextFreeId(nextTypeId + 1, reservedTypeSlots);
- }
- }
- // Sanity check that everything has been assigned, except STYLEABLE. There shouldn't be
- // anything of type PUBLIC either (since that isn't a real resource).
- // We will need to update the list if there is a new resource type.
- for (ResourceType t : innerClasses.keySet()) {
- Preconditions.checkState(
- t == ResourceType.STYLEABLE || allocatedTypeIds.containsKey(t),
- "Resource type %s is not allocated a type ID",
- t);
- }
- return allocatedTypeIds;
- }
-
- private Map<ResourceType, Integer> assignTypeIdsForPublic() {
- Map<ResourceType, Integer> allocatedTypeIds = new EnumMap<>(ResourceType.class);
- if (publicIds.isEmpty()) {
- return allocatedTypeIds;
- }
- // Keep track of the reverse mapping from Int -> Type for validation.
- Map<Integer, ResourceType> assignedIds = new HashMap<>();
- for (Map.Entry<ResourceType, SortedMap<String, Optional<Integer>>> publicTypeEntry :
- publicIds.entrySet()) {
- ResourceType currentType = publicTypeEntry.getKey();
- Integer reservedTypeSlot = null;
- String previousResource = null;
- for (Map.Entry<String, Optional<Integer>> publicEntry :
- publicTypeEntry.getValue().entrySet()) {
- Optional<Integer> reservedId = publicEntry.getValue();
- if (!reservedId.isPresent()) {
- continue;
- }
- Integer typePortion = extractTypeId(reservedId.get());
- if (reservedTypeSlot == null) {
- reservedTypeSlot = typePortion;
- previousResource = publicEntry.getKey();
- } else {
- if (!reservedTypeSlot.equals(typePortion)) {
- logger.warning(
- String.format(
- "%s has conflicting type codes for its public identifiers (%s=%s vs %s=%s)",
- currentType.getName(),
- previousResource,
- reservedTypeSlot,
- publicEntry.getKey(),
- typePortion));
- }
- }
- }
- if (currentType == ResourceType.ATTR
- && reservedTypeSlot != null
- && !reservedTypeSlot.equals(ATTR_TYPE_ID)) {
- logger.warning(
- String.format(
- "Cannot force ATTR to have type code other than 0x%02x (got 0x%02x from %s)",
- ATTR_TYPE_ID, reservedTypeSlot, previousResource));
- }
- allocatedTypeIds.put(currentType, reservedTypeSlot);
- ResourceType alreadyAssigned = assignedIds.put(reservedTypeSlot, currentType);
- if (alreadyAssigned != null) {
- logger.warning(
- String.format(
- "Multiple type names declared for public type identifier 0x%x (%s vs %s)",
- reservedTypeSlot, alreadyAssigned, currentType));
- }
- }
- return allocatedTypeIds;
- }
-
- private Map<String, Integer> assignAttrIds(int attrTypeId) {
- // Attrs are special, since they can be defined within a declare-styleable. Those are sorted
- // after top-level definitions.
- if (!innerClasses.containsKey(ResourceType.ATTR)) {
- return ImmutableMap.of();
- }
- Map<String, Integer> attrToId = Maps.newHashMapWithExpectedSize(
- innerClasses.get(ResourceType.ATTR).size());
- // After assigning public IDs, we count up monotonically, so we don't need to track additional
- // assignedIds to avoid collisions (use an ImmutableSet to ensure we don't add more).
- Set<Integer> assignedIds = ImmutableSet.of();
- if (publicIds.containsKey(ResourceType.ATTR)) {
- assignedIds = assignPublicIds(attrToId, publicIds.get(ResourceType.ATTR), attrTypeId);
- }
- Set<String> inlineAttrs = new HashSet<>();
- Set<String> styleablesWithInlineAttrs = new TreeSet<>();
- for (Map.Entry<String, Map<String, Boolean>> styleableAttrEntry
- : styleableAttrs.entrySet()) {
- Map<String, Boolean> attrs = styleableAttrEntry.getValue();
- for (Map.Entry<String, Boolean> attrEntry : attrs.entrySet()) {
- if (attrEntry.getValue()) {
- inlineAttrs.add(attrEntry.getKey());
- styleablesWithInlineAttrs.add(styleableAttrEntry.getKey());
- }
- }
- }
- int nextId = nextFreeId(getInitialIdForTypeId(attrTypeId), assignedIds);
- // Technically, aapt assigns based on declaration order, but the merge should have sorted
- // the non-inline attributes, so assigning by sorted order is the same.
- ImmutableList<String> sortedAttrs = Ordering.natural()
- .immutableSortedCopy(innerClasses.get(ResourceType.ATTR));
- for (String attr : sortedAttrs) {
- if (!inlineAttrs.contains(attr) && !attrToId.containsKey(attr)) {
- attrToId.put(attr, nextId);
- nextId = nextFreeId(nextId + 1, assignedIds);
- }
- }
- for (String styleable : styleablesWithInlineAttrs) {
- Map<String, Boolean> attrs = styleableAttrs.get(styleable);
- for (Map.Entry<String, Boolean> attrEntry : attrs.entrySet()) {
- if (attrEntry.getValue() && !attrToId.containsKey(attrEntry.getKey())) {
- attrToId.put(attrEntry.getKey(), nextId);
- nextId = nextFreeId(nextId + 1, assignedIds);
- }
- }
- }
- return attrToId;
- }
-
- private void fillInitializers(Map<ResourceType, List<FieldInitializer>> initializers)
- throws AttrLookupException {
- Map<ResourceType, Integer> typeIdMap = chooseTypeIds();
- Map<String, Integer> attrAssignments = assignAttrIds(typeIdMap.get(ResourceType.ATTR));
- for (Map.Entry<ResourceType, Set<String>> fieldEntries : innerClasses.entrySet()) {
- ResourceType type = fieldEntries.getKey();
- ImmutableList<String> sortedFields = Ordering
- .natural()
- .immutableSortedCopy(fieldEntries.getValue());
- List<FieldInitializer> fields;
- if (type == ResourceType.STYLEABLE) {
- fields = getStyleableInitializers(attrAssignments, sortedFields);
- } else if (type == ResourceType.ATTR) {
- fields = getAttrInitializers(attrAssignments, sortedFields);
- } else {
- int typeId = typeIdMap.get(type);
- fields = getResourceInitializers(type, typeId, sortedFields);
+ Map<ResourceType, List<FieldInitializer>> initializers = generator.build();
+ if (includeClassFile) {
+ writeAsClass(initializers);
}
- // The maximum number of Java fields is 2^16.
- // See the JVM reference "4.11. Limitations of the Java Virtual Machine."
- Preconditions.checkArgument(fields.size() < (1 << 16));
- initializers.put(type, fields);
- }
- }
-
- private List<FieldInitializer> getStyleableInitializers(
- Map<String, Integer> attrAssignments,
- Collection<String> styleableFields)
- throws AttrLookupException {
- ImmutableList.Builder<FieldInitializer> initList = ImmutableList.builder();
- for (String field : styleableFields) {
- Set<String> attrs = styleableAttrs.get(field).keySet();
- ImmutableMap.Builder<String, Integer> arrayInitValues = ImmutableMap.builder();
- for (String attr : attrs) {
- Integer attrId = attrAssignments.get(attr);
- if (attrId == null) {
- // It should be a framework resource, otherwise we don't know about the resource.
- if (!attr.startsWith(NORMALIZED_ANDROID_PREFIX)) {
- throw new AttrLookupException("App attribute not found: " + attr);
- }
- String attrWithoutPrefix = attr.substring(NORMALIZED_ANDROID_PREFIX.length());
- attrId = androidIdProvider.getAttrId(attrWithoutPrefix);
- }
- arrayInitValues.put(attr, attrId);
- }
- // The styleable array should be sorted by ID value.
- // Make sure that if we have android: framework attributes, their IDs are listed first.
- ImmutableMap<String, Integer> arrayInitMap = arrayInitValues
- .orderEntriesByValue(Ordering.<Integer>natural())
- .build();
- initList.add(new IntArrayFieldInitializer(field, arrayInitMap.values()));
- int index = 0;
- for (String attr : arrayInitMap.keySet()) {
- initList.add(new IntFieldInitializer(field + "_" + attr, index));
- ++index;
+ if (includeJavaFile) {
+ writeAsJava(initializers);
}
+ } catch (AttrLookupException e) {
+ throw new IOException(e);
}
- return initList.build();
- }
-
- private List<FieldInitializer> getAttrInitializers(
- Map<String, Integer> attrAssignments, Collection<String> sortedFields) {
- ImmutableList.Builder<FieldInitializer> initList = ImmutableList.builder();
- for (String field : sortedFields) {
- int attrId = attrAssignments.get(field);
- initList.add(new IntFieldInitializer(field, attrId));
- }
- return initList.build();
- }
- private List<FieldInitializer> getResourceInitializers(
- ResourceType type, int typeId, Collection<String> sortedFields) {
- ImmutableList.Builder<FieldInitializer> initList = ImmutableList.builder();
- Map<String, Integer> publicNameToId = new HashMap<>();
- Set<Integer> assignedIds = ImmutableSet.of();
- if (publicIds.containsKey(type)) {
- assignedIds = assignPublicIds(publicNameToId, publicIds.get(type), typeId);
- }
- int resourceIds = nextFreeId(getInitialIdForTypeId(typeId), assignedIds);
- for (String field : sortedFields) {
- Integer fieldValue = publicNameToId.get(field);
- if (fieldValue == null) {
- fieldValue = resourceIds;
- resourceIds = nextFreeId(resourceIds + 1, assignedIds);
- }
- initList.add(new IntFieldInitializer(field, fieldValue));
- }
- return initList.build();
}
private void writeAsJava(Map<ResourceType, List<FieldInitializer>> initializers)
@@ -459,8 +121,7 @@ public class AndroidResourceClassWriter implements Flushable {
writer.write(" */\n");
writer.write(String.format("package %s;\n", packageName));
writer.write("public final class R {\n");
- for (Map.Entry<ResourceType, Set<String>> fieldEntries : innerClasses.entrySet()) {
- ResourceType type = fieldEntries.getKey();
+ for (ResourceType type : initializers.keySet()) {
writer.write(String.format(" public static final class %s {\n", type.getName()));
for (FieldInitializer field : initializers.get(type)) {
field.writeInitSource(writer);
@@ -477,63 +138,4 @@ public class AndroidResourceClassWriter implements Flushable {
new RClassGenerator(outputBasePath, initializers, false /* finalFields */);
rClassGenerator.write(packageName);
}
-
- private static String normalizeName(String resourceName) {
- return resourceName.replace('.', '_');
- }
-
- private static String normalizeAttrName(String attrName) {
- // In addition to ".", attributes can have ":", e.g., for "android:textColor".
- return normalizeName(attrName).replace(':', '_');
- }
-
- /**
- * Assign any public ids to the given idBuilder.
- *
- * @param nameToId where to store the final name -> id mappings
- * @param publicIds known public resources (can contain null values, if ID isn't reserved)
- * @param typeId the type slot for the current resource type.
- * @return the final set of assigned resource ids (includes those without apriori assignments).
- */
- private static Set<Integer> assignPublicIds(
- Map<String, Integer> nameToId,
- SortedMap<String, Optional<Integer>> publicIds,
- int typeId) {
- HashMap<Integer, String> assignedIds = new HashMap<>();
- int prevId = getInitialIdForTypeId(typeId);
- for (Map.Entry<String, Optional<Integer>> entry : publicIds.entrySet()) {
- Optional<Integer> id = entry.getValue();
- if (id.isPresent()) {
- prevId = id.get();
- } else {
- prevId = nextFreeId(prevId + 1, assignedIds.keySet());
- }
- String previousMapping = assignedIds.put(prevId, entry.getKey());
- if (previousMapping != null) {
- logger.warning(
- String.format(
- "Multiple entry names declared for public entry identifier 0x%x (%s and %s)",
- prevId, previousMapping, entry.getKey()));
- }
- nameToId.put(entry.getKey(), prevId);
- }
- return assignedIds.keySet();
- }
-
- private static int extractTypeId(int fullID) {
- return (fullID & 0x00FF0000) >> 16;
- }
-
- private static int getInitialIdForTypeId(int typeId) {
- return APP_PACKAGE_MASK | (typeId << 16);
- }
-
- private static int nextFreeId(int nextSlot, Set<Integer> reservedSlots) {
- // Linear search for the next free slot. This assumes that reserved <public> ids are rare.
- // Otherwise we should use a NavigableSet or some other smarter data-structure.
- while (reservedSlots.contains(nextSlot)) {
- ++nextSlot;
- }
- return nextSlot;
- }
}