Legacy to Human specification conversion

- desugared library

Bug: 184026720
Change-Id: I51fb9007378210f1bff02d89f5bfde60312926bd
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/humanspecification/HumanDesugaredLibrarySpecification.java b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/humanspecification/HumanDesugaredLibrarySpecification.java
new file mode 100644
index 0000000..6a6a2d4
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/humanspecification/HumanDesugaredLibrarySpecification.java
@@ -0,0 +1,170 @@
+// Copyright (c) 2021, 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.humanspecification;
+
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexClassAndMethod;
+import com.android.tools.r8.graph.DexEncodedMethod;
+import com.android.tools.r8.graph.DexItemFactory;
+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.ir.desugar.PrefixRewritingMapper;
+import com.android.tools.r8.ir.desugar.PrefixRewritingMapper.DesugarPrefixRewritingMapper;
+import com.android.tools.r8.utils.AndroidApiLevel;
+import com.android.tools.r8.utils.InternalOptions;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+public class HumanDesugaredLibrarySpecification {
+
+  private final boolean libraryCompilation;
+  private final HumanTopLevelFlags topLevelFlags;
+  private final HumanRewritingFlags rewritingFlags;
+  private final PrefixRewritingMapper prefixRewritingMapper;
+
+  public static HumanDesugaredLibrarySpecification withOnlyRewritePrefixForTesting(
+      Map<String, String> prefix, InternalOptions options) {
+    return new HumanDesugaredLibrarySpecification(
+        HumanTopLevelFlags.empty(),
+        HumanRewritingFlags.withOnlyRewritePrefixForTesting(prefix, options),
+        true,
+        options.itemFactory);
+  }
+
+  public static HumanDesugaredLibrarySpecification empty() {
+    return new HumanDesugaredLibrarySpecification(
+        HumanTopLevelFlags.empty(), HumanRewritingFlags.empty(), false, null) {
+
+      @Override
+      public boolean isSupported(DexReference reference, AppView<?> appView) {
+        return false;
+      }
+
+      @Override
+      public boolean isEmptyConfiguration() {
+        return true;
+      }
+    };
+  }
+
+  public HumanDesugaredLibrarySpecification(
+      HumanTopLevelFlags topLevelFlags,
+      HumanRewritingFlags rewritingFlags,
+      boolean libraryCompilation,
+      DexItemFactory factory) {
+    this.libraryCompilation = libraryCompilation;
+    this.topLevelFlags = topLevelFlags;
+    this.rewritingFlags = rewritingFlags;
+    this.prefixRewritingMapper =
+        rewritingFlags.getRewritePrefix().isEmpty()
+            ? PrefixRewritingMapper.empty()
+            : new DesugarPrefixRewritingMapper(
+                rewritingFlags.getRewritePrefix(), factory, libraryCompilation);
+  }
+
+  public boolean supportAllCallbacksFromLibrary() {
+    return topLevelFlags.supportAllCallbacksFromLibrary();
+  }
+
+  public PrefixRewritingMapper getPrefixRewritingMapper() {
+    return prefixRewritingMapper;
+  }
+
+  public AndroidApiLevel getRequiredCompilationApiLevel() {
+    return topLevelFlags.getRequiredCompilationAPILevel();
+  }
+
+  public boolean isLibraryCompilation() {
+    return libraryCompilation;
+  }
+
+  public String getSynthesizedLibraryClassesPackagePrefix() {
+    return topLevelFlags.getSynthesizedLibraryClassesPackagePrefix();
+  }
+
+  public HumanTopLevelFlags getTopLevelFlags() {
+    return topLevelFlags;
+  }
+
+  public HumanRewritingFlags getRewritingFlags() {
+    return rewritingFlags;
+  }
+
+  public String getIdentifier() {
+    return topLevelFlags.getIdentifier();
+  }
+
+  public Map<String, String> getRewritePrefix() {
+    return rewritingFlags.getRewritePrefix();
+  }
+
+  public boolean hasEmulatedLibraryInterfaces() {
+    return !getEmulateLibraryInterface().isEmpty();
+  }
+
+  public Map<DexType, DexType> getEmulateLibraryInterface() {
+    return rewritingFlags.getEmulateLibraryInterface();
+  }
+
+  public boolean isSupported(DexReference reference, AppView<?> appView) {
+    return prefixRewritingMapper.hasRewrittenType(reference.getContextType(), appView);
+  }
+
+  // If the method is retargeted, answers the retargeted method, else null.
+  public DexMethod retargetMethod(DexEncodedMethod method, AppView<?> appView) {
+    Map<DexMethod, DexType> retargetCoreLibMember = rewritingFlags.getRetargetCoreLibMember();
+    DexType dexType = retargetCoreLibMember.get(method.getReference());
+    if (dexType != null) {
+      return appView
+          .dexItemFactory()
+          .createMethod(
+              dexType,
+              appView.dexItemFactory().prependHolderToProto(method.getReference()),
+              method.getName());
+    }
+    return null;
+  }
+
+  public DexMethod retargetMethod(DexClassAndMethod method, AppView<?> appView) {
+    return retargetMethod(method.getDefinition(), appView);
+  }
+
+  public Map<DexMethod, DexType> getRetargetCoreLibMember() {
+    return rewritingFlags.getRetargetCoreLibMember();
+  }
+
+  public Map<DexType, DexType> getBackportCoreLibraryMember() {
+    return rewritingFlags.getBackportCoreLibraryMember();
+  }
+
+  public Map<DexType, DexType> getCustomConversions() {
+    return rewritingFlags.getCustomConversions();
+  }
+
+  public Set<DexType> getWrapperConversions() {
+    return rewritingFlags.getWrapperConversions();
+  }
+
+  public Set<DexMethod> getDontRewriteInvocation() {
+    return rewritingFlags.getDontRewriteInvocation();
+  }
+
+  public Set<DexType> getDontRetargetLibMember() {
+    return rewritingFlags.getDontRetargetLibMember();
+  }
+
+  public List<String> getExtraKeepRules() {
+    return topLevelFlags.getExtraKeepRules();
+  }
+
+  public String getJsonSource() {
+    return topLevelFlags.getJsonSource();
+  }
+
+  public boolean isEmptyConfiguration() {
+    return false;
+  }
+}
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
new file mode 100644
index 0000000..e4f8241
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/humanspecification/HumanDesugaredLibrarySpecificationParser.java
@@ -0,0 +1,219 @@
+// Copyright (c) 2021, 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.humanspecification;
+
+import com.android.tools.r8.StringResource;
+import com.android.tools.r8.graph.DexItemFactory;
+import com.android.tools.r8.origin.Origin;
+import com.android.tools.r8.utils.AndroidApiLevel;
+import com.android.tools.r8.utils.ExceptionDiagnostic;
+import com.android.tools.r8.utils.Reporter;
+import com.android.tools.r8.utils.StringDiagnostic;
+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.List;
+import java.util.Map;
+import java.util.function.Consumer;
+
+public class HumanDesugaredLibrarySpecificationParser {
+
+  static final String IDENTIFIER_KEY = "identifier";
+  static final String REQUIRED_COMPILATION_API_LEVEL_KEY = "required_compilation_api_level";
+  static final String SYNTHESIZED_LIBRARY_CLASSES_PACKAGE_PREFIX_KEY =
+      "synthesized_library_classes_package_prefix";
+
+  static final String COMMON_FLAGS_KEY = "common_flags";
+  static final String LIBRARY_FLAGS_KEY = "library_flags";
+  static final String PROGRAM_FLAGS_KEY = "program_flags";
+
+  static final String API_LEVEL_BELOW_OR_EQUAL_KEY = "api_level_below_or_equal";
+  static final String WRAPPER_CONVERSION_KEY = "wrapper_conversion";
+  static final String CUSTOM_CONVERSION_KEY = "custom_conversion";
+  static final String REWRITE_PREFIX_KEY = "rewrite_prefix";
+  static final String RETARGET_LIB_MEMBER_KEY = "retarget_lib_member";
+  static final String EMULATE_INTERFACE_KEY = "emulate_interface";
+  static final String DONT_REWRITE_KEY = "dont_rewrite";
+  static final String DONT_RETARGET_LIB_MEMBER_KEY = "dont_retarget_lib_member";
+  static final String BACKPORT_KEY = "backport";
+  static final String SHRINKER_CONFIG_KEY = "shrinker_config";
+  static final String SUPPORT_ALL_CALLBACKS_FROM_LIBRARY_KEY = "support_all_callbacks_from_library";
+
+  final DexItemFactory dexItemFactory;
+  final Reporter reporter;
+  private final boolean libraryCompilation;
+  private final int minAPILevel;
+
+  Origin origin;
+  JsonObject jsonConfig;
+
+  public HumanDesugaredLibrarySpecificationParser(
+      DexItemFactory dexItemFactory,
+      Reporter reporter,
+      boolean libraryCompilation,
+      int minAPILevel) {
+    this.dexItemFactory = dexItemFactory;
+    this.reporter = reporter;
+    this.minAPILevel = minAPILevel;
+    this.libraryCompilation = libraryCompilation;
+  }
+
+  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 HumanDesugaredLibrarySpecification parse(StringResource stringResource) {
+    return parse(stringResource, builder -> {});
+  }
+
+  public HumanDesugaredLibrarySpecification parse(
+      StringResource stringResource, Consumer<HumanTopLevelFlags.Builder> topLevelFlagAmender) {
+    String jsonConfigString = parseJson(stringResource);
+
+    HumanTopLevelFlags topLevelFlags = parseTopLevelFlags(jsonConfigString, topLevelFlagAmender);
+
+    HumanRewritingFlags legacyRewritingFlags = parseRewritingFlags();
+
+    HumanDesugaredLibrarySpecification config =
+        new HumanDesugaredLibrarySpecification(
+            topLevelFlags, legacyRewritingFlags, libraryCompilation, dexItemFactory);
+    origin = null;
+    return config;
+  }
+
+  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 HumanRewritingFlags parseRewritingFlags() {
+    HumanRewritingFlags.Builder builder =
+        HumanRewritingFlags.builder(dexItemFactory, reporter, origin);
+    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();
+  }
+
+  HumanTopLevelFlags parseTopLevelFlags(
+      String jsonConfigString, Consumer<HumanTopLevelFlags.Builder> topLevelFlagAmender) {
+    HumanTopLevelFlags.Builder builder = HumanTopLevelFlags.builder();
+
+    builder.setJsonSource(jsonConfigString);
+
+    String identifier = required(jsonConfig, IDENTIFIER_KEY).getAsString();
+    builder.setDesugaredLibraryIdentifier(identifier);
+    builder.setSynthesizedLibraryClassesPackagePrefix(
+        required(jsonConfig, SYNTHESIZED_LIBRARY_CLASSES_PACKAGE_PREFIX_KEY).getAsString());
+
+    int required_compilation_api_level =
+        required(jsonConfig, REQUIRED_COMPILATION_API_LEVEL_KEY).getAsInt();
+    builder.setRequiredCompilationAPILevel(
+        AndroidApiLevel.getAndroidApiLevel(required_compilation_api_level));
+    if (jsonConfig.has(SHRINKER_CONFIG_KEY)) {
+      JsonArray jsonKeepRules = jsonConfig.get(SHRINKER_CONFIG_KEY).getAsJsonArray();
+      List<String> extraKeepRules = new ArrayList<>(jsonKeepRules.size());
+      for (JsonElement keepRule : jsonKeepRules) {
+        extraKeepRules.add(keepRule.getAsString());
+      }
+      builder.setExtraKeepRules(extraKeepRules);
+    }
+
+    if (jsonConfig.has(SUPPORT_ALL_CALLBACKS_FROM_LIBRARY_KEY)) {
+      boolean supportAllCallbacksFromLibrary =
+          jsonConfig.get(SUPPORT_ALL_CALLBACKS_FROM_LIBRARY_KEY).getAsBoolean();
+      builder.setSupportAllCallbacksFromLibrary(supportAllCallbacksFromLibrary);
+    }
+
+    topLevelFlagAmender.accept(builder);
+
+    return builder.build();
+  }
+
+  private void parseFlagsList(JsonArray jsonFlags, HumanRewritingFlags.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) {
+        parseFlags(flag, builder);
+      }
+    }
+  }
+
+  void parseFlags(JsonObject jsonFlagSet, HumanRewritingFlags.Builder builder) {
+    if (jsonFlagSet.has(REWRITE_PREFIX_KEY)) {
+      for (Map.Entry<String, JsonElement> rewritePrefix :
+          jsonFlagSet.get(REWRITE_PREFIX_KEY).getAsJsonObject().entrySet()) {
+        builder.putRewritePrefix(rewritePrefix.getKey(), rewritePrefix.getValue().getAsString());
+      }
+    }
+    if (jsonFlagSet.has(RETARGET_LIB_MEMBER_KEY)) {
+      for (Map.Entry<String, JsonElement> retarget :
+          jsonFlagSet.get(RETARGET_LIB_MEMBER_KEY).getAsJsonObject().entrySet()) {
+        builder.putRetargetCoreLibMember(retarget.getKey(), retarget.getValue().getAsString());
+      }
+    }
+    if (jsonFlagSet.has(BACKPORT_KEY)) {
+      for (Map.Entry<String, JsonElement> backport :
+          jsonFlagSet.get(BACKPORT_KEY).getAsJsonObject().entrySet()) {
+        builder.putBackportCoreLibraryMember(backport.getKey(), backport.getValue().getAsString());
+      }
+    }
+    if (jsonFlagSet.has(EMULATE_INTERFACE_KEY)) {
+      for (Map.Entry<String, JsonElement> itf :
+          jsonFlagSet.get(EMULATE_INTERFACE_KEY).getAsJsonObject().entrySet()) {
+        builder.putEmulateLibraryInterface(itf.getKey(), itf.getValue().getAsString());
+      }
+    }
+    if (jsonFlagSet.has(CUSTOM_CONVERSION_KEY)) {
+      for (Map.Entry<String, JsonElement> conversion :
+          jsonFlagSet.get(CUSTOM_CONVERSION_KEY).getAsJsonObject().entrySet()) {
+        builder.putCustomConversion(conversion.getKey(), conversion.getValue().getAsString());
+      }
+    }
+    if (jsonFlagSet.has(WRAPPER_CONVERSION_KEY)) {
+      for (JsonElement wrapper : jsonFlagSet.get(WRAPPER_CONVERSION_KEY).getAsJsonArray()) {
+        builder.addWrapperConversion(wrapper.getAsString());
+      }
+    }
+    if (jsonFlagSet.has(DONT_REWRITE_KEY)) {
+      JsonArray dontRewrite = jsonFlagSet.get(DONT_REWRITE_KEY).getAsJsonArray();
+      for (JsonElement rewrite : dontRewrite) {
+        builder.addDontRewriteInvocation(rewrite.getAsString());
+      }
+    }
+    if (jsonFlagSet.has(DONT_RETARGET_LIB_MEMBER_KEY)) {
+      JsonArray dontRetarget = jsonFlagSet.get(DONT_RETARGET_LIB_MEMBER_KEY).getAsJsonArray();
+      for (JsonElement rewrite : dontRetarget) {
+        builder.addDontRetargetLibMember(rewrite.getAsString());
+      }
+    }
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/humanspecification/HumanRewritingFlags.java b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/humanspecification/HumanRewritingFlags.java
new file mode 100644
index 0000000..8adb9b7
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/humanspecification/HumanRewritingFlags.java
@@ -0,0 +1,351 @@
+// Copyright (c) 2021, 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.humanspecification;
+
+import com.android.tools.r8.graph.DexItemFactory;
+import com.android.tools.r8.graph.DexMethod;
+import com.android.tools.r8.graph.DexProto;
+import com.android.tools.r8.graph.DexString;
+import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.origin.Origin;
+import com.android.tools.r8.utils.DescriptorUtils;
+import com.android.tools.r8.utils.InternalOptions;
+import com.android.tools.r8.utils.Reporter;
+import com.android.tools.r8.utils.StringDiagnostic;
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.Sets;
+import com.google.common.collect.Sets.SetView;
+import java.util.HashMap;
+import java.util.IdentityHashMap;
+import java.util.Map;
+import java.util.Set;
+import java.util.stream.Collectors;
+
+public class HumanRewritingFlags {
+
+  private final Map<String, String> rewritePrefix;
+  private final Map<DexType, DexType> emulateLibraryInterface;
+  private final Map<DexMethod, DexType> retargetCoreLibMember;
+  private final Map<DexType, DexType> backportCoreLibraryMember;
+  private final Map<DexType, DexType> customConversions;
+  private final Set<DexMethod> dontRewriteInvocation;
+  private final Set<DexType> dontRetargetLibMember;
+  private final Set<DexType> wrapperConversions;
+
+  HumanRewritingFlags(
+      Map<String, String> rewritePrefix,
+      Map<DexType, DexType> emulateLibraryInterface,
+      Map<DexMethod, DexType> retargetCoreLibMember,
+      Map<DexType, DexType> backportCoreLibraryMember,
+      Map<DexType, DexType> customConversions,
+      Set<DexMethod> dontRewriteInvocation,
+      Set<DexType> dontRetargetLibMember,
+      Set<DexType> wrapperConversions) {
+    this.rewritePrefix = rewritePrefix;
+    this.emulateLibraryInterface = emulateLibraryInterface;
+    this.retargetCoreLibMember = retargetCoreLibMember;
+    this.backportCoreLibraryMember = backportCoreLibraryMember;
+    this.customConversions = customConversions;
+    this.dontRewriteInvocation = dontRewriteInvocation;
+    this.dontRetargetLibMember = dontRetargetLibMember;
+    this.wrapperConversions = wrapperConversions;
+  }
+
+  public static HumanRewritingFlags empty() {
+    return new HumanRewritingFlags(
+        ImmutableMap.of(),
+        ImmutableMap.of(),
+        ImmutableMap.of(),
+        ImmutableMap.of(),
+        ImmutableMap.of(),
+        ImmutableSet.of(),
+        ImmutableSet.of(),
+        ImmutableSet.of());
+  }
+
+  public static HumanRewritingFlags withOnlyRewritePrefixForTesting(
+      Map<String, String> prefix, InternalOptions options) {
+    Builder builder = builder(options.dexItemFactory(), options.reporter, Origin.unknown());
+    prefix.forEach(builder::putRewritePrefix);
+    return builder.build();
+  }
+
+  public static Builder builder(DexItemFactory dexItemFactory, Reporter reporter, Origin origin) {
+    return new Builder(dexItemFactory, reporter, origin);
+  }
+
+  public Builder newBuilder(DexItemFactory dexItemFactory, Reporter reporter, Origin origin) {
+    return new Builder(
+        dexItemFactory,
+        reporter,
+        origin,
+        rewritePrefix,
+        emulateLibraryInterface,
+        retargetCoreLibMember,
+        backportCoreLibraryMember,
+        customConversions,
+        dontRewriteInvocation,
+        dontRetargetLibMember,
+        wrapperConversions);
+  }
+
+  public Map<String, String> getRewritePrefix() {
+    return rewritePrefix;
+  }
+
+  public Map<DexType, DexType> getEmulateLibraryInterface() {
+    return emulateLibraryInterface;
+  }
+
+  public Map<DexMethod, DexType> getRetargetCoreLibMember() {
+    return retargetCoreLibMember;
+  }
+
+  public Map<DexType, DexType> getBackportCoreLibraryMember() {
+    return backportCoreLibraryMember;
+  }
+
+  public Map<DexType, DexType> getCustomConversions() {
+    return customConversions;
+  }
+
+  public Set<DexMethod> getDontRewriteInvocation() {
+    return dontRewriteInvocation;
+  }
+
+  public Set<DexType> getDontRetargetLibMember() {
+    return dontRetargetLibMember;
+  }
+
+  public Set<DexType> getWrapperConversions() {
+    return wrapperConversions;
+  }
+
+  public static class Builder {
+
+    private static final String SEPARATORS = "\\s+|,\\s+|#|\\(|\\)";
+
+    private final DexItemFactory factory;
+    private final Reporter reporter;
+    private final Origin origin;
+
+    private final Map<String, String> rewritePrefix;
+    private final Map<DexType, DexType> emulateLibraryInterface;
+    private final Map<DexMethod, DexType> retargetCoreLibMember;
+    private final Map<DexType, DexType> backportCoreLibraryMember;
+    private final Map<DexType, DexType> customConversions;
+    private final Set<DexMethod> dontRewriteInvocation;
+    private final Set<DexType> dontRetargetLibMember;
+    private final Set<DexType> wrapperConversions;
+
+    Builder(DexItemFactory factory, Reporter reporter, Origin origin) {
+      this(
+          factory,
+          reporter,
+          origin,
+          new HashMap<>(),
+          new IdentityHashMap<>(),
+          new IdentityHashMap<>(),
+          new IdentityHashMap<>(),
+          new IdentityHashMap<>(),
+          Sets.newIdentityHashSet(),
+          Sets.newIdentityHashSet(),
+          Sets.newIdentityHashSet());
+    }
+
+    Builder(
+        DexItemFactory factory,
+        Reporter reporter,
+        Origin origin,
+        Map<String, String> rewritePrefix,
+        Map<DexType, DexType> emulateLibraryInterface,
+        Map<DexMethod, DexType> retargetCoreLibMember,
+        Map<DexType, DexType> backportCoreLibraryMember,
+        Map<DexType, DexType> customConversions,
+        Set<DexMethod> dontRewriteInvocation,
+        Set<DexType> dontRetargetLibMember,
+        Set<DexType> wrapperConversions) {
+      this.factory = factory;
+      this.reporter = reporter;
+      this.origin = origin;
+      this.rewritePrefix = new HashMap<>(rewritePrefix);
+      this.emulateLibraryInterface = new IdentityHashMap<>(emulateLibraryInterface);
+      this.retargetCoreLibMember = new IdentityHashMap<>(retargetCoreLibMember);
+      this.backportCoreLibraryMember = new IdentityHashMap<>(backportCoreLibraryMember);
+      this.customConversions = new IdentityHashMap<>(customConversions);
+      this.dontRewriteInvocation = dontRewriteInvocation;
+      this.dontRetargetLibMember = dontRetargetLibMember;
+      this.wrapperConversions = wrapperConversions;
+    }
+
+    // Utility to set values. Currently assumes the key is fresh.
+    private <K, V> void put(Map<K, V> map, K key, V value, String desc) {
+      if (map.containsKey(key)) {
+        throw reporter.fatalError(
+            new StringDiagnostic(
+                "Invalid desugared library configuration. "
+                    + " Duplicate assignment of key: '"
+                    + key
+                    + "' in sections for '"
+                    + desc
+                    + "'",
+                origin));
+      }
+      map.put(key, value);
+    }
+
+    public Builder putRewritePrefix(String prefix, String rewrittenPrefix) {
+      put(
+          rewritePrefix,
+          prefix,
+          rewrittenPrefix,
+          HumanDesugaredLibrarySpecificationParser.REWRITE_PREFIX_KEY);
+      return this;
+    }
+
+    public Builder putEmulateLibraryInterface(
+        String emulateLibraryItf, String rewrittenEmulateLibraryItf) {
+      DexType interfaceType = stringClassToDexType(emulateLibraryItf);
+      DexType rewrittenType = stringClassToDexType(rewrittenEmulateLibraryItf);
+      putEmulateLibraryInterface(interfaceType, rewrittenType);
+      return this;
+    }
+
+    public Builder putEmulateLibraryInterface(DexType interfaceType, DexType rewrittenType) {
+      put(
+          emulateLibraryInterface,
+          interfaceType,
+          rewrittenType,
+          HumanDesugaredLibrarySpecificationParser.EMULATE_INTERFACE_KEY);
+      return this;
+    }
+
+    public Builder putCustomConversion(String type, String conversionHolder) {
+      DexType dexType = stringClassToDexType(type);
+      DexType conversionType = stringClassToDexType(conversionHolder);
+      putCustomConversion(dexType, conversionType);
+      return this;
+    }
+
+    public Builder putCustomConversion(DexType dexType, DexType conversionType) {
+      put(
+          customConversions,
+          dexType,
+          conversionType,
+          HumanDesugaredLibrarySpecificationParser.CUSTOM_CONVERSION_KEY);
+      return this;
+    }
+
+    public Builder addWrapperConversion(String type) {
+      DexType dexType = stringClassToDexType(type);
+      addWrapperConversion(dexType);
+      return this;
+    }
+
+    public Builder addWrapperConversion(DexType dexType) {
+      wrapperConversions.add(dexType);
+      return this;
+    }
+
+    public Builder putRetargetCoreLibMember(String retarget, String rewrittenRetarget) {
+      DexMethod key = parseMethod(retarget);
+      DexType rewrittenType = stringClassToDexType(rewrittenRetarget);
+      putRetargetCoreLibMember(key, rewrittenType);
+      return this;
+    }
+
+    public Builder putRetargetCoreLibMember(DexMethod key, DexType rewrittenType) {
+      put(
+          retargetCoreLibMember,
+          key,
+          rewrittenType,
+          HumanDesugaredLibrarySpecificationParser.RETARGET_LIB_MEMBER_KEY);
+      return this;
+    }
+
+    public Builder putBackportCoreLibraryMember(String backport, String rewrittenBackport) {
+      DexType backportType = stringClassToDexType(backport);
+      DexType rewrittenBackportType = stringClassToDexType(rewrittenBackport);
+      putBackportCoreLibraryMember(backportType, rewrittenBackportType);
+      return this;
+    }
+
+    public Builder putBackportCoreLibraryMember(
+        DexType backportType, DexType rewrittenBackportType) {
+      put(
+          backportCoreLibraryMember,
+          backportType,
+          rewrittenBackportType,
+          HumanDesugaredLibrarySpecificationParser.BACKPORT_KEY);
+      return this;
+    }
+
+    public Builder addDontRewriteInvocation(String dontRewriteInvocation) {
+      DexMethod dontRewrite = parseMethod(dontRewriteInvocation);
+      addDontRewriteInvocation(dontRewrite);
+      return this;
+    }
+
+    public Builder addDontRewriteInvocation(DexMethod dontRewrite) {
+      this.dontRewriteInvocation.add(dontRewrite);
+      return this;
+    }
+
+    public Builder addDontRetargetLibMember(String dontRetargetLibMember) {
+      addDontRetargetLibMember(stringClassToDexType(dontRetargetLibMember));
+      return this;
+    }
+
+    public Builder addDontRetargetLibMember(DexType dontRetargetLibMember) {
+      this.dontRetargetLibMember.add(dontRetargetLibMember);
+      return this;
+    }
+
+    private DexMethod parseMethod(String signature) {
+      String[] split = signature.split(SEPARATORS);
+      assert split.length >= 3;
+      DexType returnType = factory.createType(DescriptorUtils.javaTypeToDescriptor(split[0]));
+      DexType holderType = factory.createType(DescriptorUtils.javaTypeToDescriptor(split[1]));
+      DexString name = factory.createString(split[2]);
+      DexType[] argTypes = new DexType[split.length - 3];
+      for (int i = 3; i < split.length; i++) {
+        argTypes[i - 3] = factory.createType(DescriptorUtils.javaTypeToDescriptor(split[i]));
+      }
+      DexProto proto = factory.createProto(returnType, argTypes);
+      return factory.createMethod(holderType, proto, name);
+    }
+
+    private DexType stringClassToDexType(String stringClass) {
+      return factory.createType(DescriptorUtils.javaTypeToDescriptor(stringClass));
+    }
+
+    public HumanRewritingFlags build() {
+      validate();
+      return new HumanRewritingFlags(
+          ImmutableMap.copyOf(rewritePrefix),
+          ImmutableMap.copyOf(emulateLibraryInterface),
+          ImmutableMap.copyOf(retargetCoreLibMember),
+          ImmutableMap.copyOf(backportCoreLibraryMember),
+          ImmutableMap.copyOf(customConversions),
+          ImmutableSet.copyOf(dontRewriteInvocation),
+          ImmutableSet.copyOf(dontRetargetLibMember),
+          ImmutableSet.copyOf(wrapperConversions));
+    }
+
+    private void validate() {
+      SetView<DexType> dups = Sets.intersection(customConversions.keySet(), wrapperConversions);
+      if (!dups.isEmpty()) {
+        throw reporter.fatalError(
+            new StringDiagnostic(
+                "Invalid desugared library configuration. "
+                    + "Duplicate types in custom conversions and wrapper conversions: "
+                    + String.join(
+                        ", ", dups.stream().map(DexType::toString).collect(Collectors.toSet())),
+                origin));
+      }
+    }
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/humanspecification/HumanTopLevelFlags.java b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/humanspecification/HumanTopLevelFlags.java
new file mode 100644
index 0000000..80a13b5
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/humanspecification/HumanTopLevelFlags.java
@@ -0,0 +1,133 @@
+// Copyright (c) 2021, 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.humanspecification;
+
+import com.android.tools.r8.utils.AndroidApiLevel;
+import com.google.common.collect.ImmutableList;
+import java.util.List;
+
+public class HumanTopLevelFlags {
+
+  private final AndroidApiLevel requiredCompilationAPILevel;
+  private final String synthesizedLibraryClassesPackagePrefix;
+  private final String identifier;
+  private final String jsonSource;
+  // Setting supportAllCallbacksFromLibrary reduces the number of generated call-backs,
+  // more specifically:
+  // - no call-back is generated for emulated interface method overrides (forEach, etc.)
+  // - no call-back is generated inside the desugared library itself.
+  // Such setting decreases significantly the desugared library dex file, but virtual calls from
+  // within the library to desugared library classes instances as receiver may be incorrect, for
+  // example the method forEach in Iterable may be executed over a concrete implementation.
+  private final boolean supportAllCallbacksFromLibrary;
+  private final List<String> extraKeepRules;
+
+  HumanTopLevelFlags(
+      AndroidApiLevel requiredCompilationAPILevel,
+      String synthesizedLibraryClassesPackagePrefix,
+      String identifier,
+      String jsonSource,
+      boolean supportAllCallbacksFromLibrary,
+      List<String> extraKeepRules) {
+    this.requiredCompilationAPILevel = requiredCompilationAPILevel;
+    this.synthesizedLibraryClassesPackagePrefix = synthesizedLibraryClassesPackagePrefix;
+    this.identifier = identifier;
+    this.jsonSource = jsonSource;
+    this.supportAllCallbacksFromLibrary = supportAllCallbacksFromLibrary;
+    this.extraKeepRules = extraKeepRules;
+  }
+
+  public static HumanTopLevelFlags empty() {
+    return new HumanTopLevelFlags(
+        AndroidApiLevel.B, "unused", null, null, true, ImmutableList.of());
+  }
+
+  public static HumanTopLevelFlags testing() {
+    return new HumanTopLevelFlags(
+        AndroidApiLevel.B, "unused", "testing", null, true, ImmutableList.of());
+  }
+
+  public static Builder builder() {
+    return new Builder();
+  }
+
+  public AndroidApiLevel getRequiredCompilationAPILevel() {
+    return requiredCompilationAPILevel;
+  }
+
+  public String getSynthesizedLibraryClassesPackagePrefix() {
+    return synthesizedLibraryClassesPackagePrefix;
+  }
+
+  public String getIdentifier() {
+    return identifier;
+  }
+
+  public String getJsonSource() {
+    return jsonSource;
+  }
+
+  public boolean supportAllCallbacksFromLibrary() {
+    return supportAllCallbacksFromLibrary;
+  }
+
+  public List<String> getExtraKeepRules() {
+    return extraKeepRules;
+  }
+
+  public static class Builder {
+
+    private AndroidApiLevel requiredCompilationAPILevel;
+    private String synthesizedLibraryClassesPackagePrefix;
+    private String identifier;
+    private String jsonSource;
+    private Boolean supportAllCallbacksFromLibrary;
+    private List<String> extraKeepRules;
+
+    Builder() {}
+
+    public Builder setRequiredCompilationAPILevel(AndroidApiLevel requiredCompilationAPILevel) {
+      this.requiredCompilationAPILevel = requiredCompilationAPILevel;
+      return this;
+    }
+
+    public Builder setSynthesizedLibraryClassesPackagePrefix(String prefix) {
+      this.synthesizedLibraryClassesPackagePrefix = prefix.replace('.', '/');
+      return this;
+    }
+
+    public Builder setDesugaredLibraryIdentifier(String identifier) {
+      this.identifier = identifier;
+      return this;
+    }
+
+    public Builder setJsonSource(String jsonSource) {
+      this.jsonSource = jsonSource;
+      return this;
+    }
+
+    public Builder setSupportAllCallbacksFromLibrary(boolean supportAllCallbacksFromLibrary) {
+      this.supportAllCallbacksFromLibrary = supportAllCallbacksFromLibrary;
+      return this;
+    }
+
+    public Builder setExtraKeepRules(List<String> rules) {
+      extraKeepRules = rules;
+      return this;
+    }
+
+    public HumanTopLevelFlags build() {
+      assert synthesizedLibraryClassesPackagePrefix != null;
+      assert supportAllCallbacksFromLibrary != null;
+      return new HumanTopLevelFlags(
+          requiredCompilationAPILevel,
+          synthesizedLibraryClassesPackagePrefix,
+          identifier,
+          jsonSource,
+          supportAllCallbacksFromLibrary,
+          extraKeepRules);
+    }
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/humanspecification/MultiAPILevelHumanDesugaredLibrarySpecification.java b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/humanspecification/MultiAPILevelHumanDesugaredLibrarySpecification.java
new file mode 100644
index 0000000..dff3d13
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/humanspecification/MultiAPILevelHumanDesugaredLibrarySpecification.java
@@ -0,0 +1,42 @@
+// Copyright (c) 2021, 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.humanspecification;
+
+import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
+
+public class MultiAPILevelHumanDesugaredLibrarySpecification {
+
+  private final HumanTopLevelFlags topLevelFlags;
+  private final Int2ObjectMap<HumanRewritingFlags> commonFlags;
+  private final Int2ObjectMap<HumanRewritingFlags> libraryFlags;
+  private final Int2ObjectMap<HumanRewritingFlags> programFlags;
+
+  public MultiAPILevelHumanDesugaredLibrarySpecification(
+      HumanTopLevelFlags topLevelFlags,
+      Int2ObjectMap<HumanRewritingFlags> commonFlags,
+      Int2ObjectMap<HumanRewritingFlags> libraryFlags,
+      Int2ObjectMap<HumanRewritingFlags> programFlags) {
+    this.topLevelFlags = topLevelFlags;
+    this.commonFlags = commonFlags;
+    this.libraryFlags = libraryFlags;
+    this.programFlags = programFlags;
+  }
+
+  public HumanTopLevelFlags getTopLevelFlags() {
+    return topLevelFlags;
+  }
+
+  public Int2ObjectMap<HumanRewritingFlags> getCommonFlags() {
+    return commonFlags;
+  }
+
+  public Int2ObjectMap<HumanRewritingFlags> getLibraryFlags() {
+    return libraryFlags;
+  }
+
+  public Int2ObjectMap<HumanRewritingFlags> getProgramFlags() {
+    return programFlags;
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/humanspecification/MultiAPILevelHumanDesugaredLibrarySpecificationJsonExporter.java b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/humanspecification/MultiAPILevelHumanDesugaredLibrarySpecificationJsonExporter.java
new file mode 100644
index 0000000..769d522
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/humanspecification/MultiAPILevelHumanDesugaredLibrarySpecificationJsonExporter.java
@@ -0,0 +1,133 @@
+// Copyright (c) 2021, 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.humanspecification;
+
+import static com.android.tools.r8.ir.desugar.desugaredlibrary.humanspecification.HumanDesugaredLibrarySpecificationParser.*;
+import static com.android.tools.r8.ir.desugar.desugaredlibrary.humanspecification.HumanDesugaredLibrarySpecificationParser.IDENTIFIER_KEY;
+
+import com.android.tools.r8.DiagnosticsHandler;
+import com.android.tools.r8.StringConsumer;
+import com.android.tools.r8.errors.Unreachable;
+import com.android.tools.r8.graph.DexItem;
+import com.android.tools.r8.graph.DexMethod;
+import com.android.tools.r8.graph.DexType;
+import com.google.common.collect.Sets;
+import com.google.gson.Gson;
+import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.TreeMap;
+
+public class MultiAPILevelHumanDesugaredLibrarySpecificationJsonExporter {
+
+  public static void export(
+      MultiAPILevelHumanDesugaredLibrarySpecification specification, StringConsumer output) {
+    new MultiAPILevelHumanDesugaredLibrarySpecificationJsonExporter()
+        .internalExport(specification, output);
+  }
+
+  private void internalExport(
+      MultiAPILevelHumanDesugaredLibrarySpecification humanSpec, StringConsumer output) {
+    HashMap<String, Object> toJson = new LinkedHashMap<>();
+    toJson.put(IDENTIFIER_KEY, humanSpec.getTopLevelFlags().getIdentifier());
+    toJson.put(
+        REQUIRED_COMPILATION_API_LEVEL_KEY,
+        humanSpec.getTopLevelFlags().getRequiredCompilationAPILevel().getLevel());
+    toJson.put(
+        SYNTHESIZED_LIBRARY_CLASSES_PACKAGE_PREFIX_KEY,
+        humanSpec.getTopLevelFlags().getSynthesizedLibraryClassesPackagePrefix());
+    toJson.put(
+        SUPPORT_ALL_CALLBACKS_FROM_LIBRARY_KEY,
+        humanSpec.getTopLevelFlags().supportAllCallbacksFromLibrary());
+
+    toJson.put(COMMON_FLAGS_KEY, rewritingFlagsToString(humanSpec.getCommonFlags()));
+    toJson.put(PROGRAM_FLAGS_KEY, rewritingFlagsToString(humanSpec.getProgramFlags()));
+    toJson.put(LIBRARY_FLAGS_KEY, rewritingFlagsToString(humanSpec.getLibraryFlags()));
+
+    toJson.put(SHRINKER_CONFIG_KEY, humanSpec.getTopLevelFlags().getExtraKeepRules());
+
+    Gson gson = new Gson();
+    String export = gson.toJson(toJson);
+    output.accept(export, new DiagnosticsHandler() {});
+  }
+
+  private List<Object> rewritingFlagsToString(
+      Int2ObjectMap<HumanRewritingFlags> rewritingFlagsMap) {
+    ArrayList<Object> list = new ArrayList<>();
+    rewritingFlagsMap.forEach(
+        (apiBelowOrEqual, flags) -> {
+          HashMap<String, Object> toJson = new LinkedHashMap<>();
+          toJson.put(API_LEVEL_BELOW_OR_EQUAL_KEY, apiBelowOrEqual);
+          if (!flags.getRewritePrefix().isEmpty()) {
+            toJson.put(REWRITE_PREFIX_KEY, new TreeMap(flags.getRewritePrefix()));
+          }
+          if (!flags.getEmulateLibraryInterface().isEmpty()) {
+            toJson.put(EMULATE_INTERFACE_KEY, mapToString(flags.getEmulateLibraryInterface()));
+          }
+          if (!flags.getDontRewriteInvocation().isEmpty()) {
+            toJson.put(DONT_REWRITE_KEY, setToString(flags.getDontRewriteInvocation()));
+          }
+          if (!flags.getRetargetCoreLibMember().isEmpty()) {
+            toJson.put(RETARGET_LIB_MEMBER_KEY, mapToString(flags.getRetargetCoreLibMember()));
+          }
+          if (!flags.getDontRetargetLibMember().isEmpty()) {
+            toJson.put(DONT_RETARGET_LIB_MEMBER_KEY, setToString(flags.getDontRetargetLibMember()));
+          }
+          if (!flags.getBackportCoreLibraryMember().isEmpty()) {
+            toJson.put(BACKPORT_KEY, mapToString(flags.getBackportCoreLibraryMember()));
+          }
+          if (!flags.getWrapperConversions().isEmpty()) {
+            toJson.put(WRAPPER_CONVERSION_KEY, setToString(flags.getWrapperConversions()));
+          }
+          if (!flags.getCustomConversions().isEmpty()) {
+            toJson.put(CUSTOM_CONVERSION_KEY, mapToString(flags.getCustomConversions()));
+          }
+          list.add(toJson);
+        });
+    return list;
+  }
+
+  private Set<String> setToString(Set<? extends DexItem> set) {
+    Set<String> stringSet = Sets.newHashSet();
+    set.forEach(e -> stringSet.add(toString(e)));
+    return stringSet;
+  }
+
+  private Map<String, String> mapToString(Map<? extends DexItem, ? extends DexItem> map) {
+    Map<String, String> stringMap = new TreeMap<>();
+    map.forEach((k, v) -> stringMap.put(toString(k), toString(v)));
+    return stringMap;
+  }
+
+  private String toString(DexItem o) {
+    if (o instanceof DexType) {
+      return o.toString();
+    }
+    if (o instanceof DexMethod) {
+      DexMethod method = (DexMethod) o;
+      StringBuilder sb =
+          new StringBuilder()
+              .append(method.getReturnType())
+              .append(" ")
+              .append(method.getHolderType())
+              .append("#")
+              .append(method.getName())
+              .append("(");
+      for (int i = 0; i < method.getParameters().size(); i++) {
+        sb.append(method.getParameter(i));
+        if (i != method.getParameters().size() - 1) {
+          sb.append(", ");
+        }
+      }
+      sb.append(")");
+      return sb.toString();
+    }
+    throw new Unreachable();
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/humanspecification/MultiAPILevelHumanDesugaredLibrarySpecificationParser.java b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/humanspecification/MultiAPILevelHumanDesugaredLibrarySpecificationParser.java
new file mode 100644
index 0000000..54c6c46
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/humanspecification/MultiAPILevelHumanDesugaredLibrarySpecificationParser.java
@@ -0,0 +1,53 @@
+// Copyright (c) 2021, 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.humanspecification;
+
+import com.android.tools.r8.StringResource;
+import com.android.tools.r8.graph.DexItemFactory;
+import com.android.tools.r8.utils.Reporter;
+import com.google.gson.JsonElement;
+import com.google.gson.JsonObject;
+import it.unimi.dsi.fastutil.ints.Int2ObjectArrayMap;
+import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
+
+public class MultiAPILevelHumanDesugaredLibrarySpecificationParser
+    extends HumanDesugaredLibrarySpecificationParser {
+
+  public MultiAPILevelHumanDesugaredLibrarySpecificationParser(
+      DexItemFactory dexItemFactory, Reporter reporter) {
+    super(dexItemFactory, reporter, false, 1);
+  }
+
+  public MultiAPILevelHumanDesugaredLibrarySpecification parseMultiLevelConfiguration(
+      StringResource stringResource) {
+
+    String jsonConfigString = parseJson(stringResource);
+
+    HumanTopLevelFlags topLevelFlags = parseTopLevelFlags(jsonConfigString, builder -> {});
+
+    Int2ObjectMap<HumanRewritingFlags> commonFlags = parseAllFlags(COMMON_FLAGS_KEY);
+    Int2ObjectMap<HumanRewritingFlags> libraryFlags = parseAllFlags(LIBRARY_FLAGS_KEY);
+    Int2ObjectMap<HumanRewritingFlags> programFlags = parseAllFlags(PROGRAM_FLAGS_KEY);
+
+    return new MultiAPILevelHumanDesugaredLibrarySpecification(
+        topLevelFlags, commonFlags, libraryFlags, programFlags);
+  }
+
+  private Int2ObjectMap<HumanRewritingFlags> parseAllFlags(String flagKey) {
+    JsonElement jsonFlags = required(jsonConfig, flagKey);
+    Int2ObjectMap<HumanRewritingFlags> flags = new Int2ObjectArrayMap<>();
+    for (JsonElement jsonFlagSet : jsonFlags.getAsJsonArray()) {
+      JsonObject flag = jsonFlagSet.getAsJsonObject();
+      int api_level_below_or_equal = required(flag, API_LEVEL_BELOW_OR_EQUAL_KEY).getAsInt();
+      HumanRewritingFlags.Builder builder =
+          flags.containsKey(api_level_below_or_equal)
+              ? flags.get(api_level_below_or_equal).newBuilder(dexItemFactory, reporter, origin)
+              : HumanRewritingFlags.builder(dexItemFactory, reporter, origin);
+      parseFlags(flag, builder);
+      flags.put(api_level_below_or_equal, builder.build());
+    }
+    return flags;
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/legacyspecification/LegacyDesugaredLibrarySpecification.java b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/legacyspecification/LegacyDesugaredLibrarySpecification.java
index 6198ce5..3da0b97 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/legacyspecification/LegacyDesugaredLibrarySpecification.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/legacyspecification/LegacyDesugaredLibrarySpecification.java
@@ -68,6 +68,14 @@
                 rewritingFlags.getRewritePrefix(), factory, libraryCompilation);
   }
 
+  public LegacyTopLevelFlags getTopLevelFlags() {
+    return topLevelFlags;
+  }
+
+  public LegacyRewritingFlags getRewritingFlags() {
+    return rewritingFlags;
+  }
+
   public boolean supportAllCallbacksFromLibrary() {
     return topLevelFlags.supportAllCallbacksFromLibrary();
   }
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/legacyspecification/MultiAPILevelLegacyDesugaredLibrarySpecification.java b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/legacyspecification/MultiAPILevelLegacyDesugaredLibrarySpecification.java
index 76a0b07..0c0bfe6 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/legacyspecification/MultiAPILevelLegacyDesugaredLibrarySpecification.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/legacyspecification/MultiAPILevelLegacyDesugaredLibrarySpecification.java
@@ -23,4 +23,20 @@
     this.libraryFlags = libraryFlags;
     this.programFlags = programFlags;
   }
+
+  public LegacyTopLevelFlags getTopLevelFlags() {
+    return topLevelFlags;
+  }
+
+  public Int2ObjectMap<LegacyRewritingFlags> getCommonFlags() {
+    return commonFlags;
+  }
+
+  public Int2ObjectMap<LegacyRewritingFlags> getLibraryFlags() {
+    return libraryFlags;
+  }
+
+  public Int2ObjectMap<LegacyRewritingFlags> getProgramFlags() {
+    return programFlags;
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/specificationconversion/LegacyToHumanSpecificationConverter.java b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/specificationconversion/LegacyToHumanSpecificationConverter.java
new file mode 100644
index 0000000..6e26eff
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/specificationconversion/LegacyToHumanSpecificationConverter.java
@@ -0,0 +1,215 @@
+// Copyright (c) 2021, 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.specificationconversion;
+
+import com.android.tools.r8.StringConsumer;
+import com.android.tools.r8.StringResource;
+import com.android.tools.r8.dex.ApplicationReader;
+import com.android.tools.r8.graph.DexApplication;
+import com.android.tools.r8.graph.DexClass;
+import com.android.tools.r8.graph.DexClassAndMethod;
+import com.android.tools.r8.graph.DexItemFactory;
+import com.android.tools.r8.graph.DexMethod;
+import com.android.tools.r8.graph.DexProto;
+import com.android.tools.r8.graph.DexString;
+import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.graph.DirectMappedDexApplication;
+import com.android.tools.r8.ir.desugar.desugaredlibrary.humanspecification.HumanDesugaredLibrarySpecification;
+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;
+import com.android.tools.r8.ir.desugar.desugaredlibrary.humanspecification.MultiAPILevelHumanDesugaredLibrarySpecificationJsonExporter;
+import com.android.tools.r8.ir.desugar.desugaredlibrary.legacyspecification.LegacyDesugaredLibrarySpecification;
+import com.android.tools.r8.ir.desugar.desugaredlibrary.legacyspecification.LegacyRewritingFlags;
+import com.android.tools.r8.ir.desugar.desugaredlibrary.legacyspecification.LegacyTopLevelFlags;
+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.origin.Origin;
+import com.android.tools.r8.utils.AndroidApiLevel;
+import com.android.tools.r8.utils.AndroidApp;
+import com.android.tools.r8.utils.InternalOptions;
+import com.android.tools.r8.utils.Pair;
+import com.android.tools.r8.utils.ThreadUtils;
+import com.android.tools.r8.utils.Timing;
+import it.unimi.dsi.fastutil.ints.Int2ObjectArrayMap;
+import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
+import java.io.IOException;
+import java.nio.file.Path;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.ExecutorService;
+
+public class LegacyToHumanSpecificationConverter implements SpecificationConverter {
+
+  @Override
+  public void convertAllAPILevels(
+      StringResource inputSpecification, Path androidLib, StringConsumer output)
+      throws IOException {
+    InternalOptions options = new InternalOptions();
+    MultiAPILevelLegacyDesugaredLibrarySpecification legacySpec =
+        new MultiAPILevelLegacyDesugaredLibrarySpecificationParser(
+                options.dexItemFactory(), options.reporter)
+            .parseMultiLevelConfiguration(inputSpecification);
+    MultiAPILevelHumanDesugaredLibrarySpecification humanSpec =
+        convertAllAPILevels(legacySpec, androidLib, options);
+    MultiAPILevelHumanDesugaredLibrarySpecificationJsonExporter.export(humanSpec, output);
+  }
+
+  public MultiAPILevelHumanDesugaredLibrarySpecification convertAllAPILevels(
+      MultiAPILevelLegacyDesugaredLibrarySpecification legacySpec,
+      Path androidLib,
+      InternalOptions options)
+      throws IOException {
+    DexApplication app = readApp(androidLib, options);
+    HumanTopLevelFlags humanTopLevelFlags = convertTopLevelFlags(legacySpec.getTopLevelFlags());
+    Int2ObjectArrayMap<HumanRewritingFlags> commonFlags =
+        convertRewritingFlagMap(legacySpec.getCommonFlags(), app);
+    Int2ObjectArrayMap<HumanRewritingFlags> programFlags =
+        convertRewritingFlagMap(legacySpec.getProgramFlags(), app);
+    Int2ObjectArrayMap<HumanRewritingFlags> libraryFlags =
+        convertRewritingFlagMap(legacySpec.getLibraryFlags(), app);
+
+    legacyLibraryFlagHacks(libraryFlags, app);
+
+    return new MultiAPILevelHumanDesugaredLibrarySpecification(
+        humanTopLevelFlags, commonFlags, libraryFlags, programFlags);
+  }
+
+  public HumanDesugaredLibrarySpecification convert(
+      LegacyDesugaredLibrarySpecification legacySpec, Path androidLib, InternalOptions options)
+      throws IOException {
+    DexApplication app = readApp(androidLib, options);
+    HumanTopLevelFlags humanTopLevelFlags = convertTopLevelFlags(legacySpec.getTopLevelFlags());
+    HumanRewritingFlags humanRewritingFlags =
+        convertRewritingFlags(legacySpec.getRewritingFlags(), app);
+    return new HumanDesugaredLibrarySpecification(
+        humanTopLevelFlags,
+        humanRewritingFlags,
+        legacySpec.isLibraryCompilation(),
+        app.dexItemFactory());
+  }
+
+  private void legacyLibraryFlagHacks(
+      Int2ObjectArrayMap<HumanRewritingFlags> libraryFlags, DexApplication app) {
+    HumanRewritingFlags humanRewritingFlags = libraryFlags.get(AndroidApiLevel.N_MR1.getLevel());
+    HumanRewritingFlags.Builder builder =
+        humanRewritingFlags.newBuilder(
+            app.dexItemFactory(), app.options.reporter, Origin.unknown());
+    DexItemFactory itemFactory = app.dexItemFactory();
+
+    // TODO(b/177977763): This is only a workaround rewriting invokes of j.u.Arrays.deepEquals0
+    // to j.u.DesugarArrays.deepEquals0.
+    DexString name = itemFactory.createString("deepEquals0");
+    DexProto proto =
+        itemFactory.createProto(
+            itemFactory.booleanType, itemFactory.objectType, itemFactory.objectType);
+    DexMethod source =
+        itemFactory.createMethod(itemFactory.createType(itemFactory.arraysDescriptor), proto, name);
+    DexType target = itemFactory.createType("Ljava/util/DesugarArrays;");
+    builder.putRetargetCoreLibMember(source, target);
+
+    // TODO(b/181629049): This is only a workaround rewriting invokes of
+    //  j.u.TimeZone.getTimeZone taking a java.time.ZoneId.
+    name = itemFactory.createString("getTimeZone");
+    proto =
+        itemFactory.createProto(
+            itemFactory.createType("Ljava/util/TimeZone;"),
+            itemFactory.createType("Ljava/time/ZoneId;"));
+    source = itemFactory.createMethod(itemFactory.createType("Ljava/util/TimeZone;"), proto, name);
+    target = itemFactory.createType("Ljava/util/DesugarTimeZone;");
+    builder.putRetargetCoreLibMember(source, target);
+
+    libraryFlags.put(25, builder.build());
+  }
+
+  private DirectMappedDexApplication readApp(Path androidLib, InternalOptions options)
+      throws IOException {
+    AndroidApp androidApp = AndroidApp.builder().addLibraryFile(androidLib).build();
+    ApplicationReader applicationReader =
+        new ApplicationReader(androidApp, options, Timing.empty());
+    ExecutorService executorService = ThreadUtils.getExecutorService(options);
+    return applicationReader.read(executorService).toDirect();
+  }
+
+  private Int2ObjectArrayMap<HumanRewritingFlags> convertRewritingFlagMap(
+      Int2ObjectMap<LegacyRewritingFlags> libFlags, DexApplication app) {
+    Int2ObjectArrayMap<HumanRewritingFlags> map = new Int2ObjectArrayMap<>();
+    libFlags.forEach((key, flags) -> map.put((int) key, convertRewritingFlags(flags, app)));
+    return map;
+  }
+
+  private HumanRewritingFlags convertRewritingFlags(
+      LegacyRewritingFlags flags, DexApplication app) {
+    HumanRewritingFlags.Builder builder =
+        HumanRewritingFlags.builder(app.dexItemFactory(), app.options.reporter, Origin.unknown());
+
+    flags.getRewritePrefix().forEach(builder::putRewritePrefix);
+    flags.getEmulateLibraryInterface().forEach(builder::putEmulateLibraryInterface);
+    flags.getBackportCoreLibraryMember().forEach(builder::putBackportCoreLibraryMember);
+    flags.getCustomConversions().forEach(builder::putCustomConversion);
+    flags.getDontRetargetLibMember().forEach(builder::addDontRetargetLibMember);
+    flags.getWrapperConversions().forEach(builder::addWrapperConversion);
+
+    flags
+        .getRetargetCoreLibMember()
+        .forEach((name, typeMap) -> convertRetargetCoreLibMember(builder, app, name, typeMap));
+    flags
+        .getDontRewriteInvocation()
+        .forEach(pair -> convertDontRewriteInvocation(builder, app, pair));
+
+    return builder.build();
+  }
+
+  private void convertDontRewriteInvocation(
+      HumanRewritingFlags.Builder builder, DexApplication app, Pair<DexType, DexString> pair) {
+    DexClass dexClass = app.definitionFor(pair.getFirst());
+    assert dexClass != null;
+    List<DexClassAndMethod> methodsWithName = findMethodsWithName(pair.getSecond(), dexClass);
+    for (DexClassAndMethod dexClassAndMethod : methodsWithName) {
+      builder.addDontRewriteInvocation(dexClassAndMethod.getReference());
+    }
+  }
+
+  private void convertRetargetCoreLibMember(
+      HumanRewritingFlags.Builder builder,
+      DexApplication app,
+      DexString name,
+      Map<DexType, DexType> typeMap) {
+    typeMap.forEach(
+        (type, rewrittenType) -> {
+          DexClass dexClass = app.definitionFor(type);
+          assert dexClass != null;
+          List<DexClassAndMethod> methodsWithName = findMethodsWithName(name, dexClass);
+          for (DexClassAndMethod dexClassAndMethod : methodsWithName) {
+            builder.putRetargetCoreLibMember(dexClassAndMethod.getReference(), rewrittenType);
+          }
+        });
+  }
+
+  private List<DexClassAndMethod> findMethodsWithName(DexString methodName, DexClass clazz) {
+    List<DexClassAndMethod> found = new ArrayList<>();
+    clazz.forEachClassMethodMatching(definition -> definition.getName() == methodName, found::add);
+    assert !found.isEmpty()
+        : "Should have found a method (library specifications) for "
+            + clazz.toSourceString()
+            + "."
+            + methodName
+            + ". Maybe the library used for the compilation should be newer.";
+    return found;
+  }
+
+  private HumanTopLevelFlags convertTopLevelFlags(LegacyTopLevelFlags topLevelFlags) {
+    return HumanTopLevelFlags.builder()
+        .setDesugaredLibraryIdentifier(topLevelFlags.getIdentifier())
+        .setExtraKeepRules(topLevelFlags.getExtraKeepRules())
+        .setJsonSource(topLevelFlags.getJsonSource())
+        .setRequiredCompilationAPILevel(topLevelFlags.getRequiredCompilationAPILevel())
+        .setSupportAllCallbacksFromLibrary(topLevelFlags.supportAllCallbacksFromLibrary())
+        .setSynthesizedLibraryClassesPackagePrefix(
+            topLevelFlags.getSynthesizedLibraryClassesPackagePrefix())
+        .build();
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/specificationconversion/SpecificationConverter.java b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/specificationconversion/SpecificationConverter.java
new file mode 100644
index 0000000..85a8c8b
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/specificationconversion/SpecificationConverter.java
@@ -0,0 +1,16 @@
+// Copyright (c) 2021, 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.specificationconversion;
+
+import com.android.tools.r8.StringConsumer;
+import com.android.tools.r8.StringResource;
+import java.io.IOException;
+import java.nio.file.Path;
+
+public interface SpecificationConverter {
+
+  void convertAllAPILevels(
+      StringResource inputSpecification, Path androidLib, StringConsumer output) throws IOException;
+}
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
new file mode 100644
index 0000000..5797acf
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/specification/ConvertExportReadTest.java
@@ -0,0 +1,125 @@
+// Copyright (c) 2021, 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.desugar.desugaredlibrary.specification;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+import com.android.tools.r8.StringResource;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.ToolHelper;
+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;
+import com.android.tools.r8.ir.desugar.desugaredlibrary.humanspecification.MultiAPILevelHumanDesugaredLibrarySpecificationJsonExporter;
+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.specificationconversion.LegacyToHumanSpecificationConverter;
+import com.android.tools.r8.origin.Origin;
+import com.android.tools.r8.utils.Box;
+import com.android.tools.r8.utils.InternalOptions;
+import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
+import java.io.IOException;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class ConvertExportReadTest extends TestBase {
+
+  private final TestParameters parameters;
+
+  @Parameterized.Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withNoneRuntime().build();
+  }
+
+  public ConvertExportReadTest(TestParameters parameters) {
+    this.parameters = parameters;
+  }
+
+  @Test
+  public void testMultiLevel() throws IOException {
+    LegacyToHumanSpecificationConverter converter = new LegacyToHumanSpecificationConverter();
+
+    InternalOptions options = new InternalOptions();
+
+    MultiAPILevelLegacyDesugaredLibrarySpecification spec =
+        new MultiAPILevelLegacyDesugaredLibrarySpecificationParser(
+                options.dexItemFactory(), options.reporter)
+            .parseMultiLevelConfiguration(
+                StringResource.fromFile(ToolHelper.getDesugarLibJsonForTesting()));
+
+    MultiAPILevelHumanDesugaredLibrarySpecification humanSpec1 =
+        converter.convertAllAPILevels(spec, ToolHelper.getAndroidJar(31), options);
+
+    Box<String> json = new Box<>();
+    MultiAPILevelHumanDesugaredLibrarySpecificationJsonExporter.export(
+        humanSpec1, (string, handler) -> json.set(string));
+    MultiAPILevelHumanDesugaredLibrarySpecification humanSpec2 =
+        new MultiAPILevelHumanDesugaredLibrarySpecificationParser(
+                options.dexItemFactory(), options.reporter)
+            .parseMultiLevelConfiguration(StringResource.fromString(json.get(), Origin.unknown()));
+
+    assertSpecEquals(humanSpec1, humanSpec2);
+  }
+
+  private void assertSpecEquals(
+      MultiAPILevelHumanDesugaredLibrarySpecification humanSpec1,
+      MultiAPILevelHumanDesugaredLibrarySpecification humanSpec2) {
+    assertTopLevelFlagsEquals(humanSpec1.getTopLevelFlags(), humanSpec2.getTopLevelFlags());
+    assertFlagMapEquals(humanSpec1.getCommonFlags(), humanSpec2.getCommonFlags());
+  }
+
+  private void assertFlagMapEquals(
+      Int2ObjectMap<HumanRewritingFlags> commonFlags1,
+      Int2ObjectMap<HumanRewritingFlags> commonFlags2) {
+    assertEquals(commonFlags1.size(), commonFlags2.size());
+    for (int integer : commonFlags1.keySet()) {
+      assertTrue(commonFlags2.containsKey(integer));
+      assertFlagsEquals(commonFlags1.get(integer), commonFlags2.get(integer));
+    }
+  }
+
+  private void assertFlagsEquals(
+      HumanRewritingFlags humanRewritingFlags1, HumanRewritingFlags humanRewritingFlags2) {
+    assertEquals(humanRewritingFlags1.getRewritePrefix(), humanRewritingFlags2.getRewritePrefix());
+    assertEquals(
+        humanRewritingFlags1.getBackportCoreLibraryMember(),
+        humanRewritingFlags2.getBackportCoreLibraryMember());
+    assertEquals(
+        humanRewritingFlags1.getCustomConversions(), humanRewritingFlags2.getCustomConversions());
+    assertEquals(
+        humanRewritingFlags1.getEmulateLibraryInterface(),
+        humanRewritingFlags2.getEmulateLibraryInterface());
+    assertEquals(
+        humanRewritingFlags1.getRetargetCoreLibMember(),
+        humanRewritingFlags2.getRetargetCoreLibMember());
+
+    assertEquals(
+        humanRewritingFlags1.getDontRetargetLibMember(),
+        humanRewritingFlags2.getDontRetargetLibMember());
+    assertEquals(
+        humanRewritingFlags1.getDontRewriteInvocation(),
+        humanRewritingFlags2.getDontRewriteInvocation());
+    assertEquals(
+        humanRewritingFlags1.getWrapperConversions(), humanRewritingFlags2.getWrapperConversions());
+  }
+
+  private void assertTopLevelFlagsEquals(
+      HumanTopLevelFlags topLevelFlags1, HumanTopLevelFlags topLevelFlags2) {
+    assertEquals(topLevelFlags1.getExtraKeepRules(), topLevelFlags2.getExtraKeepRules());
+    assertEquals(topLevelFlags1.getIdentifier(), topLevelFlags2.getIdentifier());
+    assertEquals(
+        topLevelFlags1.getRequiredCompilationAPILevel().getLevel(),
+        topLevelFlags2.getRequiredCompilationAPILevel().getLevel());
+    assertEquals(
+        topLevelFlags1.getSynthesizedLibraryClassesPackagePrefix(),
+        topLevelFlags2.getSynthesizedLibraryClassesPackagePrefix());
+  }
+}