AutoValue and Immutable Collection Conversion: NestDigest and its Dependent Classes.

#java11 #desugar #refactor

- NestDigest and its Dependent Classes have been settled down to have clear boundary
  between reading and writing.
- Followup of https://github.com/bazelbuild/bazel/commit/5dbaff6b836281bd22c8c1c92f9b95a52173c05f

PiperOrigin-RevId: 295281029
diff --git a/src/test/java/com/google/devtools/build/android/desugar/langmodel/ClassMemberRecordTest.java b/src/test/java/com/google/devtools/build/android/desugar/langmodel/ClassMemberRecordTest.java
index 85b4553..c0dd084 100644
--- a/src/test/java/com/google/devtools/build/android/desugar/langmodel/ClassMemberRecordTest.java
+++ b/src/test/java/com/google/devtools/build/android/desugar/langmodel/ClassMemberRecordTest.java
@@ -18,6 +18,7 @@
 
 import static com.google.common.truth.Truth.assertThat;
 
+import com.google.devtools.build.android.desugar.langmodel.ClassMemberRecord.ClassMemberRecordBuilder;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.junit.runners.JUnit4;
@@ -25,9 +26,9 @@
 
 /** Tests for {@link ClassMemberRecord}. */
 @RunWith(JUnit4.class)
-public class ClassMemberRecordTest {
+public final class ClassMemberRecordTest {
 
-  private final ClassMemberRecord classMemberRecord = ClassMemberRecord.create();
+  private final ClassMemberRecordBuilder classMemberRecord = ClassMemberRecord.builder();
 
   @Test
   public void trackFieldUse() {
@@ -38,7 +39,7 @@
     classMemberRecord.logMemberUse(classMemberKey, Opcodes.H_GETFIELD);
     classMemberRecord.logMemberUse(classMemberKey, Opcodes.H_PUTFIELD);
 
-    assertThat(classMemberRecord.findAllMemberUseKind(classMemberKey))
+    assertThat(classMemberRecord.build().findAllMemberUseKind(classMemberKey))
         .containsExactly(
             MemberUseKind.GETFIELD,
             MemberUseKind.PUTFIELD,
@@ -53,7 +54,7 @@
     classMemberRecord.logMemberUse(classMemberKey, Opcodes.INVOKESPECIAL);
     classMemberRecord.logMemberUse(classMemberKey, Opcodes.H_NEWINVOKESPECIAL);
 
-    assertThat(classMemberRecord.findAllMemberUseKind(classMemberKey))
+    assertThat(classMemberRecord.build().findAllMemberUseKind(classMemberKey))
         .containsExactly(MemberUseKind.INVOKESPECIAL, MemberUseKind.H_NEWINVOKESPECIAL);
   }
 
@@ -72,7 +73,7 @@
     classMemberRecord.logMemberUse(classMemberKey, Opcodes.H_NEWINVOKESPECIAL);
     classMemberRecord.logMemberUse(classMemberKey, Opcodes.H_INVOKEINTERFACE);
 
-    assertThat(classMemberRecord.findAllMemberUseKind(classMemberKey))
+    assertThat(classMemberRecord.build().findAllMemberUseKind(classMemberKey))
         .containsExactly(
             MemberUseKind.INVOKEVIRTUAL,
             MemberUseKind.INVOKESPECIAL,
@@ -93,9 +94,10 @@
     classMemberRecord.logMemberDecl(
         classMemberKey, Opcodes.ACC_PUBLIC | Opcodes.ACC_SUPER, Opcodes.ACC_PRIVATE);
 
-    assertThat(classMemberRecord.findOwnerAccessCode(classMemberKey))
+    ClassMemberRecord readOnlyClassMemberRecord = classMemberRecord.build();
+    assertThat(readOnlyClassMemberRecord.findOwnerAccessCode(classMemberKey))
         .isEqualTo(Opcodes.ACC_PUBLIC | Opcodes.ACC_SUPER);
-    assertThat(classMemberRecord.findMemberAccessCode(classMemberKey))
+    assertThat(readOnlyClassMemberRecord.findMemberAccessCode(classMemberKey))
         .isEqualTo(Opcodes.ACC_PRIVATE);
   }
 
@@ -107,15 +109,16 @@
         classMemberKey,
         Opcodes.ACC_DEPRECATED | Opcodes.ACC_PUBLIC | Opcodes.ACC_SUPER,
         Opcodes.ACC_DEPRECATED | Opcodes.ACC_PRIVATE);
-    assertThat(classMemberRecord.findOwnerAccessCode(classMemberKey))
+    ClassMemberRecord rawClassMemberRecord = classMemberRecord.build();
+    assertThat(rawClassMemberRecord.findOwnerAccessCode(classMemberKey))
         .isEqualTo(Opcodes.ACC_DEPRECATED | Opcodes.ACC_PUBLIC | Opcodes.ACC_SUPER);
-    assertThat(classMemberRecord.findMemberAccessCode(classMemberKey))
+    assertThat(rawClassMemberRecord.findMemberAccessCode(classMemberKey))
         .isEqualTo(Opcodes.ACC_DEPRECATED | Opcodes.ACC_PRIVATE);
   }
 
   @Test
   public void mergeRecord_trackingReasons() {
-    ClassMemberRecord otherClassMemberRecord = ClassMemberRecord.create();
+    ClassMemberRecordBuilder otherClassMemberRecord = ClassMemberRecord.builder();
 
     MethodKey method1 =
         MethodKey.create(ClassName.create("package/path/OwnerClass"), "method1", "(II)I");
@@ -131,23 +134,24 @@
     otherClassMemberRecord.logMemberUse(method2, Opcodes.INVOKESPECIAL);
     otherClassMemberRecord.logMemberUse(method3, Opcodes.INVOKEVIRTUAL);
 
-    classMemberRecord.mergeFrom(otherClassMemberRecord);
+    ClassMemberRecord mergedRecord =
+        classMemberRecord.mergeFrom(otherClassMemberRecord.build()).build();
 
-    assertThat(classMemberRecord.hasDeclReason(method1)).isTrue();
-    assertThat(classMemberRecord.findOwnerAccessCode(method1)).isEqualTo(Opcodes.ACC_SUPER);
-    assertThat(classMemberRecord.findMemberAccessCode(method1)).isEqualTo(Opcodes.ACC_PRIVATE);
-    assertThat(classMemberRecord.findAllMemberUseKind(method1)).isEmpty();
+    assertThat(mergedRecord.hasDeclReason(method1)).isTrue();
+    assertThat(mergedRecord.findOwnerAccessCode(method1)).isEqualTo(Opcodes.ACC_SUPER);
+    assertThat(mergedRecord.findMemberAccessCode(method1)).isEqualTo(Opcodes.ACC_PRIVATE);
+    assertThat(mergedRecord.findAllMemberUseKind(method1)).isEmpty();
 
-    assertThat(classMemberRecord.hasDeclReason(method2)).isTrue();
-    assertThat(classMemberRecord.findOwnerAccessCode(method2)).isEqualTo(Opcodes.ACC_SUPER);
-    assertThat(classMemberRecord.findMemberAccessCode(method2)).isEqualTo(Opcodes.ACC_PRIVATE);
-    assertThat(classMemberRecord.findAllMemberUseKind(method2))
+    assertThat(mergedRecord.hasDeclReason(method2)).isTrue();
+    assertThat(mergedRecord.findOwnerAccessCode(method2)).isEqualTo(Opcodes.ACC_SUPER);
+    assertThat(mergedRecord.findMemberAccessCode(method2)).isEqualTo(Opcodes.ACC_PRIVATE);
+    assertThat(mergedRecord.findAllMemberUseKind(method2))
         .containsExactly(MemberUseKind.INVOKEVIRTUAL, MemberUseKind.INVOKESPECIAL);
 
-    assertThat(classMemberRecord.hasDeclReason(method3)).isFalse();
-    assertThat(classMemberRecord.findOwnerAccessCode(method3)).isEqualTo(0);
-    assertThat(classMemberRecord.findMemberAccessCode(method3)).isEqualTo(0);
-    assertThat(classMemberRecord.findAllMemberUseKind(method3))
+    assertThat(mergedRecord.hasDeclReason(method3)).isFalse();
+    assertThat(mergedRecord.findOwnerAccessCode(method3)).isEqualTo(0);
+    assertThat(mergedRecord.findMemberAccessCode(method3)).isEqualTo(0);
+    assertThat(mergedRecord.findAllMemberUseKind(method3))
         .containsExactly(MemberUseKind.INVOKEVIRTUAL);
   }
 
@@ -157,10 +161,12 @@
         MethodKey.create(ClassName.create("package/path/OwnerClass"), "method", "(II)I");
 
     classMemberRecord.logMemberUse(classMemberKey, Opcodes.INVOKEVIRTUAL);
-    assertThat(classMemberRecord.hasTrackingReason(classMemberKey)).isTrue();
+    ClassMemberRecord rawClassMemberRecord = classMemberRecord.build();
+    assertThat(rawClassMemberRecord.hasTrackingReason(classMemberKey)).isTrue();
 
-    classMemberRecord.filterUsedMemberWithTrackedDeclaration();
-    assertThat(classMemberRecord.hasTrackingReason(classMemberKey)).isFalse();
+    ClassMemberRecord filteredRecord =
+        rawClassMemberRecord.filterUsedMemberWithTrackedDeclaration();
+    assertThat(filteredRecord.hasTrackingReason(classMemberKey)).isFalse();
   }
 
   @Test
@@ -170,10 +176,12 @@
 
     classMemberRecord.logMemberDecl(
         classMemberKey, Opcodes.ACC_PUBLIC | Opcodes.ACC_SUPER, Opcodes.ACC_PRIVATE);
-    assertThat(classMemberRecord.hasTrackingReason(classMemberKey)).isTrue();
+    ClassMemberRecord rawClassMemberRecord = classMemberRecord.build();
+    assertThat(rawClassMemberRecord.hasTrackingReason(classMemberKey)).isTrue();
 
-    classMemberRecord.filterUsedMemberWithTrackedDeclaration();
-    assertThat(classMemberRecord.hasTrackingReason(classMemberKey)).isFalse();
+    ClassMemberRecord filteredRecord =
+        rawClassMemberRecord.filterUsedMemberWithTrackedDeclaration();
+    assertThat(filteredRecord.hasTrackingReason(classMemberKey)).isFalse();
   }
 
   @Test
@@ -183,9 +191,11 @@
 
     classMemberRecord.logMemberDecl(
         classMemberKey, Opcodes.ACC_PUBLIC | Opcodes.ACC_INTERFACE, Opcodes.ACC_PRIVATE);
-    assertThat(classMemberRecord.hasTrackingReason(classMemberKey)).isTrue();
+    ClassMemberRecord rawClassMemberRecord = classMemberRecord.build();
+    assertThat(rawClassMemberRecord.hasTrackingReason(classMemberKey)).isTrue();
 
-    classMemberRecord.filterUsedMemberWithTrackedDeclaration();
-    assertThat(classMemberRecord.hasTrackingReason(classMemberKey)).isTrue();
+    ClassMemberRecord filteredRecord =
+        rawClassMemberRecord.filterUsedMemberWithTrackedDeclaration();
+    assertThat(filteredRecord.hasTrackingReason(classMemberKey)).isTrue();
   }
 }
diff --git a/src/test/java/com/google/devtools/build/android/desugar/nest/NestAnalyzerTest.java b/src/test/java/com/google/devtools/build/android/desugar/nest/NestAnalyzerTest.java
index 24c3343..21c4f33 100644
--- a/src/test/java/com/google/devtools/build/android/desugar/nest/NestAnalyzerTest.java
+++ b/src/test/java/com/google/devtools/build/android/desugar/nest/NestAnalyzerTest.java
@@ -19,8 +19,6 @@
 
 import com.google.common.collect.ImmutableList;
 import com.google.devtools.build.android.desugar.io.FileContentProvider;
-import com.google.devtools.build.android.desugar.langmodel.ClassAttributeRecord;
-import com.google.devtools.build.android.desugar.langmodel.ClassMemberRecord;
 import com.google.devtools.build.android.desugar.testing.junit.DesugarRule;
 import com.google.devtools.build.android.desugar.testing.junit.DesugarRunner;
 import com.google.devtools.build.android.desugar.testing.junit.JarEntryRecord;
@@ -51,15 +49,10 @@
           .enableIterativeTransformation(0)
           .build();
 
-  private final ClassMemberRecord classMemberRecord = ClassMemberRecord.create();
-  private final ClassAttributeRecord classAttributeRecord = ClassAttributeRecord.create();
-  private final NestDigest nestDigest = NestDigest.create(classMemberRecord, classAttributeRecord);
-
   @Test
   public void emptyInputFiles() throws IOException {
-    NestAnalyzer nestAnalyzer =
-        new NestAnalyzer(ImmutableList.of(), nestDigest, classMemberRecord, classAttributeRecord);
-    nestAnalyzer.analyze();
+    NestDigest nestDigest = NestAnalyzer.analyzeNests(ImmutableList.of());
+
     assertThat(nestDigest.getAllCompanionClassNames()).isEmpty();
   }
 
@@ -72,19 +65,15 @@
       throws IOException {
 
     JarFile jarFile = analyzedTarget.jarFile();
-    NestAnalyzer nestAnalyzer =
-        new NestAnalyzer(
+
+    NestDigest nestDigest =
+        NestAnalyzer.analyzeNests(
             jarFile.stream()
                 .map(
                     entry ->
                         new FileContentProvider<>(
                             entry.getName(), () -> getJarEntryInputStream(jarFile, entry)))
-                .collect(toImmutableList()),
-            nestDigest,
-            classMemberRecord,
-            classAttributeRecord);
-
-    nestAnalyzer.analyze();
+                .collect(toImmutableList()));
 
     assertThat(nestDigest.getAllCompanionClassNames())
         .containsExactly(
diff --git a/src/test/java/com/google/devtools/build/android/desugar/nest/NestDigestTest.java b/src/test/java/com/google/devtools/build/android/desugar/nest/NestDigestTest.java
index d789b3b..0f95132 100644
--- a/src/test/java/com/google/devtools/build/android/desugar/nest/NestDigestTest.java
+++ b/src/test/java/com/google/devtools/build/android/desugar/nest/NestDigestTest.java
@@ -17,8 +17,10 @@
 import static com.google.common.truth.Truth.assertThat;
 
 import com.google.devtools.build.android.desugar.langmodel.ClassAttributeRecord;
+import com.google.devtools.build.android.desugar.langmodel.ClassAttributeRecord.ClassAttributeRecordBuilder;
 import com.google.devtools.build.android.desugar.langmodel.ClassAttributes;
 import com.google.devtools.build.android.desugar.langmodel.ClassMemberRecord;
+import com.google.devtools.build.android.desugar.langmodel.ClassMemberRecord.ClassMemberRecordBuilder;
 import com.google.devtools.build.android.desugar.langmodel.ClassName;
 import com.google.devtools.build.android.desugar.langmodel.MethodKey;
 import org.junit.Test;
@@ -28,11 +30,10 @@
 
 /** The tests for {@link NestDigest}. */
 @RunWith(JUnit4.class)
-public class NestDigestTest {
+public final class NestDigestTest {
 
-  private final ClassMemberRecord classMemberRecord = ClassMemberRecord.create();
-  private final ClassAttributeRecord classAttributeRecord = ClassAttributeRecord.create();
-  private final NestDigest nestDigest = NestDigest.create(classMemberRecord, classAttributeRecord);
+  private final ClassMemberRecordBuilder classMemberRecord = ClassMemberRecord.builder();
+  private final ClassAttributeRecordBuilder classAttributeRecord = ClassAttributeRecord.builder();
 
   @Test
   public void prepareCompanionClassWriters_noCompanionClassesGenerated() {
@@ -41,7 +42,11 @@
         /* ownerAccess= */ Opcodes.ACC_PUBLIC | Opcodes.ACC_INTERFACE,
         /* memberDeclAccess= */ Opcodes.ACC_PRIVATE);
 
-    nestDigest.prepareCompanionClasses();
+    NestDigest nestDigest =
+        NestDigest.builder()
+            .setClassMemberRecord(classMemberRecord.build())
+            .setClassAttributeRecord(classAttributeRecord.build())
+            .build();
 
     assertThat(nestDigest.getAllCompanionClassNames()).isEmpty();
   }
@@ -53,18 +58,22 @@
     classMemberRecord.logMemberDecl(
         constructor, Opcodes.ACC_PUBLIC | Opcodes.ACC_SUPER, Opcodes.ACC_PRIVATE);
     classMemberRecord.logMemberUse(constructor, Opcodes.INVOKESPECIAL);
-    classAttributeRecord.setClassAttributes(
+    classAttributeRecord.addClassAttributes(
         ClassAttributes.builder()
             .setClassBinaryName(ClassName.create("package/path/OwnerClass$NestedClass"))
             .setNestHost(ClassName.create("package/path/OwnerClass"))
             .build());
-    classAttributeRecord.setClassAttributes(
+    classAttributeRecord.addClassAttributes(
         ClassAttributes.builder()
             .setClassBinaryName(ClassName.create("package/path/OwnerClass"))
             .addNestMember(ClassName.create("package/path/OwnerClass$NestedClass"))
             .build());
 
-    nestDigest.prepareCompanionClasses();
+    NestDigest nestDigest =
+        NestDigest.builder()
+            .setClassMemberRecord(classMemberRecord.build())
+            .setClassAttributeRecord(classAttributeRecord.build())
+            .build();
 
     assertThat(nestDigest.getAllCompanionClassNames())
         .containsExactly("package/path/OwnerClass$NestCC");
@@ -95,29 +104,33 @@
         MethodKey.create(ClassName.create("package/path/OwnerClassB"), "<init>", "()V"),
         Opcodes.INVOKESPECIAL);
 
-    classAttributeRecord.setClassAttributes(
+    classAttributeRecord.addClassAttributes(
         ClassAttributes.builder()
             .setClassBinaryName(ClassName.create("package/path/OwnerClassA$NestedClass"))
             .setNestHost(ClassName.create("package/path/OwnerClassA"))
             .build());
-    classAttributeRecord.setClassAttributes(
+    classAttributeRecord.addClassAttributes(
         ClassAttributes.builder()
             .setClassBinaryName(ClassName.create("package/path/OwnerClassB$NestedClass"))
             .setNestHost(ClassName.create("package/path/OwnerClassB"))
             .build());
 
-    classAttributeRecord.setClassAttributes(
+    classAttributeRecord.addClassAttributes(
         ClassAttributes.builder()
             .setClassBinaryName(ClassName.create("package/path/OwnerClassA"))
             .addNestMember(ClassName.create("package/path/OwnerClassA$NestedClass"))
             .build());
-    classAttributeRecord.setClassAttributes(
+    classAttributeRecord.addClassAttributes(
         ClassAttributes.builder()
             .setClassBinaryName(ClassName.create("package/path/OwnerClassB"))
             .addNestMember(ClassName.create("package/path/OwnerClassB$NestedClass"))
             .build());
 
-    nestDigest.prepareCompanionClasses();
+    NestDigest nestDigest =
+        NestDigest.builder()
+            .setClassMemberRecord(classMemberRecord.build())
+            .setClassAttributeRecord(classAttributeRecord.build())
+            .build();
 
     assertThat(nestDigest.getAllCompanionClassNames())
         .containsExactly("package/path/OwnerClassA$NestCC", "package/path/OwnerClassB$NestCC");
@@ -132,18 +145,22 @@
         constructor, Opcodes.ACC_PUBLIC | Opcodes.ACC_SUPER, Opcodes.ACC_PRIVATE);
     classMemberRecord.logMemberUse(constructor, Opcodes.INVOKESPECIAL);
 
-    classAttributeRecord.setClassAttributes(
+    classAttributeRecord.addClassAttributes(
         ClassAttributes.builder()
             .setClassBinaryName(ClassName.create(constructor.ownerName() + "$NestClass"))
             .setNestHost(ClassName.create(constructor.ownerName()))
             .build());
-    classAttributeRecord.setClassAttributes(
+    classAttributeRecord.addClassAttributes(
         ClassAttributes.builder()
             .setClassBinaryName(ClassName.create(constructor.ownerName()))
             .addNestMember(ClassName.create(constructor.ownerName() + "$NestClass"))
             .build());
 
-    nestDigest.prepareCompanionClasses();
+    NestDigest nestDigest =
+        NestDigest.builder()
+            .setClassMemberRecord(classMemberRecord.build())
+            .setClassAttributeRecord(classAttributeRecord.build())
+            .build();
 
     assertThat(nestDigest.getAllCompanionClassNames())
         .containsExactly("package/path/$Owner$Class$$NestCC");
diff --git a/src/tools/android/java/com/google/devtools/build/android/desugar/Desugar.java b/src/tools/android/java/com/google/devtools/build/android/desugar/Desugar.java
index 625eb1c..9eff3bc 100644
--- a/src/tools/android/java/com/google/devtools/build/android/desugar/Desugar.java
+++ b/src/tools/android/java/com/google/devtools/build/android/desugar/Desugar.java
@@ -40,8 +40,6 @@
 import com.google.devtools.build.android.desugar.io.InputFileProvider;
 import com.google.devtools.build.android.desugar.io.OutputFileProvider;
 import com.google.devtools.build.android.desugar.io.ThrowingClassLoader;
-import com.google.devtools.build.android.desugar.langmodel.ClassAttributeRecord;
-import com.google.devtools.build.android.desugar.langmodel.ClassMemberRecord;
 import com.google.devtools.build.android.desugar.langmodel.ClassMemberUseCounter;
 import com.google.devtools.build.android.desugar.nest.NestAnalyzer;
 import com.google.devtools.build.android.desugar.nest.NestDesugaring;
@@ -655,12 +653,10 @@
       ClassVsInterface interfaceCache,
       ImmutableSet.Builder<String> interfaceLambdaMethodCollector)
       throws IOException {
-    ClassMemberRecord classMemberRecord = ClassMemberRecord.create();
-    ClassAttributeRecord classAttributeRecord = ClassAttributeRecord.create();
+
     ImmutableList<FileContentProvider<? extends InputStream>> inputFileContents =
         inputFiles.toInputFileStreams();
-    NestDigest nestDigest =
-        NestAnalyzer.analyzeNests(inputFileContents, classMemberRecord, classAttributeRecord);
+    NestDigest nestDigest = NestAnalyzer.analyzeNests(inputFileContents);
     // Apply core library type name remapping to the digest instance produced by the nest analyzer,
     // since the analysis-oriented nest analyzer visits core library classes without name remapping
     // as those transformation-oriented visitors.
@@ -1142,6 +1138,7 @@
     return 0;
   }
 
+  @SuppressWarnings("CatchAndPrintStackTrace")
   static void verifyLambdaDumpDirectoryRegistered(Path dumpDirectory) throws IOException {
     try {
       Class<?> klass = Class.forName("java.lang.invoke.InnerClassLambdaMetafactory");
@@ -1161,7 +1158,7 @@
     } catch (ReflectiveOperationException e) {
       // We do not want to crash Desugar, if we cannot load or access these classes or fields.
       // We aim to provide better diagnostics. If we cannot, just let it go.
-      e.printStackTrace(System.err); // To silence error-prone's complaint.
+      e.printStackTrace(System.err);
     }
   }
 
diff --git a/src/tools/android/java/com/google/devtools/build/android/desugar/langmodel/ClassAttributeRecord.java b/src/tools/android/java/com/google/devtools/build/android/desugar/langmodel/ClassAttributeRecord.java
index 95b21e0..b54b2fe 100644
--- a/src/tools/android/java/com/google/devtools/build/android/desugar/langmodel/ClassAttributeRecord.java
+++ b/src/tools/android/java/com/google/devtools/build/android/desugar/langmodel/ClassAttributeRecord.java
@@ -18,58 +18,59 @@
 
 import static com.google.common.base.Preconditions.checkNotNull;
 
+import com.google.auto.value.AutoValue;
+import com.google.common.collect.ImmutableMap;
 import com.google.common.collect.ImmutableSet;
-import java.util.HashMap;
-import java.util.Map;
 import java.util.Optional;
 
 /** Tracks {@link ClassAttributes} for all classes under investigation. */
-public final class ClassAttributeRecord implements TypeMappable<ClassAttributeRecord> {
+@AutoValue
+public abstract class ClassAttributeRecord implements TypeMappable<ClassAttributeRecord> {
 
-  private final Map<ClassName, ClassAttributes> record;
+  public abstract ImmutableMap<ClassName, ClassAttributes> record();
 
-  public static ClassAttributeRecord create() {
-    return new ClassAttributeRecord(new HashMap<>());
+  public static ClassAttributeRecordBuilder builder() {
+    return new AutoValue_ClassAttributeRecord.Builder();
   }
 
-  private ClassAttributeRecord(Map<ClassName, ClassAttributes> record) {
-    this.record = record;
-  }
-
-  public ClassAttributes setClassAttributes(ClassAttributes classAttributes) {
-    ClassName classBinaryName = classAttributes.classBinaryName();
-    if (record.containsKey(classBinaryName)) {
-      throw new IllegalStateException(
-          String.format(
-              "Expected the ClassAttributes of a class to be put into this record only once during"
-                  + " {@link ClassVisitor#visitEnd}: Pre-existing: (%s), Now (%s)",
-              record.get(classBinaryName), classAttributes));
-    }
-    return record.put(classBinaryName, classAttributes);
-  }
-
-  public Optional<ClassName> getNestHost(ClassName className) {
-    ClassAttributes classAttributes = record.get(className);
+  public final Optional<ClassName> getNestHost(ClassName className) {
+    ClassAttributes classAttributes = record().get(className);
     checkNotNull(
         classAttributes,
         "Expected recorded ClassAttributes for (%s). Available record: %s",
         className,
-        record.keySet());
+        record().keySet());
     return classAttributes.nestHost();
   }
 
-  public ImmutableSet<ClassName> getNestMembers(ClassName className) {
-    ClassAttributes classAttributes = record.get(className);
+  public final ImmutableSet<ClassName> getNestMembers(ClassName className) {
+    ClassAttributes classAttributes = record().get(className);
     checkNotNull(
         classAttributes,
         "Expected recorded ClassAttributes for (%s). Available record: %s",
         className,
-        record.keySet());
+        record().keySet());
     return classAttributes.nestMembers();
   }
 
   @Override
-  public ClassAttributeRecord acceptTypeMapper(TypeMapper typeMapper) {
-    return new ClassAttributeRecord(typeMapper.mapMutable(record));
+  public final ClassAttributeRecord acceptTypeMapper(TypeMapper typeMapper) {
+    return ClassAttributeRecord.builder().setRecord(typeMapper.map(record())).build();
+  }
+
+  /** The builder for {@link ClassAttributeRecord}. */
+  @AutoValue.Builder
+  public abstract static class ClassAttributeRecordBuilder {
+
+    abstract ImmutableMap.Builder<ClassName, ClassAttributes> recordBuilder();
+
+    abstract ClassAttributeRecordBuilder setRecord(ImmutableMap<ClassName, ClassAttributes> record);
+
+    public final ClassAttributeRecordBuilder addClassAttributes(ClassAttributes classAttributes) {
+      recordBuilder().put(classAttributes.classBinaryName(), classAttributes);
+      return this;
+    }
+
+    public abstract ClassAttributeRecord build();
   }
 }
diff --git a/src/tools/android/java/com/google/devtools/build/android/desugar/langmodel/ClassMemberRecord.java b/src/tools/android/java/com/google/devtools/build/android/desugar/langmodel/ClassMemberRecord.java
index 28786ae..0608a91 100644
--- a/src/tools/android/java/com/google/devtools/build/android/desugar/langmodel/ClassMemberRecord.java
+++ b/src/tools/android/java/com/google/devtools/build/android/desugar/langmodel/ClassMemberRecord.java
@@ -17,8 +17,13 @@
 package com.google.devtools.build.android.desugar.langmodel;
 
 import static com.google.common.collect.ImmutableList.toImmutableList;
+import static com.google.common.collect.ImmutableMap.toImmutableMap;
 
+import com.google.auto.value.AutoValue;
 import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.Maps;
+import com.google.devtools.build.android.desugar.langmodel.ClassMemberTrackReason.ClassMemberTrackReasonBuilder;
 import java.util.LinkedHashMap;
 import java.util.Map;
 import java.util.function.Predicate;
@@ -27,105 +32,123 @@
  * A record that tracks the declarations and usages of a class member, including fields,
  * constructors and methods.
  */
-public final class ClassMemberRecord implements TypeMappable<ClassMemberRecord> {
+@AutoValue
+public abstract class ClassMemberRecord implements TypeMappable<ClassMemberRecord> {
 
   /** Tracks a class member with a reason. */
-  private final Map<ClassMemberKey<?>, ClassMemberTrackReason> reasons;
+  abstract ImmutableMap<ClassMemberKey<?>, ClassMemberTrackReason> reasons();
 
-  /** The factory method of this class. */
-  public static ClassMemberRecord create() {
-    return new ClassMemberRecord(new LinkedHashMap<>());
+  /** Creates a builder instance for this class. */
+  public static ClassMemberRecordBuilder builder() {
+    return new AutoValue_ClassMemberRecord.Builder();
   }
 
   /** Gets class members with both tracked declarations and tracked usage. */
-  public boolean filterUsedMemberWithTrackedDeclaration() {
-    return reasons
-        .values()
-        .removeIf(
-            reason -> {
-              // keep interface members and class members with use references.
-              return !(reason.hasInterfaceDeclReason()
-                  || (reason.hasDeclReason() && reason.hasMemberUseReason()));
-            });
-  }
-
-  private ClassMemberRecord(Map<ClassMemberKey<?>, ClassMemberTrackReason> reasons) {
-    this.reasons = reasons;
+  public final ClassMemberRecord filterUsedMemberWithTrackedDeclaration() {
+    return builder()
+        .setReasons(
+            reasons().entrySet().stream()
+                .filter(
+                    entry -> {
+                      ClassMemberTrackReason reason = entry.getValue();
+                      // keep interface members and class members with use references.
+                      return reason.hasInterfaceDeclReason()
+                          || (reason.hasDeclReason() && reason.hasMemberUseReason());
+                    })
+                .collect(toImmutableMap(Map.Entry::getKey, Map.Entry::getValue)))
+        .autoInternalBuild();
   }
 
   /** Find all member keys that represent a constructor. */
-  public ImmutableList<ClassMemberKey<?>> findAllConstructorMemberKeys() {
+  public final ImmutableList<ClassMemberKey<?>> findAllConstructorMemberKeys() {
     return findAllMatchedMemberKeys(ClassMemberKey::isConstructor);
   }
 
   /** Find all member keys based on the given member key predicate. */
-  ImmutableList<ClassMemberKey<?>> findAllMatchedMemberKeys(
+  private ImmutableList<ClassMemberKey<?>> findAllMatchedMemberKeys(
       Predicate<ClassMemberKey<?>> predicate) {
-    return reasons.keySet().stream().filter(predicate).collect(toImmutableList());
+    return reasons().keySet().stream().filter(predicate).collect(toImmutableList());
   }
 
-  /** Whether this record has any reason to desugar a nest. */
-  public boolean hasAnyReason() {
-    return !reasons.isEmpty();
+  public final boolean hasTrackingReason(ClassMemberKey<?> classMemberKey) {
+    return reasons().containsKey(classMemberKey);
   }
 
-  public boolean hasTrackingReason(ClassMemberKey<?> classMemberKey) {
-    return reasons.containsKey(classMemberKey);
-  }
-
-  boolean hasDeclReason(ClassMemberKey<?> classMemberKey) {
-    return hasTrackingReason(classMemberKey) && reasons.get(classMemberKey).hasDeclReason();
+  final boolean hasDeclReason(ClassMemberKey<?> classMemberKey) {
+    return hasTrackingReason(classMemberKey) && reasons().get(classMemberKey).hasDeclReason();
   }
 
   /** Find the original access code for the owner of the class member. */
-  int findOwnerAccessCode(ClassMemberKey<?> memberKey) {
-    if (reasons.containsKey(memberKey)) {
-      return reasons.get(memberKey).getOwnerAccess();
+  final int findOwnerAccessCode(ClassMemberKey<?> memberKey) {
+    if (reasons().containsKey(memberKey)) {
+      return reasons().get(memberKey).ownerAccess();
     }
     throw new IllegalStateException(String.format("ClassMemberKey Not Found: %s", memberKey));
   }
 
   /** Find the original access code for the class member declaration. */
-  int findMemberAccessCode(ClassMemberKey<?> memberKey) {
-    if (reasons.containsKey(memberKey)) {
-      return reasons.get(memberKey).getMemberAccess();
+  final int findMemberAccessCode(ClassMemberKey<?> memberKey) {
+    if (reasons().containsKey(memberKey)) {
+      return reasons().get(memberKey).memberAccess();
     }
     throw new IllegalStateException(String.format("ClassMemberKey Not Found: %s", memberKey));
   }
 
   /** Find all invocation codes of a class member. */
-  public ImmutableList<MemberUseKind> findAllMemberUseKind(ClassMemberKey<?> memberKey) {
-    if (reasons.containsKey(memberKey)) {
-      return ImmutableList.copyOf(reasons.get(memberKey).getUseAccesses());
-    }
-    return ImmutableList.of();
-  }
-
-  /** Logs the declaration of a class member. */
-  public ClassMemberTrackReason logMemberDecl(
-      ClassMemberKey<?> memberKey, int ownerAccess, int memberDeclAccess) {
-    return reasons
-        .computeIfAbsent(memberKey, classMemberKey -> new ClassMemberTrackReason())
-        .setDeclAccess(ownerAccess, memberDeclAccess);
-  }
-
-  /** Logs the use of a class member, including field access and method invocations. */
-  public ClassMemberTrackReason logMemberUse(ClassMemberKey<?> memberKey, int invokeOpcode) {
-    return reasons
-        .computeIfAbsent(memberKey, classMemberKey -> new ClassMemberTrackReason())
-        .addUseAccess(invokeOpcode);
-  }
-
-  /** Merge an another member record into this record. */
-  public void mergeFrom(ClassMemberRecord otherClassMemberRecord) {
-    otherClassMemberRecord.reasons.forEach(
-        (classMemberKey, classMemberTrackReason) ->
-            reasons.merge(
-                classMemberKey, classMemberTrackReason, ClassMemberTrackReason::mergeFrom));
+  public final ImmutableList<MemberUseKind> findAllMemberUseKind(ClassMemberKey<?> memberKey) {
+    return reasons().containsKey(memberKey)
+        ? reasons().get(memberKey).useAccesses().asList()
+        : ImmutableList.of();
   }
 
   @Override
   public ClassMemberRecord acceptTypeMapper(TypeMapper typeMapper) {
-    return new ClassMemberRecord(typeMapper.mapKey(reasons));
+    return builder().setReasons(typeMapper.mapKey(reasons())).autoInternalBuild();
+  }
+
+  /** The builder for {@link ClassMemberRecord}. */
+  @AutoValue.Builder
+  public abstract static class ClassMemberRecordBuilder {
+
+    private final Map<ClassMemberKey<?>, ClassMemberTrackReasonBuilder> reasonsCollector =
+        new LinkedHashMap<>();
+
+    abstract ClassMemberRecordBuilder setReasons(
+        Map<ClassMemberKey<?>, ClassMemberTrackReason> value);
+
+    /** Logs the declaration of a class member. */
+    public final ClassMemberTrackReasonBuilder logMemberDecl(
+        ClassMemberKey<?> memberKey, int ownerAccess, int memberDeclAccess) {
+      return getTrackReason(memberKey).setDeclAccess(ownerAccess, memberDeclAccess);
+    }
+
+    /** Logs the use of a class member, including field access and method invocations. */
+    public final ClassMemberTrackReasonBuilder logMemberUse(
+        ClassMemberKey<?> memberKey, int invokeOpcode) {
+      return getTrackReason(memberKey).addUseAccess(invokeOpcode);
+    }
+
+    /** Merges an another member record into this record builder. */
+    public final ClassMemberRecordBuilder mergeFrom(ClassMemberRecord otherClassMemberRecord) {
+      otherClassMemberRecord
+          .reasons()
+          .forEach(
+              (otherMemberKey, otherMemberTrackReason) ->
+                  getTrackReason(otherMemberKey).mergeFrom(otherMemberTrackReason));
+      return this;
+    }
+
+    private ClassMemberTrackReasonBuilder getTrackReason(ClassMemberKey<?> memberKey) {
+      return reasonsCollector.computeIfAbsent(
+          memberKey, classMemberKey -> ClassMemberTrackReason.builder());
+    }
+
+    public final ClassMemberRecord build() {
+      return setReasons(
+              Maps.transformValues(reasonsCollector, ClassMemberTrackReasonBuilder::build))
+          .autoInternalBuild();
+    }
+
+    abstract ClassMemberRecord autoInternalBuild();
   }
 }
diff --git a/src/tools/android/java/com/google/devtools/build/android/desugar/langmodel/ClassMemberTrackReason.java b/src/tools/android/java/com/google/devtools/build/android/desugar/langmodel/ClassMemberTrackReason.java
index 223682f..8c06f32 100644
--- a/src/tools/android/java/com/google/devtools/build/android/desugar/langmodel/ClassMemberTrackReason.java
+++ b/src/tools/android/java/com/google/devtools/build/android/desugar/langmodel/ClassMemberTrackReason.java
@@ -16,98 +16,79 @@
 
 package com.google.devtools.build.android.desugar.langmodel;
 
-import com.google.common.hash.Hashing;
-import java.util.EnumSet;
+import com.google.auto.value.AutoValue;
+import com.google.common.collect.ImmutableSet;
+import java.util.Collection;
 import org.objectweb.asm.Opcodes;
 
 /**
  * Used to track the declaration and invocation information of a class member, including fields,
  * constructors and methods.
  */
-public final class ClassMemberTrackReason {
+@AutoValue
+abstract class ClassMemberTrackReason {
 
-  private boolean hasDeclReason;
-  private int ownerAccess;
-  private int memberAccess;
-  private final EnumSet<MemberUseKind> useAccesses = EnumSet.noneOf(MemberUseKind.class);
+  abstract boolean hasDeclReason();
 
-  ClassMemberTrackReason setDeclAccess(int ownerAccess, int memberAccess) {
-    this.ownerAccess = ownerAccess;
-    this.memberAccess = memberAccess;
-    this.hasDeclReason = true;
-    return this;
+  abstract int ownerAccess();
+
+  abstract int memberAccess();
+
+  abstract ImmutableSet<MemberUseKind> useAccesses();
+
+  public static ClassMemberTrackReasonBuilder builder() {
+    return new AutoValue_ClassMemberTrackReason.Builder()
+        .setHasDeclReason(false)
+        .setOwnerAccess(0)
+        .setMemberAccess(0);
   }
 
-  ClassMemberTrackReason addUseAccess(int invokeOpcode) {
-    this.useAccesses.add(MemberUseKind.fromValue(invokeOpcode));
-    return this;
+  abstract ClassMemberTrackReasonBuilder toBuilder();
+
+  final boolean hasInterfaceDeclReason() {
+    return hasDeclReason() && (ownerAccess() & Opcodes.ACC_INTERFACE) != 0;
   }
 
-  boolean hasDeclReason() {
-    return hasDeclReason;
+  final boolean hasMemberUseReason() {
+    return !useAccesses().isEmpty();
   }
 
-  boolean hasInterfaceDeclReason() {
-    return hasDeclReason && (ownerAccess & Opcodes.ACC_INTERFACE) != 0;
-  }
+  /** The builder for {@link ClassMemberTrackReason}. */
+  @AutoValue.Builder
+  abstract static class ClassMemberTrackReasonBuilder {
 
-  boolean hasMemberUseReason() {
-    return !useAccesses.isEmpty();
-  }
+    abstract ClassMemberTrackReasonBuilder setHasDeclReason(boolean value);
 
-  int getOwnerAccess() {
-    return ownerAccess;
-  }
+    abstract ClassMemberTrackReasonBuilder setOwnerAccess(int value);
 
-  int getMemberAccess() {
-    return memberAccess;
-  }
+    abstract ClassMemberTrackReasonBuilder setMemberAccess(int value);
 
-  EnumSet<MemberUseKind> getUseAccesses() {
-    return useAccesses;
-  }
+    abstract ClassMemberTrackReasonBuilder setUseAccesses(Collection<MemberUseKind> value);
 
-  ClassMemberTrackReason mergeFrom(ClassMemberTrackReason otherClassMemberTrackReason) {
-    if (!hasDeclReason() && otherClassMemberTrackReason.hasDeclReason()) {
-      ownerAccess = otherClassMemberTrackReason.getOwnerAccess();
-      memberAccess = otherClassMemberTrackReason.getMemberAccess();
-      hasDeclReason = true;
+    abstract ImmutableSet.Builder<MemberUseKind> useAccessesBuilder();
+
+    final ClassMemberTrackReasonBuilder setDeclAccess(int ownerAccess, int memberAccess) {
+      return setHasDeclReason(true).setOwnerAccess(ownerAccess).setMemberAccess(memberAccess);
     }
-    useAccesses.addAll(otherClassMemberTrackReason.getUseAccesses());
-    return this;
-  }
 
-  @Override
-  public int hashCode() {
-    return Hashing.sha256()
-        .newHasher()
-        .putBoolean(hasDeclReason)
-        .putInt(ownerAccess)
-        .putInt(memberAccess)
-        .putInt(useAccesses.hashCode())
-        .hash()
-        .asInt();
-  }
-
-  @Override
-  public boolean equals(Object obj) {
-    if (obj == this) {
-      return true;
+    final ClassMemberTrackReasonBuilder addUseAccess(int invokeOpcode) {
+      useAccessesBuilder().add(MemberUseKind.fromValue(invokeOpcode));
+      return this;
     }
-    if (obj instanceof ClassMemberTrackReason) {
-      ClassMemberTrackReason other = (ClassMemberTrackReason) obj;
-      return this.hasDeclReason == other.hasDeclReason
-          && this.ownerAccess == other.ownerAccess
-          && this.memberAccess == other.memberAccess
-          && this.useAccesses.equals(other.useAccesses);
-    }
-    return false;
-  }
 
-  @Override
-  public String toString() {
-    return String.format(
-        "%s{hasDeclReason=%s, ownerAccess=%d, memberAccess=%d, useAccesses=%s}",
-        getClass().getSimpleName(), hasDeclReason, ownerAccess, memberAccess, useAccesses);
+    final ClassMemberTrackReasonBuilder addAllUseAccesses(Collection<MemberUseKind> values) {
+      useAccessesBuilder().addAll(values);
+      return this;
+    }
+
+    final ClassMemberTrackReasonBuilder mergeFrom(ClassMemberTrackReason otherReason) {
+      if (otherReason.hasDeclReason()) {
+        setDeclAccess(otherReason.ownerAccess(), otherReason.memberAccess());
+      }
+      addAllUseAccesses(otherReason.useAccesses());
+      return this;
+    }
+
+    abstract ClassMemberTrackReason build();
   }
 }
diff --git a/src/tools/android/java/com/google/devtools/build/android/desugar/langmodel/TypeMapper.java b/src/tools/android/java/com/google/devtools/build/android/desugar/langmodel/TypeMapper.java
index 25ecee2..4ed92bd 100644
--- a/src/tools/android/java/com/google/devtools/build/android/desugar/langmodel/TypeMapper.java
+++ b/src/tools/android/java/com/google/devtools/build/android/desugar/langmodel/TypeMapper.java
@@ -20,14 +20,11 @@
 import static com.google.common.collect.ImmutableMap.toImmutableMap;
 import static com.google.common.collect.ImmutableSet.toImmutableSet;
 import static java.util.stream.Collectors.toCollection;
-import static java.util.stream.Collectors.toMap;
 
 import com.google.common.collect.ConcurrentHashMultiset;
 import com.google.common.collect.ImmutableList;
 import com.google.common.collect.ImmutableMap;
 import com.google.common.collect.ImmutableSet;
-import java.util.HashMap;
-import java.util.Map;
 import java.util.function.Function;
 import org.objectweb.asm.commons.Remapper;
 
@@ -73,32 +70,10 @@
                 e -> e.getKey().acceptTypeMapper(this), e -> e.getValue().acceptTypeMapper(this)));
   }
 
-  // TODO(b/149403079): Remove this method once the map collection used by referencing classes have
-  // been migrated to immutable.
-  public <K extends TypeMappable<? extends K>, V extends TypeMappable<V>> Map<K, V> mapMutable(
-      Map<K, V> mappableTypes) {
-    return mappableTypes.entrySet().stream()
-        .collect(
-            toMap(
-                e -> e.getKey().acceptTypeMapper(this),
-                e -> e.getValue().acceptTypeMapper(this),
-                (prev, next) -> next,
-                HashMap::new));
-  }
-
   public <K extends TypeMappable<? extends K>, V> ImmutableMap<K, V> mapKey(
       ImmutableMap<K, V> mappableTypes) {
     return mappableTypes.entrySet().stream()
         .collect(toImmutableMap(e -> e.getKey().acceptTypeMapper(this), e -> e.getValue()));
   }
 
-  public <K extends TypeMappable<? extends K>, V> Map<K, V> mapKey(Map<K, V> mappableTypes) {
-    return mappableTypes.entrySet().stream()
-        .collect(
-            toMap(
-                e -> e.getKey().acceptTypeMapper(this),
-                e -> e.getValue(),
-                (prev, next) -> next,
-                HashMap::new));
-  }
 }
diff --git a/src/tools/android/java/com/google/devtools/build/android/desugar/nest/CrossMateMainCollector.java b/src/tools/android/java/com/google/devtools/build/android/desugar/nest/CrossMateMainCollector.java
index 24d7b65..937abb6 100644
--- a/src/tools/android/java/com/google/devtools/build/android/desugar/nest/CrossMateMainCollector.java
+++ b/src/tools/android/java/com/google/devtools/build/android/desugar/nest/CrossMateMainCollector.java
@@ -14,10 +14,11 @@
 
 package com.google.devtools.build.android.desugar.nest;
 
-import com.google.devtools.build.android.desugar.langmodel.ClassAttributeRecord;
+import com.google.devtools.build.android.desugar.langmodel.ClassAttributeRecord.ClassAttributeRecordBuilder;
 import com.google.devtools.build.android.desugar.langmodel.ClassAttributes;
 import com.google.devtools.build.android.desugar.langmodel.ClassAttributes.ClassAttributesBuilder;
 import com.google.devtools.build.android.desugar.langmodel.ClassMemberRecord;
+import com.google.devtools.build.android.desugar.langmodel.ClassMemberRecord.ClassMemberRecordBuilder;
 import com.google.devtools.build.android.desugar.langmodel.ClassName;
 import com.google.devtools.build.android.desugar.langmodel.FieldKey;
 import com.google.devtools.build.android.desugar.langmodel.LangModelHelper;
@@ -34,15 +35,15 @@
 final class CrossMateMainCollector extends ClassVisitor {
 
   /** The project-wise class member records. */
-  private final ClassMemberRecord memberRecord;
+  private final ClassMemberRecordBuilder memberRecord;
 
-  private final ClassAttributeRecord classAttributeRecord;
+  private final ClassAttributeRecordBuilder classAttributeRecord;
 
   /**
    * An class member record to stage member record candidates, merging into the project-wise member
    * record during the {@link #visitEnd()} where eligible conditions are specified.
    */
-  private final ClassMemberRecord stagingMemberRecord = ClassMemberRecord.create();
+  private final ClassMemberRecordBuilder stagingMemberRecord = ClassMemberRecord.builder();
 
   private final ClassAttributesBuilder classAttributesBuilder = ClassAttributes.builder();
 
@@ -51,7 +52,7 @@
   private boolean isInNest;
 
   public CrossMateMainCollector(
-      ClassMemberRecord memberRecord, ClassAttributeRecord classAttributeRecord) {
+      ClassMemberRecordBuilder memberRecord, ClassAttributeRecordBuilder classAttributeRecord) {
     super(Opcodes.ASM7);
     this.memberRecord = memberRecord;
     this.classAttributeRecord = classAttributeRecord;
@@ -129,9 +130,9 @@
   @Override
   public void visitEnd() {
     if (isInNest || (classAccessCode & Opcodes.ACC_INTERFACE) != 0) {
-      memberRecord.mergeFrom(stagingMemberRecord);
+      memberRecord.mergeFrom(stagingMemberRecord.build());
     }
-    classAttributeRecord.setClassAttributes(classAttributesBuilder.build());
+    classAttributeRecord.addClassAttributes(classAttributesBuilder.build());
     super.visitEnd();
   }
 
@@ -150,10 +151,12 @@
      *
      * <p>@see CrossMateMainCollector#stagingMemberRecord for more details.
      */
-    private final ClassMemberRecord memberRecord;
+    private final ClassMemberRecordBuilder memberRecord;
 
     CrossMateRefCollector(
-        MethodVisitor methodVisitor, MethodKey enclosingMethodKey, ClassMemberRecord memberRecord) {
+        MethodVisitor methodVisitor,
+        MethodKey enclosingMethodKey,
+        ClassMemberRecordBuilder memberRecord) {
       super(Opcodes.ASM7, methodVisitor);
       this.enclosingMethodKey = enclosingMethodKey;
       this.memberRecord = memberRecord;
diff --git a/src/tools/android/java/com/google/devtools/build/android/desugar/nest/NestAnalyzer.java b/src/tools/android/java/com/google/devtools/build/android/desugar/nest/NestAnalyzer.java
index 14a0e77..204dfea 100644
--- a/src/tools/android/java/com/google/devtools/build/android/desugar/nest/NestAnalyzer.java
+++ b/src/tools/android/java/com/google/devtools/build/android/desugar/nest/NestAnalyzer.java
@@ -16,11 +16,12 @@
 
 import static com.google.common.base.Preconditions.checkNotNull;
 
-import com.google.common.annotations.VisibleForTesting;
 import com.google.common.collect.ImmutableList;
 import com.google.devtools.build.android.desugar.io.FileContentProvider;
 import com.google.devtools.build.android.desugar.langmodel.ClassAttributeRecord;
+import com.google.devtools.build.android.desugar.langmodel.ClassAttributeRecord.ClassAttributeRecordBuilder;
 import com.google.devtools.build.android.desugar.langmodel.ClassMemberRecord;
+import com.google.devtools.build.android.desugar.langmodel.ClassMemberRecord.ClassMemberRecordBuilder;
 import java.io.IOException;
 import java.io.InputStream;
 import org.objectweb.asm.ClassReader;
@@ -32,9 +33,8 @@
 public class NestAnalyzer {
 
   private final ImmutableList<FileContentProvider<? extends InputStream>> inputFileContents;
-  private final NestDigest nestDigest;
-  private final ClassMemberRecord classMemberRecord;
-  private final ClassAttributeRecord classAttributeRecord;
+  private final ClassMemberRecordBuilder classMemberRecord;
+  private final ClassAttributeRecordBuilder classAttributeRecord;
 
   /**
    * Perform a nest-based analysis of input classes, including tracking private member access
@@ -43,32 +43,25 @@
    * @return A manager class for nest companions.
    */
   public static NestDigest analyzeNests(
-      ImmutableList<FileContentProvider<? extends InputStream>> inputFileContents,
-      ClassMemberRecord classMemberRecord,
-      ClassAttributeRecord classAttributeRecord)
+      ImmutableList<FileContentProvider<? extends InputStream>> inputFileContents)
       throws IOException {
-    NestDigest nestDigest = NestDigest.create(classMemberRecord, classAttributeRecord);
     NestAnalyzer nestAnalyzer =
-        new NestAnalyzer(inputFileContents, nestDigest, classMemberRecord, classAttributeRecord);
-    nestAnalyzer.analyze();
-    return nestDigest;
+        new NestAnalyzer(
+            inputFileContents, ClassAttributeRecord.builder(), ClassMemberRecord.builder());
+    return nestAnalyzer.analyze();
   }
 
-  @VisibleForTesting
-  NestAnalyzer(
+  private NestAnalyzer(
       ImmutableList<FileContentProvider<? extends InputStream>> inputFileContents,
-      NestDigest nestDigest,
-      ClassMemberRecord classMemberRecord,
-      ClassAttributeRecord classAttributeRecord) {
+      ClassAttributeRecordBuilder classAttributeRecord,
+      ClassMemberRecordBuilder classMemberRecord) {
     this.inputFileContents = checkNotNull(inputFileContents);
-    this.nestDigest = checkNotNull(nestDigest);
-    this.classMemberRecord = checkNotNull(classMemberRecord);
+    this.classMemberRecord = classMemberRecord;
     this.classAttributeRecord = classAttributeRecord;
   }
 
   /** Performs class member declaration and usage analysis of files. */
-  @VisibleForTesting
-  void analyze() throws IOException {
+  private NestDigest analyze() throws IOException {
     for (FileContentProvider<? extends InputStream> inputClassFile : inputFileContents) {
       if (inputClassFile.isClassFile()) {
         try (InputStream inputStream = inputClassFile.get()) {
@@ -79,7 +72,10 @@
         }
       }
     }
-    classMemberRecord.filterUsedMemberWithTrackedDeclaration();
-    nestDigest.prepareCompanionClasses();
+
+    return NestDigest.builder()
+        .setClassMemberRecord(classMemberRecord.build().filterUsedMemberWithTrackedDeclaration())
+        .setClassAttributeRecord(classAttributeRecord.build())
+        .build();
   }
 }
diff --git a/src/tools/android/java/com/google/devtools/build/android/desugar/nest/NestDigest.java b/src/tools/android/java/com/google/devtools/build/android/desugar/nest/NestDigest.java
index 9d983f2..d39c49e 100644
--- a/src/tools/android/java/com/google/devtools/build/android/desugar/nest/NestDigest.java
+++ b/src/tools/android/java/com/google/devtools/build/android/desugar/nest/NestDigest.java
@@ -16,8 +16,11 @@
 
 import static com.google.common.base.Preconditions.checkNotNull;
 import static com.google.common.collect.ImmutableList.toImmutableList;
+import static com.google.common.collect.ImmutableMap.toImmutableMap;
 import static com.google.devtools.build.android.desugar.langmodel.LangModelConstants.NEST_COMPANION_CLASS_SIMPLE_NAME;
 
+import com.google.auto.value.AutoValue;
+import com.google.auto.value.extension.memoized.Memoized;
 import com.google.common.collect.ImmutableList;
 import com.google.common.collect.ImmutableMap;
 import com.google.common.collect.Streams;
@@ -30,7 +33,6 @@
 import com.google.devtools.build.android.desugar.langmodel.TypeMappable;
 import com.google.devtools.build.android.desugar.langmodel.TypeMapper;
 import java.io.ByteArrayInputStream;
-import java.util.HashMap;
 import java.util.Map;
 import java.util.Optional;
 import java.util.Set;
@@ -38,60 +40,50 @@
 import org.objectweb.asm.ClassWriter;
 
 /** Manages the creation and IO stream for nest-companion classes. */
-public class NestDigest implements TypeMappable<NestDigest> {
+@AutoValue
+public abstract class NestDigest implements TypeMappable<NestDigest> {
 
-  private final ClassMemberRecord classMemberRecord;
-  private final ClassAttributeRecord classAttributeRecord;
-  private final Map<ClassName, ClassName> nestCompanionToHostMap;
+  public abstract ClassMemberRecord classMemberRecord();
+
+  public abstract ClassAttributeRecord classAttributeRecord();
+
+  @Memoized
+  ImmutableList<ClassName> nestHostsWithCompanion() {
+    return classMemberRecord().findAllConstructorMemberKeys().stream()
+        .map(
+            constructor -> nestHost(constructor.owner(), classAttributeRecord(), ImmutableMap.of()))
+        .flatMap(Streams::stream)
+        .distinct()
+        .collect(toImmutableList());
+  }
+
+  @Memoized
+  ImmutableMap<ClassName, ClassName> nestCompanionToHostMap() {
+    return nestHostsWithCompanion().stream()
+        .collect(
+            toImmutableMap(
+                nestHost -> nestHost.innerClass(NEST_COMPANION_CLASS_SIMPLE_NAME),
+                nestHost -> nestHost));
+  }
 
   /**
    * A map from the class binary names of nest hosts to the associated class writer of the nest's
    * companion.
    */
-  private ImmutableMap<ClassName, ClassWriter> companionWriters;
-
-  public static NestDigest create(
-      ClassMemberRecord classMemberRecord, ClassAttributeRecord classAttributeRecord) {
-    return new NestDigest(
-        classMemberRecord, classAttributeRecord, new HashMap<>(), /* companionWriters= */ null);
+  @Memoized
+  ImmutableMap<ClassName, ClassWriter> companionWriters() {
+    return nestHostsWithCompanion().stream()
+        .collect(
+            toImmutableMap(
+                nestHost -> nestHost, nestHost -> new ClassWriter(ClassWriter.COMPUTE_MAXS)));
   }
 
-  private NestDigest(
-      ClassMemberRecord classMemberRecord,
-      ClassAttributeRecord classAttributeRecord,
-      Map<ClassName, ClassName> nestCompanionToHostMap,
-      ImmutableMap<ClassName, ClassWriter> companionWriters) {
-    this.classMemberRecord = classMemberRecord;
-    this.classAttributeRecord = classAttributeRecord;
-    this.nestCompanionToHostMap = nestCompanionToHostMap;
-    this.companionWriters = companionWriters;
-  }
-
-  /**
-   * Generates the nest companion class writers. The nest companion classes will be generated as the
-   * last placeholder class type for the synthetic constructor, whose originating constructor has
-   * any invocation in other classes in nest.
-   */
-  void prepareCompanionClasses() {
-    ImmutableList<ClassName> nestHostsWithCompanion =
-        classMemberRecord.findAllConstructorMemberKeys().stream()
-            .map(
-                constructor ->
-                    nestHost(constructor.owner(), classAttributeRecord, nestCompanionToHostMap))
-            .flatMap(Streams::stream)
-            .distinct()
-            .collect(toImmutableList());
-    ImmutableMap.Builder<ClassName, ClassWriter> companionWriterBuilders = ImmutableMap.builder();
-    for (ClassName nestHost : nestHostsWithCompanion) {
-      ClassName nestCompanion = nestHost.innerClass(NEST_COMPANION_CLASS_SIMPLE_NAME);
-      nestCompanionToHostMap.put(nestCompanion, nestHost);
-      companionWriterBuilders.put(nestHost, new ClassWriter(ClassWriter.COMPUTE_MAXS));
-    }
-    companionWriters = companionWriterBuilders.build();
+  public static NestDigestBuilder builder() {
+    return new AutoValue_NestDigest.Builder();
   }
 
   public boolean hasAnyTrackingReason(ClassMemberKey<?> classMemberKey) {
-    return classMemberRecord.hasTrackingReason(classMemberKey);
+    return classMemberRecord().hasTrackingReason(classMemberKey);
   }
 
   public boolean hasAnyUse(ClassMemberKey<?> classMemberKey, MemberUseKind useKind) {
@@ -99,7 +91,7 @@
   }
 
   public ImmutableList<MemberUseKind> findAllMemberUseKinds(ClassMemberKey<?> classMemberKey) {
-    return classMemberRecord.findAllMemberUseKind(classMemberKey);
+    return classMemberRecord().findAllMemberUseKind(classMemberKey);
   }
 
   /**
@@ -110,8 +102,8 @@
    */
   public Optional<ClassName> nestHost(ClassName className) {
     // Ensures prepareCompanionClasses has been executed.
-    checkNotNull(companionWriters);
-    return nestHost(className, classAttributeRecord, nestCompanionToHostMap);
+    checkNotNull(companionWriters());
+    return nestHost(className, classAttributeRecord(), nestCompanionToHostMap());
   }
 
   /**
@@ -163,7 +155,7 @@
    */
   @Nullable
   public ClassWriter getCompanionClassWriter(ClassName className) {
-    return nestHost(className).map(nestHost -> companionWriters.get(nestHost)).orElse(null);
+    return nestHost(className).map(nestHost -> companionWriters().get(nestHost)).orElse(null);
   }
 
   /** Gets all nest companion classes required to be generated. */
@@ -172,7 +164,7 @@
   }
 
   public ImmutableList<ClassName> getAllCompanionClasses() {
-    return companionWriters.keySet().stream().map(this::nestCompanion).collect(toImmutableList());
+    return companionWriters().keySet().stream().map(this::nestCompanion).collect(toImmutableList());
   }
 
   /** Gets all nest companion files required to be generated. */
@@ -194,16 +186,26 @@
     checkNotNull(
         companionClassWriter,
         "Expected companion class (%s) to be present in (%s)",
-        companionWriters);
+        companionWriters());
     return new ByteArrayInputStream(companionClassWriter.toByteArray());
   }
 
   @Override
   public NestDigest acceptTypeMapper(TypeMapper typeMapper) {
-    return new NestDigest(
-        classMemberRecord.acceptTypeMapper(typeMapper),
-        classAttributeRecord.acceptTypeMapper(typeMapper),
-        typeMapper.mapMutable(nestCompanionToHostMap),
-        typeMapper.mapKey(companionWriters));
+    return NestDigest.builder()
+        .setClassMemberRecord(classMemberRecord().acceptTypeMapper(typeMapper))
+        .setClassAttributeRecord(classAttributeRecord().acceptTypeMapper(typeMapper))
+        .build();
+  }
+
+  /** The builder class for {@link NestDigest}. */
+  @AutoValue.Builder
+  public abstract static class NestDigestBuilder {
+
+    public abstract NestDigestBuilder setClassMemberRecord(ClassMemberRecord value);
+
+    public abstract NestDigestBuilder setClassAttributeRecord(ClassAttributeRecord value);
+
+    public abstract NestDigest build();
   }
 }