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())))); } }