Support parsing machine specification

Bug: b/238987119
Change-Id: I83c7f8126ebe15f3b78b6d112748fce3dbbcad0c
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/humanspecification/HumanDesugaredLibrarySpecificationParser.java b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/humanspecification/HumanDesugaredLibrarySpecificationParser.java
index 1aa5f62..5f8e35a 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/humanspecification/HumanDesugaredLibrarySpecificationParser.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/humanspecification/HumanDesugaredLibrarySpecificationParser.java
@@ -13,8 +13,8 @@
 import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.ir.desugar.desugaredlibrary.TopLevelFlagsBuilder;
-import com.android.tools.r8.ir.desugar.desugaredlibrary.humanspecification.memberparser.HumanFieldParser;
-import com.android.tools.r8.ir.desugar.desugaredlibrary.humanspecification.memberparser.HumanMethodParser;
+import com.android.tools.r8.ir.desugar.desugaredlibrary.memberparser.HumanFieldParser;
+import com.android.tools.r8.ir.desugar.desugaredlibrary.memberparser.HumanMethodParser;
 import com.android.tools.r8.origin.Origin;
 import com.android.tools.r8.utils.AndroidApiLevel;
 import com.android.tools.r8.utils.DescriptorUtils;
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/machinespecification/CustomConversionDescriptor.java b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/machinespecification/CustomConversionDescriptor.java
index 6921202..6579f8f 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/machinespecification/CustomConversionDescriptor.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/machinespecification/CustomConversionDescriptor.java
@@ -5,6 +5,7 @@
 package com.android.tools.r8.ir.desugar.desugaredlibrary.machinespecification;
 
 import com.android.tools.r8.graph.DexMethod;
+import java.util.Objects;
 
 public class CustomConversionDescriptor implements SpecificationDescriptor {
   private final DexMethod to;
@@ -30,4 +31,21 @@
       MultiAPILevelMachineDesugaredLibrarySpecificationJsonExporter exporter) {
     return exporter.exportCustomConversionDescriptor(this);
   }
+
+  @Override
+  public boolean equals(Object o) {
+    if (this == o) {
+      return true;
+    }
+    if (!(o instanceof CustomConversionDescriptor)) {
+      return false;
+    }
+    CustomConversionDescriptor that = (CustomConversionDescriptor) o;
+    return to == that.to && from == that.from;
+  }
+
+  @Override
+  public int hashCode() {
+    return Objects.hash(to, from);
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/machinespecification/DerivedMethod.java b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/machinespecification/DerivedMethod.java
index 88aec73..dde1f48 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/machinespecification/DerivedMethod.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/machinespecification/DerivedMethod.java
@@ -9,6 +9,7 @@
 import com.android.tools.r8.graph.DexString;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.synthesis.SyntheticNaming.SyntheticKind;
+import java.util.Objects;
 
 /**
  * A derived method is: - if the holderKind is null, a normal dexMethod; - if the holderKind is
@@ -55,4 +56,21 @@
       MultiAPILevelMachineDesugaredLibrarySpecificationJsonExporter exporter) {
     return exporter.exportDerivedMethod(this);
   }
+
+  @Override
+  public boolean equals(Object o) {
+    if (this == o) {
+      return true;
+    }
+    if (!(o instanceof DerivedMethod)) {
+      return false;
+    }
+    DerivedMethod that = (DerivedMethod) o;
+    return method == that.method && Objects.equals(holderKind, that.holderKind);
+  }
+
+  @Override
+  public int hashCode() {
+    return Objects.hash(method, holderKind);
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/machinespecification/EmulatedDispatchMethodDescriptor.java b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/machinespecification/EmulatedDispatchMethodDescriptor.java
index ec81273..9737d09 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/machinespecification/EmulatedDispatchMethodDescriptor.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/machinespecification/EmulatedDispatchMethodDescriptor.java
@@ -6,6 +6,7 @@
 
 import com.android.tools.r8.graph.DexType;
 import java.util.LinkedHashMap;
+import java.util.Objects;
 
 public class EmulatedDispatchMethodDescriptor implements SpecificationDescriptor {
 
@@ -73,4 +74,24 @@
       MultiAPILevelMachineDesugaredLibrarySpecificationJsonExporter exporter) {
     return exporter.exportEmulatedDispatchMethodDescriptor(this);
   }
+
+  @Override
+  public boolean equals(Object o) {
+    if (this == o) {
+      return true;
+    }
+    if (!(o instanceof EmulatedDispatchMethodDescriptor)) {
+      return false;
+    }
+    EmulatedDispatchMethodDescriptor that = (EmulatedDispatchMethodDescriptor) o;
+    return Objects.equals(interfaceMethod, that.interfaceMethod)
+        && Objects.equals(emulatedDispatchMethod, that.emulatedDispatchMethod)
+        && Objects.equals(forwardingMethod, that.forwardingMethod)
+        && Objects.equals(dispatchCases, that.dispatchCases);
+  }
+
+  @Override
+  public int hashCode() {
+    return Objects.hash(interfaceMethod, emulatedDispatchMethod, forwardingMethod, dispatchCases);
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/machinespecification/EmulatedInterfaceDescriptor.java b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/machinespecification/EmulatedInterfaceDescriptor.java
index bd4ffb1..fb96fe6 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/machinespecification/EmulatedInterfaceDescriptor.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/machinespecification/EmulatedInterfaceDescriptor.java
@@ -7,6 +7,7 @@
 import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.graph.DexType;
 import java.util.Map;
+import java.util.Objects;
 
 public class EmulatedInterfaceDescriptor implements SpecificationDescriptor {
   private final DexType rewrittenType;
@@ -31,4 +32,21 @@
       MultiAPILevelMachineDesugaredLibrarySpecificationJsonExporter exporter) {
     return exporter.exportEmulatedInterfaceDescriptor(this);
   }
+
+  @Override
+  public boolean equals(Object o) {
+    if (this == o) {
+      return true;
+    }
+    if (!(o instanceof EmulatedInterfaceDescriptor)) {
+      return false;
+    }
+    EmulatedInterfaceDescriptor that = (EmulatedInterfaceDescriptor) o;
+    return rewrittenType == that.rewrittenType && emulatedMethods.equals(that.emulatedMethods);
+  }
+
+  @Override
+  public int hashCode() {
+    return Objects.hash(rewrittenType, emulatedMethods);
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/machinespecification/MachineDesugaredLibrarySpecification.java b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/machinespecification/MachineDesugaredLibrarySpecification.java
index 4aa99b6..0d9ef85 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/machinespecification/MachineDesugaredLibrarySpecification.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/machinespecification/MachineDesugaredLibrarySpecification.java
@@ -4,20 +4,24 @@
 
 package com.android.tools.r8.ir.desugar.desugaredlibrary.machinespecification;
 
+import com.android.tools.r8.graph.DexApplication;
 import com.android.tools.r8.graph.DexField;
 import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.graph.DexReference;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.graph.FieldAccessFlags;
 import com.android.tools.r8.graph.MethodAccessFlags;
+import com.android.tools.r8.ir.desugar.desugaredlibrary.DesugaredLibrarySpecification;
 import com.android.tools.r8.utils.AndroidApiLevel;
 import com.android.tools.r8.utils.SemanticVersion;
+import com.android.tools.r8.utils.Timing;
+import java.io.IOException;
 import java.util.List;
 import java.util.Map;
 import java.util.Set;
 import java.util.function.Consumer;
 
-public class MachineDesugaredLibrarySpecification {
+public class MachineDesugaredLibrarySpecification implements DesugaredLibrarySpecification {
 
   private final boolean libraryCompilation;
   private final MachineTopLevelFlags topLevelFlags;
@@ -60,6 +64,14 @@
     return libraryCompilation;
   }
 
+  public MachineTopLevelFlags getTopLevelFlags() {
+    return topLevelFlags;
+  }
+
+  public MachineRewritingFlags getRewritingFlags() {
+    return rewritingFlags;
+  }
+
   public AndroidApiLevel getRequiredCompilationAPILevel() {
     return topLevelFlags.getRequiredCompilationAPILevel();
   }
@@ -208,6 +220,12 @@
     return topLevelFlags.getRequiredCompilationAPILevel();
   }
 
+  @Override
+  public MachineDesugaredLibrarySpecification toMachineSpecification(
+      DexApplication app, Timing timing) throws IOException {
+    return this;
+  }
+
   public boolean requiresTypeRewriting() {
     return !getRewriteType().isEmpty() || !getRewriteDerivedTypeOnly().isEmpty();
   }
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/machinespecification/MachineDesugaredLibrarySpecificationParser.java b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/machinespecification/MachineDesugaredLibrarySpecificationParser.java
new file mode 100644
index 0000000..6cdd123
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/machinespecification/MachineDesugaredLibrarySpecificationParser.java
@@ -0,0 +1,462 @@
+// Copyright (c) 2022, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.ir.desugar.desugaredlibrary.machinespecification;
+
+import static com.android.tools.r8.ir.desugar.desugaredlibrary.machinespecification.MachineSpecificationJsonPool.AMEND_LIBRARY_FIELD_KEY;
+import static com.android.tools.r8.ir.desugar.desugaredlibrary.machinespecification.MachineSpecificationJsonPool.AMEND_LIBRARY_METHOD_KEY;
+import static com.android.tools.r8.ir.desugar.desugaredlibrary.machinespecification.MachineSpecificationJsonPool.API_GENERIC_TYPES_CONVERSION_KEY;
+import static com.android.tools.r8.ir.desugar.desugaredlibrary.machinespecification.MachineSpecificationJsonPool.API_LEVEL_BELOW_OR_EQUAL_KEY;
+import static com.android.tools.r8.ir.desugar.desugaredlibrary.machinespecification.MachineSpecificationJsonPool.API_LEVEL_GREATER_OR_EQUAL_KEY;
+import static com.android.tools.r8.ir.desugar.desugaredlibrary.machinespecification.MachineSpecificationJsonPool.COMMON_FLAGS_KEY;
+import static com.android.tools.r8.ir.desugar.desugaredlibrary.machinespecification.MachineSpecificationJsonPool.COVARIANT_RETARGET_KEY;
+import static com.android.tools.r8.ir.desugar.desugaredlibrary.machinespecification.MachineSpecificationJsonPool.CUSTOM_CONVERSION_KEY;
+import static com.android.tools.r8.ir.desugar.desugaredlibrary.machinespecification.MachineSpecificationJsonPool.DONT_RETARGET_KEY;
+import static com.android.tools.r8.ir.desugar.desugaredlibrary.machinespecification.MachineSpecificationJsonPool.EMULATED_INTERFACE_KEY;
+import static com.android.tools.r8.ir.desugar.desugaredlibrary.machinespecification.MachineSpecificationJsonPool.EMULATED_VIRTUAL_RETARGET_KEY;
+import static com.android.tools.r8.ir.desugar.desugaredlibrary.machinespecification.MachineSpecificationJsonPool.EMULATED_VIRTUAL_RETARGET_THROUGH_EMULATED_INTERFACE_KEY;
+import static com.android.tools.r8.ir.desugar.desugaredlibrary.machinespecification.MachineSpecificationJsonPool.IDENTIFIER_KEY;
+import static com.android.tools.r8.ir.desugar.desugaredlibrary.machinespecification.MachineSpecificationJsonPool.LEGACY_BACKPORT_KEY;
+import static com.android.tools.r8.ir.desugar.desugaredlibrary.machinespecification.MachineSpecificationJsonPool.LIBRARY_FLAGS_KEY;
+import static com.android.tools.r8.ir.desugar.desugaredlibrary.machinespecification.MachineSpecificationJsonPool.MAINTAIN_TYPE_KEY;
+import static com.android.tools.r8.ir.desugar.desugaredlibrary.machinespecification.MachineSpecificationJsonPool.NON_EMULATED_VIRTUAL_RETARGET_KEY;
+import static com.android.tools.r8.ir.desugar.desugaredlibrary.machinespecification.MachineSpecificationJsonPool.PACKAGE_MAP_KEY;
+import static com.android.tools.r8.ir.desugar.desugaredlibrary.machinespecification.MachineSpecificationJsonPool.PROGRAM_FLAGS_KEY;
+import static com.android.tools.r8.ir.desugar.desugaredlibrary.machinespecification.MachineSpecificationJsonPool.REQUIRED_COMPILATION_API_LEVEL_KEY;
+import static com.android.tools.r8.ir.desugar.desugaredlibrary.machinespecification.MachineSpecificationJsonPool.REWRITE_DERIVED_TYPE_ONLY_KEY;
+import static com.android.tools.r8.ir.desugar.desugaredlibrary.machinespecification.MachineSpecificationJsonPool.REWRITE_TYPE_KEY;
+import static com.android.tools.r8.ir.desugar.desugaredlibrary.machinespecification.MachineSpecificationJsonPool.SHRINKER_CONFIG_KEY;
+import static com.android.tools.r8.ir.desugar.desugaredlibrary.machinespecification.MachineSpecificationJsonPool.STATIC_FIELD_RETARGET_KEY;
+import static com.android.tools.r8.ir.desugar.desugaredlibrary.machinespecification.MachineSpecificationJsonPool.STATIC_RETARGET_KEY;
+import static com.android.tools.r8.ir.desugar.desugaredlibrary.machinespecification.MachineSpecificationJsonPool.SUPPORT_ALL_CALLBACKS_FROM_LIBRARY_KEY;
+import static com.android.tools.r8.ir.desugar.desugaredlibrary.machinespecification.MachineSpecificationJsonPool.SYNTHESIZED_LIBRARY_CLASSES_PACKAGE_PREFIX_KEY;
+import static com.android.tools.r8.ir.desugar.desugaredlibrary.machinespecification.MachineSpecificationJsonPool.WRAPPER_KEY;
+
+import com.android.tools.r8.StringResource;
+import com.android.tools.r8.graph.DexField;
+import com.android.tools.r8.graph.DexItemFactory;
+import com.android.tools.r8.graph.DexMethod;
+import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.ir.desugar.desugaredlibrary.memberparser.MachineFieldParser;
+import com.android.tools.r8.ir.desugar.desugaredlibrary.memberparser.MachineMethodParser;
+import com.android.tools.r8.origin.Origin;
+import com.android.tools.r8.synthesis.SyntheticNaming;
+import com.android.tools.r8.synthesis.SyntheticNaming.SyntheticKind;
+import com.android.tools.r8.utils.AndroidApiLevel;
+import com.android.tools.r8.utils.DescriptorUtils;
+import com.android.tools.r8.utils.ExceptionDiagnostic;
+import com.android.tools.r8.utils.Reporter;
+import com.android.tools.r8.utils.StringDiagnostic;
+import com.google.common.collect.ImmutableBiMap;
+import com.google.common.collect.ImmutableList;
+import com.google.gson.JsonArray;
+import com.google.gson.JsonElement;
+import com.google.gson.JsonObject;
+import com.google.gson.JsonParser;
+import java.util.ArrayList;
+import java.util.IdentityHashMap;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+
+public class MachineDesugaredLibrarySpecificationParser {
+
+  private static final String ERROR_MESSAGE_PREFIX = "Invalid desugared library specification: ";
+
+  private final DexItemFactory dexItemFactory;
+  private final MachineMethodParser methodParser;
+  private final MachineFieldParser fieldParser;
+  private final Reporter reporter;
+  private final boolean libraryCompilation;
+  private final int minAPILevel;
+  private final SyntheticNaming syntheticNaming;
+
+  private Origin origin;
+  private JsonObject jsonConfig;
+  private Map<String, String> packageMap;
+
+  public MachineDesugaredLibrarySpecificationParser(
+      DexItemFactory dexItemFactory,
+      Reporter reporter,
+      boolean libraryCompilation,
+      int minAPILevel,
+      SyntheticNaming syntheticNaming) {
+    this.dexItemFactory = dexItemFactory;
+    this.methodParser = new MachineMethodParser(dexItemFactory, this::stringDescriptorToDexType);
+    this.fieldParser = new MachineFieldParser(dexItemFactory, this::stringDescriptorToDexType);
+    this.reporter = reporter;
+    this.minAPILevel = minAPILevel;
+    this.syntheticNaming = syntheticNaming;
+    this.libraryCompilation = libraryCompilation;
+  }
+
+  public DexItemFactory dexItemFactory() {
+    return dexItemFactory;
+  }
+
+  public Reporter reporter() {
+    return reporter;
+  }
+
+  public JsonObject getJsonConfig() {
+    return jsonConfig;
+  }
+
+  public Origin getOrigin() {
+    assert origin != null;
+    return origin;
+  }
+
+  JsonElement required(JsonObject json, String key) {
+    if (!json.has(key)) {
+      throw reporter.fatalError(
+          new StringDiagnostic(
+              "Invalid desugared library configuration. Expected required key '" + key + "'",
+              origin));
+    }
+    return json.get(key);
+  }
+
+  public MachineDesugaredLibrarySpecification parse(StringResource stringResource) {
+    String jsonConfigString = parseJson(stringResource);
+    return parse(origin, jsonConfigString, jsonConfig);
+  }
+
+  public MachineDesugaredLibrarySpecification parse(
+      Origin origin, String jsonConfigString, JsonObject jsonConfig) {
+    this.origin = origin;
+    this.jsonConfig = jsonConfig;
+    MachineTopLevelFlags topLevelFlags = parseTopLevelFlags(jsonConfigString);
+    parsePackageMap();
+    MachineRewritingFlags rewritingFlags = parseRewritingFlags();
+    MachineDesugaredLibrarySpecification config =
+        new MachineDesugaredLibrarySpecification(libraryCompilation, topLevelFlags, rewritingFlags);
+    this.origin = null;
+    return config;
+  }
+
+  private void parsePackageMap() {
+    JsonObject packageMapJson = required(jsonConfig, PACKAGE_MAP_KEY).getAsJsonObject();
+    ImmutableBiMap.Builder<String, String> builder = ImmutableBiMap.builder();
+    packageMapJson.entrySet().forEach((e) -> builder.put(e.getValue().getAsString(), e.getKey()));
+    packageMap = builder.build();
+  }
+
+  String parseJson(StringResource stringResource) {
+    setOrigin(stringResource);
+    String jsonConfigString;
+    try {
+      jsonConfigString = stringResource.getString();
+      JsonParser parser = new JsonParser();
+      jsonConfig = parser.parse(jsonConfigString).getAsJsonObject();
+    } catch (Exception e) {
+      throw reporter.fatalError(new ExceptionDiagnostic(e, origin));
+    }
+    return jsonConfigString;
+  }
+
+  void setOrigin(StringResource stringResource) {
+    origin = stringResource.getOrigin();
+    assert origin != null;
+  }
+
+  private MachineRewritingFlags parseRewritingFlags() {
+    MachineRewritingFlags.Builder builder = MachineRewritingFlags.builder();
+    JsonElement commonFlags = required(jsonConfig, COMMON_FLAGS_KEY);
+    JsonElement libraryFlags = required(jsonConfig, LIBRARY_FLAGS_KEY);
+    JsonElement programFlags = required(jsonConfig, PROGRAM_FLAGS_KEY);
+    parseFlagsList(commonFlags.getAsJsonArray(), builder);
+    parseFlagsList(
+        libraryCompilation ? libraryFlags.getAsJsonArray() : programFlags.getAsJsonArray(),
+        builder);
+    return builder.build();
+  }
+
+  MachineTopLevelFlags parseTopLevelFlags(String jsonConfigString) {
+    String identifier = required(jsonConfig, IDENTIFIER_KEY).getAsString();
+    String prefix =
+        required(jsonConfig, SYNTHESIZED_LIBRARY_CLASSES_PACKAGE_PREFIX_KEY).getAsString();
+    int required_compilation_api_level =
+        required(jsonConfig, REQUIRED_COMPILATION_API_LEVEL_KEY).getAsInt();
+    String keepRules = required(jsonConfig, SHRINKER_CONFIG_KEY).getAsString();
+    boolean supportAllCallbacksFromLibrary =
+        jsonConfig.get(SUPPORT_ALL_CALLBACKS_FROM_LIBRARY_KEY).getAsBoolean();
+
+    return new MachineTopLevelFlags(
+        AndroidApiLevel.getAndroidApiLevel(required_compilation_api_level),
+        prefix,
+        identifier,
+        jsonConfigString,
+        supportAllCallbacksFromLibrary,
+        ImmutableList.of(keepRules));
+  }
+
+  private void parseFlagsList(JsonArray jsonFlags, MachineRewritingFlags.Builder builder) {
+    for (JsonElement jsonFlagSet : jsonFlags) {
+      JsonObject flag = jsonFlagSet.getAsJsonObject();
+      int api_level_below_or_equal = required(flag, API_LEVEL_BELOW_OR_EQUAL_KEY).getAsInt();
+      if (minAPILevel <= api_level_below_or_equal) {
+        if (flag.has(API_LEVEL_GREATER_OR_EQUAL_KEY)) {
+          if (minAPILevel >= flag.get(API_LEVEL_GREATER_OR_EQUAL_KEY).getAsInt()) {
+            parseFlags(flag, builder);
+          }
+        } else {
+          parseFlags(flag, builder);
+        }
+      }
+    }
+  }
+
+  void parseFlags(JsonObject jsonFlagSet, MachineRewritingFlags.Builder builder) {
+    if (jsonFlagSet.has(REWRITE_TYPE_KEY)) {
+      for (Map.Entry<String, JsonElement> rewritePrefix :
+          jsonFlagSet.get(REWRITE_TYPE_KEY).getAsJsonObject().entrySet()) {
+        builder.rewriteType(
+            stringDescriptorToDexType(rewritePrefix.getKey()),
+            stringDescriptorToDexType(rewritePrefix.getValue().getAsString()));
+      }
+    }
+    if (jsonFlagSet.has(MAINTAIN_TYPE_KEY)) {
+      for (JsonElement maintainPrefix : jsonFlagSet.get(MAINTAIN_TYPE_KEY).getAsJsonArray()) {
+        builder.maintainType(stringDescriptorToDexType(maintainPrefix.getAsString()));
+      }
+    }
+    if (jsonFlagSet.has(REWRITE_DERIVED_TYPE_ONLY_KEY)) {
+      for (Map.Entry<String, JsonElement> rewriteDerivedTypeOnly :
+          jsonFlagSet.get(REWRITE_DERIVED_TYPE_ONLY_KEY).getAsJsonObject().entrySet()) {
+        builder.rewriteDerivedTypeOnly(
+            stringDescriptorToDexType(rewriteDerivedTypeOnly.getKey()),
+            stringDescriptorToDexType(rewriteDerivedTypeOnly.getValue().getAsString()));
+      }
+    }
+    if (jsonFlagSet.has(STATIC_FIELD_RETARGET_KEY)) {
+      for (Map.Entry<String, JsonElement> staticFieldRetarget :
+          jsonFlagSet.get(STATIC_FIELD_RETARGET_KEY).getAsJsonObject().entrySet()) {
+        builder.putStaticFieldRetarget(
+            parseField(staticFieldRetarget.getKey()),
+            parseField(staticFieldRetarget.getValue().getAsString()));
+      }
+    }
+    if (jsonFlagSet.has(COVARIANT_RETARGET_KEY)) {
+      for (Map.Entry<String, JsonElement> covariantRetarget :
+          jsonFlagSet.get(COVARIANT_RETARGET_KEY).getAsJsonObject().entrySet()) {
+        builder.putCovariantRetarget(
+            parseMethod(covariantRetarget.getKey()),
+            parseMethod(covariantRetarget.getValue().getAsString()));
+      }
+    }
+    if (jsonFlagSet.has(STATIC_RETARGET_KEY)) {
+      for (Map.Entry<String, JsonElement> staticRetarget :
+          jsonFlagSet.get(STATIC_RETARGET_KEY).getAsJsonObject().entrySet()) {
+        builder.putStaticRetarget(
+            parseMethod(staticRetarget.getKey()),
+            parseMethod(staticRetarget.getValue().getAsString()));
+      }
+    }
+    if (jsonFlagSet.has(NON_EMULATED_VIRTUAL_RETARGET_KEY)) {
+      for (Map.Entry<String, JsonElement> virtualRetarget :
+          jsonFlagSet.get(NON_EMULATED_VIRTUAL_RETARGET_KEY).getAsJsonObject().entrySet()) {
+        builder.putNonEmulatedVirtualRetarget(
+            parseMethod(virtualRetarget.getKey()),
+            parseMethod(virtualRetarget.getValue().getAsString()));
+      }
+    }
+    if (jsonFlagSet.has(EMULATED_VIRTUAL_RETARGET_KEY)) {
+      for (Map.Entry<String, JsonElement> virtualRetarget :
+          jsonFlagSet.get(EMULATED_VIRTUAL_RETARGET_KEY).getAsJsonObject().entrySet()) {
+        builder.putEmulatedVirtualRetarget(
+            parseMethod(virtualRetarget.getKey()),
+            parseEmulatedDispatchDescriptor(virtualRetarget.getValue().getAsJsonArray()));
+      }
+    }
+    if (jsonFlagSet.has(EMULATED_VIRTUAL_RETARGET_THROUGH_EMULATED_INTERFACE_KEY)) {
+      for (Map.Entry<String, JsonElement> virtualRetarget :
+          jsonFlagSet
+              .get(EMULATED_VIRTUAL_RETARGET_THROUGH_EMULATED_INTERFACE_KEY)
+              .getAsJsonObject()
+              .entrySet()) {
+        builder.putEmulatedVirtualRetargetThroughEmulatedInterface(
+            parseMethod(virtualRetarget.getKey()),
+            parseMethod(virtualRetarget.getValue().getAsString()));
+      }
+    }
+    if (jsonFlagSet.has(API_GENERIC_TYPES_CONVERSION_KEY)) {
+      for (Map.Entry<String, JsonElement> apiGenericType :
+          jsonFlagSet.get(API_GENERIC_TYPES_CONVERSION_KEY).getAsJsonObject().entrySet()) {
+        builder.addApiGenericTypesConversion(
+            parseMethod(apiGenericType.getKey()),
+            parseMethodArray(apiGenericType.getValue().getAsJsonArray()));
+      }
+    }
+    if (jsonFlagSet.has(EMULATED_INTERFACE_KEY)) {
+      for (Map.Entry<String, JsonElement> emulatedInterface :
+          jsonFlagSet.get(EMULATED_INTERFACE_KEY).getAsJsonObject().entrySet()) {
+        builder.putEmulatedInterface(
+            stringDescriptorToDexType(emulatedInterface.getKey()),
+            parseEmulatedInterfaceDescriptor(emulatedInterface.getValue().getAsJsonArray()));
+      }
+    }
+    if (jsonFlagSet.has(WRAPPER_KEY)) {
+      for (Map.Entry<String, JsonElement> wrapper :
+          jsonFlagSet.get(WRAPPER_KEY).getAsJsonObject().entrySet()) {
+        builder.addWrapper(
+            stringDescriptorToDexType(wrapper.getKey()),
+            parseWrapperDescriptor(wrapper.getValue().getAsJsonArray()));
+      }
+    }
+    if (jsonFlagSet.has(LEGACY_BACKPORT_KEY)) {
+      for (Map.Entry<String, JsonElement> legacyBackport :
+          jsonFlagSet.get(LEGACY_BACKPORT_KEY).getAsJsonObject().entrySet()) {
+        builder.putLegacyBackport(
+            stringDescriptorToDexType(legacyBackport.getKey()),
+            stringDescriptorToDexType(legacyBackport.getValue().getAsString()));
+      }
+    }
+    if (jsonFlagSet.has(DONT_RETARGET_KEY)) {
+      for (JsonElement dontRetarget : jsonFlagSet.get(DONT_RETARGET_KEY).getAsJsonArray()) {
+        builder.addDontRetarget(stringDescriptorToDexType(dontRetarget.getAsString()));
+      }
+    }
+    if (jsonFlagSet.has(CUSTOM_CONVERSION_KEY)) {
+      for (Map.Entry<String, JsonElement> customConversion :
+          jsonFlagSet.get(CUSTOM_CONVERSION_KEY).getAsJsonObject().entrySet()) {
+        builder.putCustomConversion(
+            stringDescriptorToDexType(customConversion.getKey()),
+            parseCustomConversionDescriptor(customConversion.getValue().getAsJsonArray()));
+      }
+    }
+    if (jsonFlagSet.has(AMEND_LIBRARY_METHOD_KEY)) {
+      JsonArray amendLibraryMember = jsonFlagSet.get(AMEND_LIBRARY_METHOD_KEY).getAsJsonArray();
+      for (JsonElement amend : amendLibraryMember) {
+        methodParser.parseMethod(amend.getAsString());
+        builder.amendLibraryMethod(methodParser.getMethod(), methodParser.getFlags());
+      }
+    }
+    if (jsonFlagSet.has(AMEND_LIBRARY_FIELD_KEY)) {
+      JsonArray amendLibraryMember = jsonFlagSet.get(AMEND_LIBRARY_FIELD_KEY).getAsJsonArray();
+      for (JsonElement amend : amendLibraryMember) {
+        fieldParser.parseField(amend.getAsString());
+        builder.amendLibraryField(fieldParser.getField(), fieldParser.getFlags());
+      }
+    }
+  }
+
+  private CustomConversionDescriptor parseCustomConversionDescriptor(JsonArray jsonArray) {
+    return new CustomConversionDescriptor(
+        parseMethod(jsonArray.get(0).getAsString()), parseMethod(jsonArray.get(1).getAsString()));
+  }
+
+  private WrapperDescriptor parseWrapperDescriptor(JsonArray jsonArray) {
+    List<DexMethod> methods = parseMethodList(jsonArray.get(0).getAsJsonArray());
+    boolean nonPublicAccess = jsonArray.get(1).getAsBoolean();
+    List<DexType> subwrappers = parseTypeList(jsonArray.get(2).getAsJsonArray());
+    return new WrapperDescriptor(methods, subwrappers, nonPublicAccess);
+  }
+
+  private void require(JsonArray jsonArray, int size, String elementString) {
+    if (jsonArray.size() != size) {
+      throw reporter.fatalError(
+          ERROR_MESSAGE_PREFIX + elementString + "(Json array of size " + jsonArray.size() + ")");
+    }
+  }
+
+  private EmulatedInterfaceDescriptor parseEmulatedInterfaceDescriptor(JsonArray jsonArray) {
+    require(jsonArray, 2, "emulated interface descriptor");
+    DexType rewrittenType = stringDescriptorToDexType(jsonArray.get(0).getAsString());
+    Map<DexMethod, EmulatedDispatchMethodDescriptor> methods =
+        parseEmulatedInterfaceMap(jsonArray.get(1).getAsJsonObject());
+    return new EmulatedInterfaceDescriptor(rewrittenType, methods);
+  }
+
+  private Map<DexMethod, EmulatedDispatchMethodDescriptor> parseEmulatedInterfaceMap(
+      JsonObject jsonObject) {
+    Map<DexMethod, EmulatedDispatchMethodDescriptor> map = new IdentityHashMap<>();
+    for (Map.Entry<String, JsonElement> entry : jsonObject.entrySet()) {
+      map.put(
+          parseMethod(entry.getKey()),
+          parseEmulatedDispatchDescriptor(entry.getValue().getAsJsonArray()));
+    }
+    return map;
+  }
+
+  private LinkedHashMap<DexType, DerivedMethod> parseEmulatedDispatchMap(JsonObject jsonObject) {
+    LinkedHashMap<DexType, DerivedMethod> map = new LinkedHashMap<>();
+    for (Map.Entry<String, JsonElement> entry : jsonObject.entrySet()) {
+      map.put(
+          stringDescriptorToDexType(entry.getKey()),
+          parseDerivedMethod(entry.getValue().getAsJsonArray()));
+    }
+    return map;
+  }
+
+  private EmulatedDispatchMethodDescriptor parseEmulatedDispatchDescriptor(JsonArray jsonArray) {
+    require(jsonArray, 4, "emulated dispatch descriptor");
+    DerivedMethod interfaceMethod = parseDerivedMethod(jsonArray.get(0).getAsJsonArray());
+    DerivedMethod emulatedDispatchMethod = parseDerivedMethod(jsonArray.get(1).getAsJsonArray());
+    DerivedMethod forwardingMethod = parseDerivedMethod(jsonArray.get(2).getAsJsonArray());
+    LinkedHashMap<DexType, DerivedMethod> dispatchCases =
+        parseEmulatedDispatchMap(jsonArray.get(3).getAsJsonObject());
+    return new EmulatedDispatchMethodDescriptor(
+        interfaceMethod, emulatedDispatchMethod, forwardingMethod, dispatchCases);
+  }
+
+  private DerivedMethod parseDerivedMethod(JsonArray jsonArray) {
+    require(jsonArray, 2, "derived method");
+    DexMethod dexMethod = parseMethod(jsonArray.get(0).getAsString());
+    int kind = jsonArray.get(1).getAsInt();
+    if (kind == -1) {
+      return new DerivedMethod(dexMethod);
+    }
+    SyntheticKind syntheticKind = syntheticNaming.fromId(kind);
+    return new DerivedMethod(dexMethod, syntheticKind);
+  }
+
+  private List<DexMethod> parseMethodList(JsonArray array) {
+    List<DexMethod> methods = new ArrayList<>();
+    for (JsonElement method : array) {
+      methods.add(parseMethod(method.getAsString()));
+    }
+    return methods;
+  }
+
+  private List<DexType> parseTypeList(JsonArray array) {
+    List<DexType> types = new ArrayList<>();
+    for (JsonElement typeString : array) {
+      types.add(stringDescriptorToDexType(typeString.getAsString()));
+    }
+    return types;
+  }
+
+  private DexMethod[] parseMethodArray(JsonArray array) {
+    DexMethod[] dexMethods = new DexMethod[array.size()];
+    for (int i = 0; i < array.size(); i++) {
+      String str = array.get(i).getAsString();
+      dexMethods[i] = str.isEmpty() ? null : parseMethod(str);
+    }
+    return dexMethods;
+  }
+
+  private DexMethod parseMethod(String signature) {
+    methodParser.parseMethod(signature);
+    return methodParser.getMethod();
+  }
+
+  private DexField parseField(String signature) {
+    fieldParser.parseField(signature);
+    return fieldParser.getField();
+  }
+
+  public DexType stringDescriptorToDexType(String stringClass) {
+    if (stringClass.charAt(1) != '$') {
+      return dexItemFactory.createType(DescriptorUtils.javaTypeToDescriptor(stringClass));
+    }
+    String type = stringClass.substring(2);
+    String prefix = packageMap.get(stringClass.substring(0, 2));
+    if (prefix == null) {
+      throw reporter.fatalError(
+          ERROR_MESSAGE_PREFIX + "Missing package mapping for " + stringClass.substring(0, 2));
+    }
+    return dexItemFactory.createType(DescriptorUtils.javaTypeToDescriptor(prefix + "." + type));
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/machinespecification/MultiAPILevelMachineDesugaredLibrarySpecificationJsonExporter.java b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/machinespecification/MultiAPILevelMachineDesugaredLibrarySpecificationJsonExporter.java
index ce60cb0..e55127c 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/machinespecification/MultiAPILevelMachineDesugaredLibrarySpecificationJsonExporter.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/machinespecification/MultiAPILevelMachineDesugaredLibrarySpecificationJsonExporter.java
@@ -57,7 +57,8 @@
 
   private final DexItemFactory factory;
   private final Map<String, String> packageMap = new TreeMap<>();
-  private static final String chars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
+  private static final String chars =
+      "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789æÆøØ";
   private int next = 0;
 
   public MultiAPILevelMachineDesugaredLibrarySpecificationJsonExporter(DexItemFactory factory) {
@@ -148,7 +149,7 @@
       String key, Map<? extends DexItem, DexMethod[]> map, Map<String, Object> toJson) {
     if (!map.isEmpty()) {
       TreeMap<String, Object> stringMap = new TreeMap<>();
-      map.forEach((k, v) -> stringMap.put(toString(k), methodArraytoJsonStruct(v)));
+      map.forEach((k, v) -> stringMap.put(toString(k), methodArrayToJsonStruct(v)));
       toJson.put(key, stringMap);
     }
   }
@@ -229,10 +230,10 @@
     return stringCol;
   }
 
-  private String[] methodArraytoJsonStruct(DexMethod[] methodArray) {
+  private String[] methodArrayToJsonStruct(DexMethod[] methodArray) {
     String[] strings = new String[methodArray.length];
     for (int i = 0; i < methodArray.length; i++) {
-      strings[i] = toString(methodArray[i]);
+      strings[i] = methodArray[i] == null ? "" : toString(methodArray[i]);
     }
     return strings;
   }
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/machinespecification/WrapperDescriptor.java b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/machinespecification/WrapperDescriptor.java
index 51a7c5a..156547e 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/machinespecification/WrapperDescriptor.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/machinespecification/WrapperDescriptor.java
@@ -7,6 +7,7 @@
 import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.graph.DexType;
 import java.util.List;
+import java.util.Objects;
 
 public class WrapperDescriptor implements SpecificationDescriptor {
   private final List<DexMethod> methods;
@@ -37,4 +38,23 @@
       MultiAPILevelMachineDesugaredLibrarySpecificationJsonExporter exporter) {
     return exporter.exportWrapperDescriptor(this);
   }
+
+  @Override
+  public boolean equals(Object o) {
+    if (this == o) {
+      return true;
+    }
+    if (!(o instanceof WrapperDescriptor)) {
+      return false;
+    }
+    WrapperDescriptor that = (WrapperDescriptor) o;
+    return nonPublicAccess == that.nonPublicAccess
+        && Objects.equals(methods, that.methods)
+        && Objects.equals(subwrappers, that.subwrappers);
+  }
+
+  @Override
+  public int hashCode() {
+    return Objects.hash(methods, subwrappers, nonPublicAccess);
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/humanspecification/memberparser/AbstractFieldParser.java b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/memberparser/AbstractFieldParser.java
similarity index 94%
rename from src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/humanspecification/memberparser/AbstractFieldParser.java
rename to src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/memberparser/AbstractFieldParser.java
index 8062063..ba3a060 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/humanspecification/memberparser/AbstractFieldParser.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/memberparser/AbstractFieldParser.java
@@ -2,7 +2,7 @@
 // for details. All rights reserved. Use of this source code is governed by a
 // BSD-style license that can be found in the LICENSE file.
 
-package com.android.tools.r8.ir.desugar.desugaredlibrary.humanspecification.memberparser;
+package com.android.tools.r8.ir.desugar.desugaredlibrary.memberparser;
 
 import com.android.tools.r8.errors.CompilationError;
 import com.android.tools.r8.graph.DexItemFactory;
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/humanspecification/memberparser/AbstractMemberParser.java b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/memberparser/AbstractMemberParser.java
similarity index 94%
rename from src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/humanspecification/memberparser/AbstractMemberParser.java
rename to src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/memberparser/AbstractMemberParser.java
index 99475b2..1848ed8 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/humanspecification/memberparser/AbstractMemberParser.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/memberparser/AbstractMemberParser.java
@@ -2,7 +2,7 @@
 // for details. All rights reserved. Use of this source code is governed by a
 // BSD-style license that can be found in the LICENSE file.
 
-package com.android.tools.r8.ir.desugar.desugaredlibrary.humanspecification.memberparser;
+package com.android.tools.r8.ir.desugar.desugaredlibrary.memberparser;
 
 import com.android.tools.r8.dex.Constants;
 import com.android.tools.r8.graph.DexItemFactory;
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/humanspecification/memberparser/AbstractMethodParser.java b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/memberparser/AbstractMethodParser.java
similarity index 94%
rename from src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/humanspecification/memberparser/AbstractMethodParser.java
rename to src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/memberparser/AbstractMethodParser.java
index 1afff88..c10882d 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/humanspecification/memberparser/AbstractMethodParser.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/memberparser/AbstractMethodParser.java
@@ -2,7 +2,7 @@
 // for details. All rights reserved. Use of this source code is governed by a
 // BSD-style license that can be found in the LICENSE file.
 
-package com.android.tools.r8.ir.desugar.desugaredlibrary.humanspecification.memberparser;
+package com.android.tools.r8.ir.desugar.desugaredlibrary.memberparser;
 
 import com.android.tools.r8.errors.CompilationError;
 import com.android.tools.r8.graph.DexItemFactory;
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/humanspecification/memberparser/HumanFieldParser.java b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/memberparser/HumanFieldParser.java
similarity index 95%
rename from src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/humanspecification/memberparser/HumanFieldParser.java
rename to src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/memberparser/HumanFieldParser.java
index 9a5a44d..7f1b03f 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/humanspecification/memberparser/HumanFieldParser.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/memberparser/HumanFieldParser.java
@@ -2,7 +2,7 @@
 // for details. All rights reserved. Use of this source code is governed by a
 // BSD-style license that can be found in the LICENSE file.
 
-package com.android.tools.r8.ir.desugar.desugaredlibrary.humanspecification.memberparser;
+package com.android.tools.r8.ir.desugar.desugaredlibrary.memberparser;
 
 import com.android.tools.r8.graph.DexField;
 import com.android.tools.r8.graph.DexItemFactory;
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/humanspecification/memberparser/HumanMethodParser.java b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/memberparser/HumanMethodParser.java
similarity index 95%
rename from src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/humanspecification/memberparser/HumanMethodParser.java
rename to src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/memberparser/HumanMethodParser.java
index d78267b..b7bfd33 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/humanspecification/memberparser/HumanMethodParser.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/memberparser/HumanMethodParser.java
@@ -2,7 +2,7 @@
 // for details. All rights reserved. Use of this source code is governed by a
 // BSD-style license that can be found in the LICENSE file.
 
-package com.android.tools.r8.ir.desugar.desugaredlibrary.humanspecification.memberparser;
+package com.android.tools.r8.ir.desugar.desugaredlibrary.memberparser;
 
 import com.android.tools.r8.graph.DexItemFactory;
 import com.android.tools.r8.graph.DexMethod;
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/memberparser/MachineFieldParser.java b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/memberparser/MachineFieldParser.java
new file mode 100644
index 0000000..002a8d3
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/memberparser/MachineFieldParser.java
@@ -0,0 +1,23 @@
+// Copyright (c) 2022, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.ir.desugar.desugaredlibrary.memberparser;
+
+import com.android.tools.r8.graph.DexItemFactory;
+import com.android.tools.r8.graph.DexType;
+import java.util.function.Function;
+
+public class MachineFieldParser extends HumanFieldParser {
+  private final Function<String, DexType> typeParser;
+
+  public MachineFieldParser(DexItemFactory factory, Function<String, DexType> typeParser) {
+    super(factory);
+    this.typeParser = typeParser;
+  }
+
+  @Override
+  DexType stringTypeToDexType(String stringType) {
+    return typeParser.apply(stringType);
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/memberparser/MachineMethodParser.java b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/memberparser/MachineMethodParser.java
new file mode 100644
index 0000000..6b88d87
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/memberparser/MachineMethodParser.java
@@ -0,0 +1,23 @@
+// Copyright (c) 2022, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.ir.desugar.desugaredlibrary.memberparser;
+
+import com.android.tools.r8.graph.DexItemFactory;
+import com.android.tools.r8.graph.DexType;
+import java.util.function.Function;
+
+public class MachineMethodParser extends HumanMethodParser {
+  private final Function<String, DexType> typeParser;
+
+  public MachineMethodParser(DexItemFactory factory, Function<String, DexType> typeParser) {
+    super(factory);
+    this.typeParser = typeParser;
+  }
+
+  @Override
+  DexType stringTypeToDexType(String stringType) {
+    return typeParser.apply(stringType);
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/specification/ConvertExportReadTest.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/specification/ConvertExportReadTest.java
index c352702..ec8c5f7 100644
--- a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/specification/ConvertExportReadTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/specification/ConvertExportReadTest.java
@@ -4,7 +4,9 @@
 
 package com.android.tools.r8.desugar.desugaredlibrary.specification;
 
+import static org.junit.Assert.assertArrayEquals;
 import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertTrue;
 
 import com.android.tools.r8.StringResource;
@@ -15,6 +17,8 @@
 import com.android.tools.r8.desugar.desugaredlibrary.test.LibraryDesugaringSpecification;
 import com.android.tools.r8.graph.DexApplication;
 import com.android.tools.r8.ir.desugar.desugaredlibrary.ApiLevelRange;
+import com.android.tools.r8.ir.desugar.desugaredlibrary.humanspecification.HumanDesugaredLibrarySpecification;
+import com.android.tools.r8.ir.desugar.desugaredlibrary.humanspecification.HumanDesugaredLibrarySpecificationParser;
 import com.android.tools.r8.ir.desugar.desugaredlibrary.humanspecification.HumanRewritingFlags;
 import com.android.tools.r8.ir.desugar.desugaredlibrary.humanspecification.HumanTopLevelFlags;
 import com.android.tools.r8.ir.desugar.desugaredlibrary.humanspecification.MultiAPILevelHumanDesugaredLibrarySpecification;
@@ -22,11 +26,17 @@
 import com.android.tools.r8.ir.desugar.desugaredlibrary.humanspecification.MultiAPILevelHumanDesugaredLibrarySpecificationParser;
 import com.android.tools.r8.ir.desugar.desugaredlibrary.legacyspecification.MultiAPILevelLegacyDesugaredLibrarySpecification;
 import com.android.tools.r8.ir.desugar.desugaredlibrary.legacyspecification.MultiAPILevelLegacyDesugaredLibrarySpecificationParser;
+import com.android.tools.r8.ir.desugar.desugaredlibrary.machinespecification.MachineDesugaredLibrarySpecification;
+import com.android.tools.r8.ir.desugar.desugaredlibrary.machinespecification.MachineDesugaredLibrarySpecificationParser;
+import com.android.tools.r8.ir.desugar.desugaredlibrary.machinespecification.MachineRewritingFlags;
+import com.android.tools.r8.ir.desugar.desugaredlibrary.machinespecification.MachineTopLevelFlags;
 import com.android.tools.r8.ir.desugar.desugaredlibrary.machinespecification.MultiAPILevelMachineDesugaredLibrarySpecification;
 import com.android.tools.r8.ir.desugar.desugaredlibrary.machinespecification.MultiAPILevelMachineDesugaredLibrarySpecificationJsonExporter;
 import com.android.tools.r8.ir.desugar.desugaredlibrary.specificationconversion.HumanToMachineSpecificationConverter;
 import com.android.tools.r8.ir.desugar.desugaredlibrary.specificationconversion.LegacyToHumanSpecificationConverter;
 import com.android.tools.r8.origin.Origin;
+import com.android.tools.r8.synthesis.SyntheticNaming;
+import com.android.tools.r8.utils.AndroidApiLevel;
 import com.android.tools.r8.utils.Box;
 import com.android.tools.r8.utils.InternalOptions;
 import com.android.tools.r8.utils.Timing;
@@ -50,7 +60,7 @@
   }
 
   @Test
-  public void testMultiLevel() throws IOException {
+  public void testMultiLevelLegacy() throws IOException {
     Assume.assumeTrue(ToolHelper.isLocalDevelopment());
 
     LibraryDesugaringSpecification legacySpec = LibraryDesugaringSpecification.JDK8;
@@ -87,6 +97,67 @@
         converter2.convertAllAPILevels(humanSpec2, app);
     MultiAPILevelMachineDesugaredLibrarySpecificationJsonExporter.export(
         machineSpec1, (string, handler) -> json2.set(string), options.dexItemFactory());
+
+    MachineDesugaredLibrarySpecification machineSpecParsed =
+        new MachineDesugaredLibrarySpecificationParser(
+                options.dexItemFactory(),
+                options.reporter,
+                true,
+                AndroidApiLevel.B.getLevel(),
+                new SyntheticNaming())
+            .parse(StringResource.fromString(json2.get(), Origin.unknown()));
+    assertFalse(machineSpecParsed.getRewriteType().isEmpty());
+  }
+
+  @Test
+  public void testSingleLevel() throws IOException {
+    Assume.assumeTrue(ToolHelper.isLocalDevelopment());
+
+    LibraryDesugaringSpecification humanSpec = LibraryDesugaringSpecification.JDK11_PATH;
+
+    InternalOptions options = new InternalOptions();
+
+    DexApplication app = humanSpec.getAppForTesting(options, true);
+
+    MultiAPILevelHumanDesugaredLibrarySpecification humanSpec2 =
+        new MultiAPILevelHumanDesugaredLibrarySpecificationParser(
+                options.dexItemFactory(), options.reporter)
+            .parseMultiLevelConfiguration(StringResource.fromFile(humanSpec.getSpecification()));
+
+    Box<String> json2 = new Box<>();
+    HumanToMachineSpecificationConverter converter2 =
+        new HumanToMachineSpecificationConverter(Timing.empty());
+    MultiAPILevelMachineDesugaredLibrarySpecification machineSpec1 =
+        converter2.convertAllAPILevels(humanSpec2, app);
+    MultiAPILevelMachineDesugaredLibrarySpecificationJsonExporter.export(
+        machineSpec1, (string, handler) -> json2.set(string), options.dexItemFactory());
+
+    for (AndroidApiLevel api :
+        new AndroidApiLevel[] {AndroidApiLevel.B, AndroidApiLevel.N, AndroidApiLevel.O}) {
+      MachineDesugaredLibrarySpecification machineSpecParsed =
+          new MachineDesugaredLibrarySpecificationParser(
+                  options.dexItemFactory(),
+                  options.reporter,
+                  true,
+                  api.getLevel(),
+                  new SyntheticNaming())
+              .parse(StringResource.fromString(json2.get(), Origin.unknown()));
+
+      HumanDesugaredLibrarySpecification humanSpecB =
+          new HumanDesugaredLibrarySpecificationParser(
+                  options.dexItemFactory(), options.reporter, true, api.getLevel())
+              .parse(StringResource.fromFile(humanSpec.getSpecification()));
+      MachineDesugaredLibrarySpecification machineSpecConverted =
+          humanSpecB.toMachineSpecification(app, Timing.empty());
+
+      assertSpecEquals(machineSpecConverted, machineSpecParsed);
+    }
+  }
+
+  private void assertSpecEquals(
+      MachineDesugaredLibrarySpecification spec1, MachineDesugaredLibrarySpecification spec2) {
+    assertTopLevelFlagsEquals(spec1.getTopLevelFlags(), spec2.getTopLevelFlags());
+    assertFlagsEquals(spec1.getRewritingFlags(), spec2.getRewritingFlags());
   }
 
   private void assertSpecEquals(
@@ -109,6 +180,46 @@
   }
 
   private void assertFlagsEquals(
+      MachineRewritingFlags rewritingFlags1, MachineRewritingFlags rewritingFlags2) {
+    assertEquals(rewritingFlags1.getRewriteType(), rewritingFlags2.getRewriteType());
+    assertEquals(rewritingFlags1.getMaintainType(), rewritingFlags2.getMaintainType());
+    assertEquals(
+        rewritingFlags1.getRewriteDerivedTypeOnly(), rewritingFlags2.getRewriteDerivedTypeOnly());
+    assertEquals(
+        rewritingFlags1.getStaticFieldRetarget(), rewritingFlags2.getStaticFieldRetarget());
+    assertEquals(rewritingFlags1.getCovariantRetarget(), rewritingFlags2.getCovariantRetarget());
+    assertEquals(rewritingFlags1.getStaticRetarget(), rewritingFlags2.getStaticRetarget());
+    assertEquals(
+        rewritingFlags1.getNonEmulatedVirtualRetarget(),
+        rewritingFlags2.getNonEmulatedVirtualRetarget());
+    assertEquals(
+        rewritingFlags1.getEmulatedVirtualRetarget(), rewritingFlags2.getEmulatedVirtualRetarget());
+    assertEquals(
+        rewritingFlags1.getEmulatedVirtualRetargetThroughEmulatedInterface(),
+        rewritingFlags2.getEmulatedVirtualRetargetThroughEmulatedInterface());
+    assertEquals(
+        rewritingFlags1.getApiGenericConversion().keySet(),
+        rewritingFlags2.getApiGenericConversion().keySet());
+    rewritingFlags1
+        .getApiGenericConversion()
+        .keySet()
+        .forEach(
+            k ->
+                assertArrayEquals(
+                    rewritingFlags1.getApiGenericConversion().get(k),
+                    rewritingFlags2.getApiGenericConversion().get(k)));
+    assertEquals(
+        rewritingFlags1.getEmulatedInterfaces().keySet(),
+        rewritingFlags2.getEmulatedInterfaces().keySet());
+    assertEquals(rewritingFlags1.getWrappers().keySet(), rewritingFlags2.getWrappers().keySet());
+    assertEquals(rewritingFlags1.getLegacyBackport(), rewritingFlags2.getLegacyBackport());
+    assertEquals(rewritingFlags1.getDontRetarget(), rewritingFlags2.getDontRetarget());
+    assertEquals(rewritingFlags1.getCustomConversions(), rewritingFlags2.getCustomConversions());
+    assertEquals(rewritingFlags1.getAmendLibraryMethod(), rewritingFlags2.getAmendLibraryMethod());
+    assertEquals(rewritingFlags1.getAmendLibraryField(), rewritingFlags2.getAmendLibraryField());
+  }
+
+  private void assertFlagsEquals(
       HumanRewritingFlags humanRewritingFlags1, HumanRewritingFlags humanRewritingFlags2) {
     assertEquals(humanRewritingFlags1.getRewritePrefix(), humanRewritingFlags2.getRewritePrefix());
     assertEquals(
@@ -136,6 +247,20 @@
   }
 
   private void assertTopLevelFlagsEquals(
+      MachineTopLevelFlags topLevelFlags1, MachineTopLevelFlags topLevelFlags2) {
+    String kr1 = String.join("\n", topLevelFlags1.getExtraKeepRules());
+    String kr2 = String.join("\n", topLevelFlags2.getExtraKeepRules());
+    assertEquals(kr1, kr2);
+    assertEquals(topLevelFlags1.getIdentifier(), topLevelFlags2.getIdentifier());
+    assertEquals(
+        topLevelFlags1.getRequiredCompilationAPILevel().getLevel(),
+        topLevelFlags2.getRequiredCompilationAPILevel().getLevel());
+    assertEquals(
+        topLevelFlags1.getSynthesizedLibraryClassesPackagePrefix(),
+        topLevelFlags2.getSynthesizedLibraryClassesPackagePrefix());
+  }
+
+  private void assertTopLevelFlagsEquals(
       HumanTopLevelFlags topLevelFlags1, HumanTopLevelFlags topLevelFlags2) {
     assertEquals(topLevelFlags1.getExtraKeepRules(), topLevelFlags2.getExtraKeepRules());
     assertEquals(topLevelFlags1.getIdentifier(), topLevelFlags2.getIdentifier());