Move the field name back into FieldInitializer

There isn't a good reason to decouple a field name from its value, unless if
one were to:
* swap out a value for an existing name
* write the same value multiple times with different names
The format was done implicitly as a workaround (b/140643407), and the latter,
if done as an optimization, belongs in aapt2.

On the other hand, we need to attach additional metadata for an R field to
track its provenance, and having name/value be separate makes it unclear where
to put the additional metadata.

This undoes unknown commit.

PiperOrigin-RevId: 267884392
diff --git a/src/tools/android/java/com/google/devtools/build/android/PlaceholderIdFieldInitializerBuilder.java b/src/tools/android/java/com/google/devtools/build/android/PlaceholderIdFieldInitializerBuilder.java
index 5634861..3d8ccc4 100644
--- a/src/tools/android/java/com/google/devtools/build/android/PlaceholderIdFieldInitializerBuilder.java
+++ b/src/tools/android/java/com/google/devtools/build/android/PlaceholderIdFieldInitializerBuilder.java
@@ -353,7 +353,7 @@
   }
 
   public FieldInitializers build() throws AttrLookupException {
-    Map<ResourceType, Map<String, FieldInitializer>> initializers =
+    Map<ResourceType, Collection<FieldInitializer>> initializers =
         new EnumMap<>(ResourceType.class);
     Map<ResourceType, Integer> typeIdMap = chooseTypeIds();
     Map<String, Integer> attrAssignments = assignAttrIds(typeIdMap.get(ResourceType.ATTR));
@@ -361,7 +361,7 @@
       ResourceType type = fieldEntries.getKey();
       ImmutableList<String> sortedFields =
           Ordering.natural().immutableSortedCopy(fieldEntries.getValue());
-      Map<String, FieldInitializer> fields;
+      ImmutableList<FieldInitializer> fields;
       if (type == ResourceType.STYLEABLE) {
         fields = getStyleableInitializers(attrAssignments, sortedFields);
       } else if (type == ResourceType.ATTR) {
@@ -405,19 +405,19 @@
     return allocatedTypeIds;
   }
 
-  private static Map<String, FieldInitializer> getAttrInitializers(
+  private static ImmutableList<FieldInitializer> getAttrInitializers(
       Map<String, Integer> attrAssignments, Collection<String> sortedFields) {
-    ImmutableMap.Builder<String, FieldInitializer> initList = ImmutableMap.builder();
+    ImmutableList.Builder<FieldInitializer> initList = ImmutableList.builder();
     for (String field : sortedFields) {
       int attrId = attrAssignments.get(field);
-      initList.put(field, IntFieldInitializer.of(attrId));
+      initList.add(IntFieldInitializer.of(field, attrId));
     }
     return initList.build();
   }
 
-  private Map<String, FieldInitializer> getResourceInitializers(
+  private ImmutableList<FieldInitializer> getResourceInitializers(
       ResourceType type, int typeId, Collection<String> sortedFields) {
-    ImmutableMap.Builder<String, FieldInitializer> initList = ImmutableMap.builder();
+    ImmutableList.Builder<FieldInitializer> initList = ImmutableList.builder();
     Map<String, Integer> publicNameToId = new LinkedHashMap<>();
     Set<Integer> assignedIds = ImmutableSet.of();
     if (publicIds.containsKey(type)) {
@@ -430,15 +430,15 @@
         fieldValue = resourceIds;
         resourceIds = nextFreeId(resourceIds + 1, assignedIds);
       }
-      initList.put(field, IntFieldInitializer.of(fieldValue));
+      initList.add(IntFieldInitializer.of(field, fieldValue));
     }
     return initList.build();
   }
 
-  private Map<String, FieldInitializer> getStyleableInitializers(
+  private ImmutableList<FieldInitializer> getStyleableInitializers(
       Map<String, Integer> attrAssignments, Collection<String> styleableFields)
       throws AttrLookupException {
-    ImmutableMap.Builder<String, FieldInitializer> initList = ImmutableMap.builder();
+    ImmutableList.Builder<FieldInitializer> initList = ImmutableList.builder();
     for (String field : styleableFields) {
       Set<String> attrs = styleableAttrs.get(field).keySet();
       ImmutableMap.Builder<String, Integer> arrayInitValues = ImmutableMap.builder();
@@ -458,10 +458,10 @@
       // 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.put(field, IntArrayFieldInitializer.of(arrayInitMap.values()));
+      initList.add(IntArrayFieldInitializer.of(field, arrayInitMap.values()));
       int index = 0;
       for (String attr : arrayInitMap.keySet()) {
-        initList.put(field + "_" + attr, IntFieldInitializer.of(index));
+        initList.add(IntFieldInitializer.of(field + "_" + attr, index));
         ++index;
       }
     }
diff --git a/src/tools/android/java/com/google/devtools/build/android/resources/FieldInitializer.java b/src/tools/android/java/com/google/devtools/build/android/resources/FieldInitializer.java
index ff7e9b8..f46f58e 100644
--- a/src/tools/android/java/com/google/devtools/build/android/resources/FieldInitializer.java
+++ b/src/tools/android/java/com/google/devtools/build/android/resources/FieldInitializer.java
@@ -28,14 +28,17 @@
    *
    * @return true if the initializer is deferred to clinit code.
    */
-  boolean writeFieldDefinition(String fieldName, ClassWriter cw, int accessLevel, boolean isFinal);
+  boolean writeFieldDefinition(ClassWriter cw, int accessLevel, boolean isFinal);
 
   /**
    * Write the bytecode for the clinit portion of initializer.
+   *
    * @return the number of stack slots needed for the code.
    */
-  int writeCLInit(String fieldName, InstructionAdapter insts, String className);
+  int writeCLInit(InstructionAdapter insts, String className);
 
   /** Write the source code for the initializer to the given writer. */
-  void writeInitSource(String fieldName, Writer writer, boolean finalFields) throws IOException;
+  void writeInitSource(Writer writer, boolean finalFields) throws IOException;
+
+  String getFieldName();
 }
diff --git a/src/tools/android/java/com/google/devtools/build/android/resources/FieldInitializers.java b/src/tools/android/java/com/google/devtools/build/android/resources/FieldInitializers.java
index 9e6ec24..3901521 100644
--- a/src/tools/android/java/com/google/devtools/build/android/resources/FieldInitializers.java
+++ b/src/tools/android/java/com/google/devtools/build/android/resources/FieldInitializers.java
@@ -15,78 +15,98 @@
 
 import com.android.resources.ResourceType;
 import com.google.common.base.MoreObjects;
-import com.google.common.collect.ImmutableMap;
-import com.google.common.collect.ImmutableSortedMap;
-import com.google.common.collect.Sets;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableListMultimap;
+import com.google.common.collect.Multimap;
+import com.google.common.collect.MultimapBuilder;
+import com.google.common.collect.SortedSetMultimap;
 import java.util.Collection;
 import java.util.EnumMap;
 import java.util.Iterator;
 import java.util.Map;
-import java.util.TreeMap;
+import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.function.Function;
+import java.util.function.Predicate;
 
 /**
  * Represents a collection of resource symbols and values suitable for writing java sources and
  * classes.
  */
 public class FieldInitializers
-    implements Iterable<Map.Entry<ResourceType, Map<String, FieldInitializer>>> {
+    implements Iterable<Map.Entry<ResourceType, Collection<FieldInitializer>>> {
 
-  private final Map<ResourceType, Map<String, FieldInitializer>> initializers;
+  private final Map<ResourceType, Collection<FieldInitializer>> initializers;
 
-  private FieldInitializers(Map<ResourceType, Map<String, FieldInitializer>> initializers) {
+  private FieldInitializers(Map<ResourceType, Collection<FieldInitializer>> initializers) {
     this.initializers = initializers;
   }
 
   /** Creates a {@link FieldInitializers} copying the contents from a {@link Map}. */
   public static FieldInitializers copyOf(
-      Map<ResourceType, Map<String, FieldInitializer>> initializers) {
-    return new FieldInitializers(ImmutableMap.copyOf(initializers));
+      Map<ResourceType, Collection<FieldInitializer>> initializers) {
+    Map<ResourceType, Collection<FieldInitializer>> deeplyImmutableInitializers =
+        initializers.entrySet().stream()
+            .collect(
+                ImmutableListMultimap.flatteningToImmutableListMultimap(
+                    Map.Entry::getKey, entry -> entry.getValue().stream()))
+            .asMap();
+
+    return new FieldInitializers(deeplyImmutableInitializers);
   }
 
   public static FieldInitializers mergedFrom(Collection<FieldInitializers> toMerge) {
-    final Map<ResourceType, Map<String, FieldInitializer>> merged =
+    final Map<ResourceType, Collection<FieldInitializer>> merged =
         new EnumMap<>(ResourceType.class);
-    for (FieldInitializers mergee : toMerge) {
-      for (Map.Entry<ResourceType, Map<String, FieldInitializer>> entry : mergee) {
-        final Map<String, FieldInitializer> fieldMap =
-            merged.containsKey(entry.getKey())
-                ? merged.get(entry.getKey())
-                : new TreeMap<String, FieldInitializer>();
-        merged.put(entry.getKey(), fieldMap);
-        for (Map.Entry<String, FieldInitializer> field : entry.getValue().entrySet()) {
-          fieldMap.put(field.getKey(), field.getValue());
-        }
+
+    for (ResourceType resourceType : ResourceType.values()) {
+      ImmutableList<FieldInitializer> fieldInitializers =
+          toMerge.stream()
+              .flatMap(
+                  fis -> fis.initializers.getOrDefault(resourceType, ImmutableList.of()).stream())
+              .filter(distinctByKey(FieldInitializer::getFieldName))
+              .collect(ImmutableList.toImmutableList());
+
+      if (!fieldInitializers.isEmpty()) {
+        merged.put(resourceType, fieldInitializers);
       }
     }
+
     return copyOf(merged);
   }
 
-  public Iterable<Map.Entry<ResourceType, Map<String, FieldInitializer>>> filter(
-      FieldInitializers fieldsToWrite) {
-    Map<ResourceType, Map<String, FieldInitializer>> initializersToWrite =
-        new EnumMap<>(ResourceType.class);
+  private static <T> Predicate<T> distinctByKey(Function<? super T, ?> keyExtractor) {
+    Set<Object> seen = ConcurrentHashMap.newKeySet();
+    return t -> seen.add(keyExtractor.apply(t));
+  }
 
-    for (Map.Entry<ResourceType, Map<String, FieldInitializer>> entry :
+  public FieldInitializers filter(FieldInitializers fieldsToWrite) {
+    // Create a map to filter with.
+    final SortedSetMultimap<ResourceType, String> symbolsToWrite =
+        MultimapBuilder.enumKeys(ResourceType.class).treeSetValues().build();
+    for (Map.Entry<ResourceType, Collection<FieldInitializer>> entry :
         fieldsToWrite.initializers.entrySet()) {
-      if (initializers.containsKey(entry.getKey())) {
-        final Map<String, FieldInitializer> valueFields = initializers.get(entry.getKey());
-        final ImmutableMap.Builder<String, FieldInitializer> fields =
-            ImmutableSortedMap.naturalOrder();
-        // Resource type may be missing if resource overriding eliminates resources at the binary
-        // level, which were originally present at the library level.
-        for (String fieldName :
-            Sets.intersection(entry.getValue().keySet(), valueFields.keySet())) {
-          fields.put(fieldName, valueFields.get(fieldName));
-        }
-        initializersToWrite.put(entry.getKey(), fields.build());
+      for (FieldInitializer initializer : entry.getValue()) {
+        symbolsToWrite.put(entry.getKey(), initializer.getFieldName());
       }
     }
 
-    return initializersToWrite.entrySet();
+    final Multimap<ResourceType, FieldInitializer> initializersToWrite =
+        MultimapBuilder.enumKeys(ResourceType.class).arrayListValues().build();
+    for (Map.Entry<ResourceType, Collection<String>> entry : symbolsToWrite.asMap().entrySet()) {
+      // Resource type may be missing if resource overriding eliminates resources at the binary
+      // level, which were originally present at the library level.
+      for (FieldInitializer field : initializers.getOrDefault(entry.getKey(), ImmutableList.of())) {
+        if (entry.getValue().contains(field.getFieldName())) {
+          initializersToWrite.put(entry.getKey(), field);
+        }
+      }
+    }
+    return copyOf(initializersToWrite.asMap());
   }
 
   @Override
-  public Iterator<Map.Entry<ResourceType, Map<String, FieldInitializer>>> iterator() {
+  public Iterator<Map.Entry<ResourceType, Collection<FieldInitializer>>> iterator() {
     return initializers.entrySet().iterator();
   }
 
diff --git a/src/tools/android/java/com/google/devtools/build/android/resources/IntArrayFieldInitializer.java b/src/tools/android/java/com/google/devtools/build/android/resources/IntArrayFieldInitializer.java
index 140439c..385fcfb 100644
--- a/src/tools/android/java/com/google/devtools/build/android/resources/IntArrayFieldInitializer.java
+++ b/src/tools/android/java/com/google/devtools/build/android/resources/IntArrayFieldInitializer.java
@@ -29,18 +29,21 @@
 public final class IntArrayFieldInitializer implements FieldInitializer {
 
   public static final String DESC = "[I";
+
+  private final String fieldName;
   private final ImmutableCollection<Integer> values;
 
-  private IntArrayFieldInitializer(ImmutableCollection<Integer> values) {
+  private IntArrayFieldInitializer(String fieldName, ImmutableCollection<Integer> values) {
+    this.fieldName = fieldName;
     this.values = values;
   }
 
-  public static FieldInitializer of(String value) {
+  public static FieldInitializer of(String fieldName, String value) {
     Preconditions.checkArgument(value.startsWith("{ "), "Expected list starting with { ");
     Preconditions.checkArgument(value.endsWith(" }"), "Expected list ending with } ");
     // Check for an empty list, which is "{ }".
     if (value.length() < 4) {
-      return of(ImmutableList.<Integer>of());
+      return of(fieldName, ImmutableList.<Integer>of());
     }
     ImmutableList.Builder<Integer> intValues = ImmutableList.builder();
     String trimmedValue = value.substring(2, value.length() - 2);
@@ -48,22 +51,21 @@
     for (String valueString : valueStrings) {
       intValues.add(Integer.decode(valueString));
     }
-    return of(intValues.build());
+    return of(fieldName, intValues.build());
   }
 
-  public static IntArrayFieldInitializer of(ImmutableCollection<Integer> values) {
-    return new IntArrayFieldInitializer(values);
+  public static IntArrayFieldInitializer of(String fieldName, ImmutableCollection<Integer> values) {
+    return new IntArrayFieldInitializer(fieldName, values);
   }
 
   @Override
-  public boolean writeFieldDefinition(
-      String fieldName, ClassWriter cw, int accessLevel, boolean isFinal) {
+  public boolean writeFieldDefinition(ClassWriter cw, int accessLevel, boolean isFinal) {
     cw.visitField(accessLevel, fieldName, DESC, null, null).visitEnd();
     return true;
   }
 
   @Override
-  public int writeCLInit(String fieldName, InstructionAdapter insts, String className) {
+  public int writeCLInit(InstructionAdapter insts, String className) {
     insts.iconst(values.size());
     insts.newarray(Type.INT_TYPE);
     int curIndex = 0;
@@ -81,8 +83,7 @@
   }
 
   @Override
-  public void writeInitSource(String fieldName, Writer writer, boolean finalFields)
-      throws IOException {
+  public void writeInitSource(Writer writer, boolean finalFields) throws IOException {
     StringBuilder builder = new StringBuilder();
     boolean first = true;
     for (Integer attrId : values) {
@@ -101,6 +102,11 @@
   }
 
   @Override
+  public String getFieldName() {
+    return fieldName;
+  }
+
+  @Override
   public String toString() {
     return MoreObjects.toStringHelper(getClass()).add("values", values).toString();
   }
diff --git a/src/tools/android/java/com/google/devtools/build/android/resources/IntFieldInitializer.java b/src/tools/android/java/com/google/devtools/build/android/resources/IntFieldInitializer.java
index 888e888..cc27606 100644
--- a/src/tools/android/java/com/google/devtools/build/android/resources/IntFieldInitializer.java
+++ b/src/tools/android/java/com/google/devtools/build/android/resources/IntFieldInitializer.java
@@ -22,30 +22,32 @@
 /** Models an int field initializer. */
 public final class IntFieldInitializer implements FieldInitializer {
 
+  private final String fieldName;
   private final int value;
+
   private static final String DESC = "I";
 
-  private IntFieldInitializer(int value) {
+  private IntFieldInitializer(String fieldName, int value) {
+    this.fieldName = fieldName;
     this.value = value;
   }
 
-  public static FieldInitializer of(String value) {
-    return of(Integer.decode(value));
+  public static FieldInitializer of(String fieldName, String value) {
+    return of(fieldName, Integer.decode(value));
   }
 
-  public static IntFieldInitializer of(int value) {
-    return new IntFieldInitializer(value);
+  public static IntFieldInitializer of(String fieldName, int value) {
+    return new IntFieldInitializer(fieldName, value);
   }
 
   @Override
-  public boolean writeFieldDefinition(
-      String fieldName, ClassWriter cw, int accessLevel, boolean isFinal) {
+  public boolean writeFieldDefinition(ClassWriter cw, int accessLevel, boolean isFinal) {
     cw.visitField(accessLevel, fieldName, DESC, null, isFinal ? value : null).visitEnd();
     return !isFinal;
   }
 
   @Override
-  public int writeCLInit(String fieldName, InstructionAdapter insts, String className) {
+  public int writeCLInit(InstructionAdapter insts, String className) {
     insts.iconst(value);
     insts.putstatic(className, fieldName, DESC);
     // Just needs one stack slot for the iconst.
@@ -53,8 +55,7 @@
   }
 
   @Override
-  public void writeInitSource(String fieldName, Writer writer, boolean finalFields)
-      throws IOException {
+  public void writeInitSource(Writer writer, boolean finalFields) throws IOException {
     writer.write(
         String.format(
             "        public static %sint %s = 0x%x;\n",
@@ -62,6 +63,11 @@
   }
 
   @Override
+  public String getFieldName() {
+    return fieldName;
+  }
+
+  @Override
   public String toString() {
     return MoreObjects.toStringHelper(getClass()).add("value", value).toString();
   }
diff --git a/src/tools/android/java/com/google/devtools/build/android/resources/RClassGenerator.java b/src/tools/android/java/com/google/devtools/build/android/resources/RClassGenerator.java
index 2012a98..35e3bdb 100644
--- a/src/tools/android/java/com/google/devtools/build/android/resources/RClassGenerator.java
+++ b/src/tools/android/java/com/google/devtools/build/android/resources/RClassGenerator.java
@@ -22,7 +22,9 @@
 import java.io.IOException;
 import java.nio.file.Files;
 import java.nio.file.Path;
-import java.util.LinkedHashMap;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
 import java.util.Map;
 import org.objectweb.asm.ClassWriter;
 import org.objectweb.asm.MethodVisitor;
@@ -77,7 +79,7 @@
 
   private void writeClasses(
       String packageName,
-      Iterable<Map.Entry<ResourceType, Map<String, FieldInitializer>>> initializersToWrite)
+      Iterable<Map.Entry<ResourceType, Collection<FieldInitializer>>> initializersToWrite)
       throws IOException {
 
     Iterable<String> folders = PACKAGE_SPLITTER.split(packageName);
@@ -108,7 +110,7 @@
     classWriter.visitSource(SdkConstants.FN_RESOURCE_CLASS, null);
     writeConstructor(classWriter);
     // Build the R.class w/ the inner classes, then later build the individual R$inner.class.
-    for (Map.Entry<ResourceType, Map<String, FieldInitializer>> entry : initializersToWrite) {
+    for (Map.Entry<ResourceType, Collection<FieldInitializer>> entry : initializersToWrite) {
       String innerClassName = rClassName + "$" + entry.getKey().toString();
       classWriter.visitInnerClass(
           innerClassName,
@@ -119,13 +121,13 @@
     classWriter.visitEnd();
     Files.write(rClassFile, classWriter.toByteArray(), CREATE_NEW);
     // Now generate the R$inner.class files.
-    for (Map.Entry<ResourceType, Map<String, FieldInitializer>> entry : initializersToWrite) {
+    for (Map.Entry<ResourceType, Collection<FieldInitializer>> entry : initializersToWrite) {
       writeInnerClass(entry.getValue(), packageDir, rClassName, entry.getKey().toString());
     }
   }
 
   private void writeInnerClass(
-      Map<String, FieldInitializer> initializers,
+      Collection<FieldInitializer> initializers,
       Path packageDir,
       String fullyQualifiedOuterClass,
       String innerClass)
@@ -134,18 +136,16 @@
     String fullyQualifiedInnerClass =
         writeInnerClassHeader(fullyQualifiedOuterClass, innerClass, innerClassWriter);
 
-    Map<String, FieldInitializer> deferredInitializers = new LinkedHashMap<>();
+    List<FieldInitializer> deferredInitializers = new ArrayList<>();
     int fieldAccessLevel = Opcodes.ACC_PUBLIC | Opcodes.ACC_STATIC;
     if (finalFields) {
       fieldAccessLevel |= Opcodes.ACC_FINAL;
     }
-    for (Map.Entry<String, FieldInitializer> entry : initializers.entrySet()) {
-      FieldInitializer init = entry.getValue();
+    for (FieldInitializer init : initializers) {
       JavaIdentifierValidator.validate(
-          entry.getKey(), "in class:", fullyQualifiedInnerClass, "and package:", packageDir);
-      if (init.writeFieldDefinition(
-          entry.getKey(), innerClassWriter, fieldAccessLevel, finalFields)) {
-        deferredInitializers.put(entry.getKey(), init);
+          init.getFieldName(), "in class:", fullyQualifiedInnerClass, "and package:", packageDir);
+      if (init.writeFieldDefinition(innerClassWriter, fieldAccessLevel, finalFields)) {
+        deferredInitializers.add(init);
       }
     }
     if (!deferredInitializers.isEmpty()) {
@@ -192,17 +192,15 @@
   private static void writeStaticClassInit(
       ClassWriter classWriter,
       String className,
-      Map<String, FieldInitializer> deferredInitializers) {
+      Collection<FieldInitializer> deferredInitializers) {
     MethodVisitor visitor =
         classWriter.visitMethod(
             Opcodes.ACC_STATIC, "<clinit>", "()V", null, /* signature */ null /* exceptions */);
     visitor.visitCode();
     int stackSlotsNeeded = 0;
     InstructionAdapter insts = new InstructionAdapter(visitor);
-    for (Map.Entry<String, FieldInitializer> fieldEntry : deferredInitializers.entrySet()) {
-      final FieldInitializer fieldInit = fieldEntry.getValue();
-      stackSlotsNeeded =
-          Math.max(stackSlotsNeeded, fieldInit.writeCLInit(fieldEntry.getKey(), insts, className));
+    for (FieldInitializer fieldInit : deferredInitializers) {
+      stackSlotsNeeded = Math.max(stackSlotsNeeded, fieldInit.writeCLInit(insts, className));
     }
     insts.areturn(Type.VOID_TYPE);
     visitor.visitMaxs(stackSlotsNeeded, 0);
diff --git a/src/tools/android/java/com/google/devtools/build/android/resources/RSourceGenerator.java b/src/tools/android/java/com/google/devtools/build/android/resources/RSourceGenerator.java
index 2f2cff3..c02e7ce 100644
--- a/src/tools/android/java/com/google/devtools/build/android/resources/RSourceGenerator.java
+++ b/src/tools/android/java/com/google/devtools/build/android/resources/RSourceGenerator.java
@@ -22,6 +22,7 @@
 import java.io.IOException;
 import java.nio.file.Files;
 import java.nio.file.Path;
+import java.util.Collection;
 import java.util.Map;
 
 /** Writes out an R.java source. */
@@ -61,7 +62,7 @@
 
   private void writeSource(
       String packageName,
-      Iterable<Map.Entry<ResourceType, Map<String, FieldInitializer>>> initializersToWrite)
+      Iterable<Map.Entry<ResourceType, Collection<FieldInitializer>>> initializersToWrite)
       throws IOException {
     String packageDir = packageName.replace('.', '/');
     Path packagePath = outputBasePath.resolve(packageDir);
@@ -81,11 +82,11 @@
       writer.write(" */\n");
       writer.write(String.format("package %s;\n", packageName));
       writer.write("public final class R {\n");
-      for (Map.Entry<ResourceType, Map<String, FieldInitializer>> entry : initializersToWrite) {
+      for (Map.Entry<ResourceType, Collection<FieldInitializer>> entry : initializersToWrite) {
         writer.write(
             String.format("    public static final class %s {\n", entry.getKey().getName()));
-        for (Map.Entry<String, FieldInitializer> fieldEntry : entry.getValue().entrySet()) {
-          fieldEntry.getValue().writeInitSource(fieldEntry.getKey(), writer, finalFields);
+        for (FieldInitializer fieldInit : entry.getValue()) {
+          fieldInit.writeInitSource(writer, finalFields);
         }
         writer.write("    }\n");
       }
diff --git a/src/tools/android/java/com/google/devtools/build/android/resources/ResourceSymbols.java b/src/tools/android/java/com/google/devtools/build/android/resources/ResourceSymbols.java
index 5926146..3cfddba 100644
--- a/src/tools/android/java/com/google/devtools/build/android/resources/ResourceSymbols.java
+++ b/src/tools/android/java/com/google/devtools/build/android/resources/ResourceSymbols.java
@@ -16,6 +16,7 @@
 import com.android.builder.core.VariantConfiguration;
 import com.android.builder.dependency.SymbolFileProvider;
 import com.android.resources.ResourceType;
+import com.google.common.collect.ImmutableMap;
 import com.google.common.collect.LinkedHashMultimap;
 import com.google.common.collect.Multimap;
 import com.google.common.util.concurrent.ListenableFuture;
@@ -27,7 +28,6 @@
 import java.nio.file.Path;
 import java.util.ArrayList;
 import java.util.Collection;
-import java.util.EnumMap;
 import java.util.LinkedHashMap;
 import java.util.List;
 import java.util.Map;
@@ -55,8 +55,9 @@
     public ResourceSymbols call() throws Exception {
       List<String> lines = Files.readAllLines(rTxtSymbols, StandardCharsets.UTF_8);
 
-      Map<ResourceType, Map<String, FieldInitializer>> initializers =
-          new EnumMap<>(ResourceType.class);
+      // NB: the inner map is working around a bug in R.txt generation!
+      // TODO(b/140643407): read directly without having to dedup by field name
+      final Map<ResourceType, Map<String, FieldInitializer>> initializers = new TreeMap<>();
 
       for (int lineIndex = 1; lineIndex <= lines.size(); lineIndex++) {
         String line = null;
@@ -73,19 +74,16 @@
           String name = line.substring(pos2 + 1, pos3);
           String value = line.substring(pos3 + 1);
 
-          final ResourceType resourceType = ResourceType.getEnum(className);
-          final Map<String, FieldInitializer> fields;
-          if (initializers.containsKey(resourceType)) {
-            fields = initializers.get(resourceType);
-          } else {
-            fields = new TreeMap<>();
-            initializers.put(resourceType, fields);
-          }
+          FieldInitializer initializer;
           if ("int".equals(type)) {
-            fields.put(name, IntFieldInitializer.of(value));
+            initializer = IntFieldInitializer.of(name, value);
           } else {
-            fields.put(name, IntArrayFieldInitializer.of(value));
+            initializer = IntArrayFieldInitializer.of(name, value);
           }
+
+          initializers
+              .computeIfAbsent(ResourceType.getEnum(className), k -> new TreeMap<>())
+              .put(name, initializer);
         } catch (IndexOutOfBoundsException e) {
           String s =
               String.format(
@@ -95,7 +93,13 @@
           throw new IOException(s, e);
         }
       }
-      return ResourceSymbols.from(FieldInitializers.copyOf(initializers));
+
+      return ResourceSymbols.from(
+          FieldInitializers.copyOf(
+              initializers.entrySet().stream()
+                  .collect(
+                      ImmutableMap.toImmutableMap(
+                          Map.Entry::getKey, entry -> entry.getValue().values()))));
     }
   }