Fix desugared library human exporter

This is used to canonicalize flags to limit
diffs with internal flags.

Change-Id: I3547cd4baa3ef12e7d85303fcfa3775ea3459b9e
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
index d77a0bc..736632a 100644
--- 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
@@ -6,6 +6,7 @@
 
 import static com.android.tools.r8.ir.desugar.desugaredlibrary.DesugaredLibrarySpecificationParser.CONFIGURATION_FORMAT_VERSION_KEY;
 import static com.android.tools.r8.ir.desugar.desugaredlibrary.humanspecification.HumanDesugaredLibrarySpecificationParser.AMEND_LIBRARY_METHOD_KEY;
+import static com.android.tools.r8.ir.desugar.desugaredlibrary.humanspecification.HumanDesugaredLibrarySpecificationParser.API_GENERIC_TYPES_CONVERSION;
 import static com.android.tools.r8.ir.desugar.desugaredlibrary.humanspecification.HumanDesugaredLibrarySpecificationParser.API_LEVEL_BELOW_OR_EQUAL_KEY;
 import static com.android.tools.r8.ir.desugar.desugaredlibrary.humanspecification.HumanDesugaredLibrarySpecificationParser.API_LEVEL_GREATER_OR_EQUAL_KEY;
 import static com.android.tools.r8.ir.desugar.desugaredlibrary.humanspecification.HumanDesugaredLibrarySpecificationParser.BACKPORT_KEY;
@@ -14,13 +15,17 @@
 import static com.android.tools.r8.ir.desugar.desugaredlibrary.humanspecification.HumanDesugaredLibrarySpecificationParser.CUSTOM_CONVERSION_KEY;
 import static com.android.tools.r8.ir.desugar.desugaredlibrary.humanspecification.HumanDesugaredLibrarySpecificationParser.DONT_RETARGET_KEY;
 import static com.android.tools.r8.ir.desugar.desugaredlibrary.humanspecification.HumanDesugaredLibrarySpecificationParser.DONT_REWRITE_KEY;
+import static com.android.tools.r8.ir.desugar.desugaredlibrary.humanspecification.HumanDesugaredLibrarySpecificationParser.DONT_REWRITE_PREFIX_KEY;
 import static com.android.tools.r8.ir.desugar.desugaredlibrary.humanspecification.HumanDesugaredLibrarySpecificationParser.EMULATE_INTERFACE_KEY;
 import static com.android.tools.r8.ir.desugar.desugaredlibrary.humanspecification.HumanDesugaredLibrarySpecificationParser.IDENTIFIER_KEY;
 import static com.android.tools.r8.ir.desugar.desugaredlibrary.humanspecification.HumanDesugaredLibrarySpecificationParser.LIBRARY_FLAGS_KEY;
+import static com.android.tools.r8.ir.desugar.desugaredlibrary.humanspecification.HumanDesugaredLibrarySpecificationParser.MAINTAIN_PREFIX_KEY;
+import static com.android.tools.r8.ir.desugar.desugaredlibrary.humanspecification.HumanDesugaredLibrarySpecificationParser.NEVER_OUTLINE_API_KEY;
 import static com.android.tools.r8.ir.desugar.desugaredlibrary.humanspecification.HumanDesugaredLibrarySpecificationParser.PROGRAM_FLAGS_KEY;
 import static com.android.tools.r8.ir.desugar.desugaredlibrary.humanspecification.HumanDesugaredLibrarySpecificationParser.REQUIRED_COMPILATION_API_LEVEL_KEY;
 import static com.android.tools.r8.ir.desugar.desugaredlibrary.humanspecification.HumanDesugaredLibrarySpecificationParser.RETARGET_METHOD_EMULATED_DISPATCH_KEY;
 import static com.android.tools.r8.ir.desugar.desugaredlibrary.humanspecification.HumanDesugaredLibrarySpecificationParser.RETARGET_METHOD_KEY;
+import static com.android.tools.r8.ir.desugar.desugaredlibrary.humanspecification.HumanDesugaredLibrarySpecificationParser.RETARGET_STATIC_FIELD_KEY;
 import static com.android.tools.r8.ir.desugar.desugaredlibrary.humanspecification.HumanDesugaredLibrarySpecificationParser.REWRITE_DERIVED_PREFIX_KEY;
 import static com.android.tools.r8.ir.desugar.desugaredlibrary.humanspecification.HumanDesugaredLibrarySpecificationParser.REWRITE_PREFIX_KEY;
 import static com.android.tools.r8.ir.desugar.desugaredlibrary.humanspecification.HumanDesugaredLibrarySpecificationParser.SHRINKER_CONFIG_KEY;
@@ -32,13 +37,15 @@
 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.AccessFlags;
+import com.android.tools.r8.graph.DexField;
 import com.android.tools.r8.graph.DexItem;
 import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.graph.DexType;
-import com.android.tools.r8.graph.MethodAccessFlags;
 import com.android.tools.r8.ir.desugar.desugaredlibrary.ApiLevelRange;
 import com.google.gson.Gson;
 import java.util.ArrayList;
+import java.util.Collections;
 import java.util.HashMap;
 import java.util.LinkedHashMap;
 import java.util.List;
@@ -102,12 +109,21 @@
             .forEach((k, v) -> rewriteDerivedPrefix.put(k, new TreeMap<>(v)));
         toJson.put(REWRITE_DERIVED_PREFIX_KEY, rewriteDerivedPrefix);
       }
+      if (!flags.getDontRewritePrefix().isEmpty()) {
+        toJson.put(DONT_REWRITE_PREFIX_KEY, stringSetToString(flags.getDontRewritePrefix()));
+      }
+      if (!flags.getMaintainPrefix().isEmpty()) {
+        toJson.put(MAINTAIN_PREFIX_KEY, stringSetToString(flags.getMaintainPrefix()));
+      }
       if (!flags.getEmulatedInterfaces().isEmpty()) {
         toJson.put(EMULATE_INTERFACE_KEY, mapToString(flags.getEmulatedInterfaces()));
       }
       if (!flags.getDontRewriteInvocation().isEmpty()) {
         toJson.put(DONT_REWRITE_KEY, setToString(flags.getDontRewriteInvocation()));
       }
+      if (!flags.getRetargetStaticField().isEmpty()) {
+        toJson.put(RETARGET_STATIC_FIELD_KEY, mapToString(flags.getRetargetStaticField()));
+      }
       if (!flags.getRetargetMethod().isEmpty()) {
         toJson.put(RETARGET_METHOD_KEY, mapToString(flags.getRetargetMethod()));
       }
@@ -122,15 +138,24 @@
       if (!flags.getLegacyBackport().isEmpty()) {
         toJson.put(BACKPORT_KEY, mapToString(flags.getLegacyBackport()));
       }
+      if (!flags.getApiGenericConversion().isEmpty()) {
+        toJson.put(API_GENERIC_TYPES_CONVERSION, mapArrayToString(flags.getApiGenericConversion()));
+      }
       if (!flags.getWrapperConversions().isEmpty()) {
         registerWrapperConversions(toJson, flags.getWrapperConversions());
       }
       if (!flags.getCustomConversions().isEmpty()) {
         toJson.put(CUSTOM_CONVERSION_KEY, mapToString(flags.getCustomConversions()));
       }
+      if (!flags.getNeverOutlineApi().isEmpty()) {
+        toJson.put(NEVER_OUTLINE_API_KEY, setToString(flags.getNeverOutlineApi()));
+      }
       if (!flags.getAmendLibraryMethod().isEmpty()) {
         toJson.put(AMEND_LIBRARY_METHOD_KEY, amendLibraryToString(flags.getAmendLibraryMethod()));
       }
+      if (!flags.getAmendLibraryField().isEmpty()) {
+        toJson.put(AMEND_LIBRARY_METHOD_KEY, amendLibraryToString(flags.getAmendLibraryField()));
+      }
       list.add(toJson);
     }
     return list;
@@ -148,20 +173,32 @@
             stringMap.put(toString(k), setToString(v));
           }
         });
-    toJson.put(WRAPPER_CONVERSION_KEY, stringSet);
-    toJson.put(WRAPPER_CONVERSION_EXCLUDING_KEY, stringMap);
+    if (!stringSet.isEmpty()) {
+      toJson.put(WRAPPER_CONVERSION_KEY, stringSet);
+    }
+    if (!stringMap.isEmpty()) {
+      toJson.put(WRAPPER_CONVERSION_EXCLUDING_KEY, stringMap);
+    }
   }
 
-  private List<String> amendLibraryToString(Map<DexMethod, MethodAccessFlags> amendLibraryMembers) {
+  private List<String> amendLibraryToString(
+      Map<? extends DexItem, ? extends AccessFlags<?>> amendLibraryMembers) {
     List<String> stringSet = new ArrayList<>();
     amendLibraryMembers.forEach(
         (member, flags) -> stringSet.add(flags.toString() + " " + toString(member)));
     return stringSet;
   }
 
+  private List<String> stringSetToString(Set<String> set) {
+    List<String> stringSet = new ArrayList<>(set);
+    Collections.sort(stringSet);
+    return stringSet;
+  }
+
   private List<String> setToString(Set<? extends DexItem> set) {
     List<String> stringSet = new ArrayList<>();
     set.forEach(e -> stringSet.add(toString(e)));
+    Collections.sort(stringSet);
     return stringSet;
   }
 
@@ -171,10 +208,36 @@
     return stringMap;
   }
 
+  private Map<String, Object[]> mapArrayToString(Map<? extends DexItem, DexMethod[]> map) {
+    Map<String, Object[]> stringMap = new TreeMap<>();
+    map.forEach((k, v) -> stringMap.put(toString(k), arrayToString(v)));
+    return stringMap;
+  }
+
+  private Object[] arrayToString(DexMethod[] array) {
+    List<Object> res = new ArrayList<>();
+    int last = array.length - 1;
+    if (array[last] != null) {
+      res.add(-1);
+      res.add(toString(array[last]));
+    }
+    for (int i = 0; i < array.length - 1; i++) {
+      if (array[i] != null) {
+        res.add(i);
+        res.add(toString(array[i]));
+      }
+    }
+    return res.toArray();
+  }
+
   private String toString(DexItem o) {
     if (o instanceof DexType) {
       return o.toString();
     }
+    if (o instanceof DexField) {
+      DexField field = (DexField) o;
+      return field.getType() + " " + field.getHolderType() + "#" + field.getName();
+    }
     if (o instanceof DexMethod) {
       DexMethod method = (DexMethod) o;
       StringBuilder sb =
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/specificationconversion/DesugaredLibraryConverter.java b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/specificationconversion/DesugaredLibraryConverter.java
index 34a8e9d..e14a21e 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/specificationconversion/DesugaredLibraryConverter.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/specificationconversion/DesugaredLibraryConverter.java
@@ -73,7 +73,7 @@
     Files.write(output, Collections.singleton(outputString));
   }
 
-  private static String convertToMachineSpecification(
+  public static String convertToMachineSpecification(
       InternalOptions options,
       DexApplication appForConversion,
       MultiAPILevelHumanDesugaredLibrarySpecification humanSpec)
@@ -88,7 +88,7 @@
     return machineJson.get();
   }
 
-  private static JsonObject parseJsonConfig(InternalOptions options, FileResource jsonResource) {
+  public static JsonObject parseJsonConfig(InternalOptions options, FileResource jsonResource) {
     JsonObject jsonConfig;
     try {
       String jsonConfigString = jsonResource.getString();
@@ -104,7 +104,7 @@
    * Parse the human specification, or parse and convert the legacy specification into human
    * specification.
    */
-  private static MultiAPILevelHumanDesugaredLibrarySpecification getInputAsHumanSpecification(
+  public static MultiAPILevelHumanDesugaredLibrarySpecification getInputAsHumanSpecification(
       InternalOptions options,
       FileResource jsonResource,
       JsonObject jsonConfig,
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 468494f..535d82a 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,12 +4,17 @@
 
 package com.android.tools.r8.desugar.desugaredlibrary.specification;
 
+import static com.android.tools.r8.ir.desugar.desugaredlibrary.DesugaredLibrarySpecificationParser.isMachineSpecification;
+import static com.android.tools.r8.ir.desugar.desugaredlibrary.specificationconversion.DesugaredLibraryConverter.convertToMachineSpecification;
+import static com.android.tools.r8.ir.desugar.desugaredlibrary.specificationconversion.DesugaredLibraryConverter.getInputAsHumanSpecification;
+import static com.android.tools.r8.ir.desugar.desugaredlibrary.specificationconversion.DesugaredLibraryConverter.parseJsonConfig;
 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;
+import com.android.tools.r8.StringResource.FileResource;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestParametersCollection;
 import com.android.tools.r8.ToolHelper;
@@ -32,7 +37,6 @@
 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.DesugaredLibraryConverter;
 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;
@@ -41,8 +45,11 @@
 import com.android.tools.r8.utils.Box;
 import com.android.tools.r8.utils.InternalOptions;
 import com.android.tools.r8.utils.Timing;
+import com.google.gson.JsonObject;
 import java.io.IOException;
+import java.nio.file.Files;
 import java.nio.file.Path;
+import java.util.Collections;
 import java.util.Map;
 import org.junit.Assume;
 import org.junit.Test;
@@ -126,11 +133,11 @@
   private void testMultiLevelUsingMain(LibraryDesugaringSpecification spec) throws IOException {
     Assume.assumeTrue(ToolHelper.isLocalDevelopment());
 
-    Path output = temp.newFile().toPath();
-    DesugaredLibraryConverter.convertMultiLevelAnythingToMachineSpecification(
-        spec.getSpecification(), spec.getDesugarJdkLibs(), spec.getLibraryFiles(), output);
-
     InternalOptions options = new InternalOptions();
+
+    Path output = temp.newFile().toPath();
+    convertMultiLevelAnythingToMachineSpecification(spec, output, options);
+
     MachineDesugaredLibrarySpecification machineSpecParsed =
         new MachineDesugaredLibrarySpecificationParser(
                 options.dexItemFactory(),
@@ -142,6 +149,36 @@
     assertFalse(machineSpecParsed.getRewriteType().isEmpty());
   }
 
+  public void convertMultiLevelAnythingToMachineSpecification(
+      LibraryDesugaringSpecification spec, Path output, InternalOptions options)
+      throws IOException {
+
+    FileResource jsonResource = StringResource.fromFile(spec.getSpecification());
+    JsonObject jsonConfig = parseJsonConfig(options, jsonResource);
+
+    if (isMachineSpecification(jsonConfig, options.reporter, jsonResource.getOrigin())) {
+      // Nothing to convert;
+      Files.copy(spec.getSpecification(), output);
+      return;
+    }
+
+    DexApplication appForConversion = spec.getAppForTesting(options, true);
+    MultiAPILevelHumanDesugaredLibrarySpecification humanSpec =
+        getInputAsHumanSpecification(options, jsonResource, jsonConfig, appForConversion);
+    String outputString = convertToMachineSpecification(options, appForConversion, humanSpec);
+    Files.write(output, Collections.singleton(outputString));
+
+    // Validate the written spec is the read spec.
+    Box<String> json = new Box<>();
+    MultiAPILevelHumanDesugaredLibrarySpecificationJsonExporter.export(
+        humanSpec, ((string, handler) -> json.set(string)));
+    MultiAPILevelHumanDesugaredLibrarySpecification writtenHumanSpec =
+        new MultiAPILevelHumanDesugaredLibrarySpecificationParser(
+                options.dexItemFactory(), options.reporter)
+            .parseMultiLevelConfiguration(StringResource.fromString(json.get(), Origin.unknown()));
+    assertSpecEquals(humanSpec, writtenHumanSpec);
+  }
+
   @Test
   public void testSingleLevel() throws IOException {
     Assume.assumeTrue(ToolHelper.isLocalDevelopment());