Add API to get user-friendly message for missing definition info

This also ensures that the collection returned by getMissingDefinitions() has deterministic order.

Change-Id: I2f8d60ddc19018f7be5c9033fa093691b2ec13cc
diff --git a/src/main/java/com/android/tools/r8/diagnostic/MissingDefinitionInfo.java b/src/main/java/com/android/tools/r8/diagnostic/MissingDefinitionInfo.java
index c2905ce..ec486de 100644
--- a/src/main/java/com/android/tools/r8/diagnostic/MissingDefinitionInfo.java
+++ b/src/main/java/com/android/tools/r8/diagnostic/MissingDefinitionInfo.java
@@ -64,6 +64,9 @@
     return null;
   }
 
+  /** User friendly description of the missing definition. */
+  String getDiagnosticMessage();
+
   /** The contexts from which this missing definition was referenced. */
   Collection<MissingDefinitionContext> getReferencedFromContexts();
 }
diff --git a/src/main/java/com/android/tools/r8/diagnostic/internal/MissingDefinitionInfoBase.java b/src/main/java/com/android/tools/r8/diagnostic/internal/MissingDefinitionInfoBase.java
index 0f9e655..b3d2872 100644
--- a/src/main/java/com/android/tools/r8/diagnostic/internal/MissingDefinitionInfoBase.java
+++ b/src/main/java/com/android/tools/r8/diagnostic/internal/MissingDefinitionInfoBase.java
@@ -18,6 +18,13 @@
   }
 
   @Override
+  public String getDiagnosticMessage() {
+    StringBuilder builder = new StringBuilder();
+    MissingDefinitionInfoUtils.writeDiagnosticMessage(builder, this);
+    return builder.toString();
+  }
+
+  @Override
   public final Collection<MissingDefinitionContext> getReferencedFromContexts() {
     return referencedFromContexts;
   }
diff --git a/src/main/java/com/android/tools/r8/diagnostic/internal/MissingDefinitionInfoUtils.java b/src/main/java/com/android/tools/r8/diagnostic/internal/MissingDefinitionInfoUtils.java
index b94dbf1..34f2f2c 100644
--- a/src/main/java/com/android/tools/r8/diagnostic/internal/MissingDefinitionInfoUtils.java
+++ b/src/main/java/com/android/tools/r8/diagnostic/internal/MissingDefinitionInfoUtils.java
@@ -4,13 +4,19 @@
 
 package com.android.tools.r8.diagnostic.internal;
 
+import static com.android.tools.r8.utils.ClassReferenceUtils.getClassReferenceComparator;
+import static com.android.tools.r8.utils.FieldReferenceUtils.getFieldReferenceComparator;
+import static com.android.tools.r8.utils.MethodReferenceUtils.getMethodReferenceComparator;
+
 import com.android.tools.r8.diagnostic.MissingClassInfo;
+import com.android.tools.r8.diagnostic.MissingDefinitionContext;
 import com.android.tools.r8.diagnostic.MissingDefinitionInfo;
 import com.android.tools.r8.diagnostic.MissingFieldInfo;
 import com.android.tools.r8.diagnostic.MissingMethodInfo;
 import com.android.tools.r8.references.ClassReference;
 import com.android.tools.r8.references.FieldReference;
 import com.android.tools.r8.references.MethodReference;
+import com.android.tools.r8.utils.Box;
 import com.android.tools.r8.utils.ClassReferenceUtils;
 import com.android.tools.r8.utils.FieldReferenceUtils;
 import com.android.tools.r8.utils.MethodReferenceUtils;
@@ -77,4 +83,65 @@
   public static Comparator<MissingDefinitionInfo> getComparator() {
     return COMPARATOR;
   }
+
+  public static void writeDiagnosticMessage(
+      StringBuilder builder, MissingDefinitionInfo missingDefinitionInfo) {
+    builder.append("Missing class ");
+    MissingDefinitionInfoUtils.accept(
+        missingDefinitionInfo,
+        missingClassInfo -> builder.append(missingClassInfo.getClassReference().getTypeName()),
+        missingFieldInfo ->
+            builder.append(
+                FieldReferenceUtils.toSourceString(missingFieldInfo.getFieldReference())),
+        missingMethodInfo ->
+            builder.append(
+                MethodReferenceUtils.toSourceString(missingMethodInfo.getMethodReference())));
+    writeReferencedFromSuffix(builder, missingDefinitionInfo);
+  }
+
+  private static void writeReferencedFromSuffix(
+      StringBuilder builder, MissingDefinitionInfo missingDefinitionInfo) {
+    Box<ClassReference> classContext = new Box<>();
+    Box<FieldReference> fieldContext = new Box<>();
+    Box<MethodReference> methodContext = new Box<>();
+    for (MissingDefinitionContext missingDefinitionContext :
+        missingDefinitionInfo.getReferencedFromContexts()) {
+      MissingDefinitionContextUtils.accept(
+          missingDefinitionContext,
+          missingDefinitionClassContext ->
+              classContext.setMin(
+                  missingDefinitionClassContext.getClassReference(), getClassReferenceComparator()),
+          missingDefinitionFieldContext ->
+              fieldContext.setMin(
+                  missingDefinitionFieldContext.getFieldReference(), getFieldReferenceComparator()),
+          missingDefinitionMethodContext ->
+              methodContext.setMin(
+                  missingDefinitionMethodContext.getMethodReference(),
+                  getMethodReferenceComparator()));
+    }
+    assert classContext.isSet() || fieldContext.isSet() || methodContext.isSet();
+    if (fieldContext.isSet()) {
+      writeReferencedFromSuffix(
+          builder, missingDefinitionInfo, FieldReferenceUtils.toSourceString(fieldContext.get()));
+    } else if (methodContext.isSet()) {
+      writeReferencedFromSuffix(
+          builder, missingDefinitionInfo, MethodReferenceUtils.toSourceString(methodContext.get()));
+    } else {
+      writeReferencedFromSuffix(builder, missingDefinitionInfo, classContext.get().getTypeName());
+    }
+  }
+
+  private static void writeReferencedFromSuffix(
+      StringBuilder builder, MissingDefinitionInfo missingDefinitionInfo, String referencedFrom) {
+    int numberOfOtherContexts = missingDefinitionInfo.getReferencedFromContexts().size() - 1;
+    assert numberOfOtherContexts >= 0;
+    builder.append(" (referenced from: ").append(referencedFrom);
+    if (numberOfOtherContexts >= 1) {
+      builder.append(" and ").append(numberOfOtherContexts).append(" other context");
+      if (numberOfOtherContexts >= 2) {
+        builder.append("s");
+      }
+    }
+    builder.append(")");
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/diagnostic/internal/MissingDefinitionsDiagnosticImpl.java b/src/main/java/com/android/tools/r8/diagnostic/internal/MissingDefinitionsDiagnosticImpl.java
index 7be87b6..a180ed2 100644
--- a/src/main/java/com/android/tools/r8/diagnostic/internal/MissingDefinitionsDiagnosticImpl.java
+++ b/src/main/java/com/android/tools/r8/diagnostic/internal/MissingDefinitionsDiagnosticImpl.java
@@ -4,21 +4,10 @@
 
 package com.android.tools.r8.diagnostic.internal;
 
-import static com.android.tools.r8.utils.ClassReferenceUtils.getClassReferenceComparator;
-import static com.android.tools.r8.utils.FieldReferenceUtils.getFieldReferenceComparator;
-import static com.android.tools.r8.utils.MethodReferenceUtils.getMethodReferenceComparator;
-
-import com.android.tools.r8.diagnostic.MissingDefinitionContext;
 import com.android.tools.r8.diagnostic.MissingDefinitionInfo;
 import com.android.tools.r8.diagnostic.MissingDefinitionsDiagnostic;
 import com.android.tools.r8.origin.Origin;
 import com.android.tools.r8.position.Position;
-import com.android.tools.r8.references.ClassReference;
-import com.android.tools.r8.references.FieldReference;
-import com.android.tools.r8.references.MethodReference;
-import com.android.tools.r8.utils.Box;
-import com.android.tools.r8.utils.FieldReferenceUtils;
-import com.android.tools.r8.utils.MethodReferenceUtils;
 import com.google.common.collect.ImmutableList;
 import java.util.ArrayList;
 import java.util.Collection;
@@ -40,12 +29,12 @@
 
   @Override
   public Collection<MissingDefinitionInfo> getMissingDefinitions() {
-    return missingDefinitions;
+    return getMissingDefinitionsWithDeterministicOrder();
   }
 
   private Collection<MissingDefinitionInfo> getMissingDefinitionsWithDeterministicOrder() {
     List<MissingDefinitionInfo> missingDefinitionsWithDeterministicOrder =
-        new ArrayList<>(getMissingDefinitions());
+        new ArrayList<>(missingDefinitions);
     missingDefinitionsWithDeterministicOrder.sort(MissingDefinitionInfoUtils.getComparator());
     return missingDefinitionsWithDeterministicOrder;
   }
@@ -72,78 +61,17 @@
     assert missingDefinitionsIterator.hasNext();
 
     // Write first line.
-    writeMissingDefinition(builder.append("Missing class "), missingDefinitionsIterator.next());
+    MissingDefinitionInfoUtils.writeDiagnosticMessage(builder, missingDefinitionsIterator.next());
 
     // Write remaining lines with line separator before.
     missingDefinitionsIterator.forEachRemaining(
         missingDefinition ->
-            writeMissingDefinition(
-                builder.append(System.lineSeparator()).append("Missing class "),
-                missingDefinition));
+            MissingDefinitionInfoUtils.writeDiagnosticMessage(
+                builder.append(System.lineSeparator()), missingDefinition));
 
     return builder.toString();
   }
 
-  private static void writeMissingDefinition(
-      StringBuilder builder, MissingDefinitionInfo missingDefinitionInfo) {
-    MissingDefinitionInfoUtils.accept(
-        missingDefinitionInfo,
-        missingClassInfo -> builder.append(missingClassInfo.getClassReference().getTypeName()),
-        missingFieldInfo ->
-            builder.append(
-                FieldReferenceUtils.toSourceString(missingFieldInfo.getFieldReference())),
-        missingMethodInfo ->
-            builder.append(
-                MethodReferenceUtils.toSourceString(missingMethodInfo.getMethodReference())));
-    writeReferencedFromSuffix(builder, missingDefinitionInfo);
-  }
-
-  private static void writeReferencedFromSuffix(
-      StringBuilder builder, MissingDefinitionInfo missingDefinitionInfo) {
-    Box<ClassReference> classContext = new Box<>();
-    Box<FieldReference> fieldContext = new Box<>();
-    Box<MethodReference> methodContext = new Box<>();
-    for (MissingDefinitionContext missingDefinitionContext :
-        missingDefinitionInfo.getReferencedFromContexts()) {
-      MissingDefinitionContextUtils.accept(
-          missingDefinitionContext,
-          missingDefinitionClassContext ->
-              classContext.setMin(
-                  missingDefinitionClassContext.getClassReference(), getClassReferenceComparator()),
-          missingDefinitionFieldContext ->
-              fieldContext.setMin(
-                  missingDefinitionFieldContext.getFieldReference(), getFieldReferenceComparator()),
-          missingDefinitionMethodContext ->
-              methodContext.setMin(
-                  missingDefinitionMethodContext.getMethodReference(),
-                  getMethodReferenceComparator()));
-    }
-    assert classContext.isSet() || fieldContext.isSet() || methodContext.isSet();
-    if (fieldContext.isSet()) {
-      writeReferencedFromSuffix(
-          builder, missingDefinitionInfo, FieldReferenceUtils.toSourceString(fieldContext.get()));
-    } else if (methodContext.isSet()) {
-      writeReferencedFromSuffix(
-          builder, missingDefinitionInfo, MethodReferenceUtils.toSourceString(methodContext.get()));
-    } else {
-      writeReferencedFromSuffix(builder, missingDefinitionInfo, classContext.get().getTypeName());
-    }
-  }
-
-  private static void writeReferencedFromSuffix(
-      StringBuilder builder, MissingDefinitionInfo missingDefinitionInfo, String referencedFrom) {
-    int numberOfOtherContexts = missingDefinitionInfo.getReferencedFromContexts().size() - 1;
-    assert numberOfOtherContexts >= 0;
-    builder.append(" (referenced from: ").append(referencedFrom);
-    if (numberOfOtherContexts >= 1) {
-      builder.append(", and ").append(numberOfOtherContexts).append(" other context");
-      if (numberOfOtherContexts >= 2) {
-        builder.append("s");
-      }
-    }
-    builder.append(")");
-  }
-
   public static class Builder {
 
     private ImmutableList.Builder<MissingDefinitionInfo> missingDefinitionsBuilder =
diff --git a/src/test/java/com/android/tools/r8/missingclasses/MissingClassesTestBase.java b/src/test/java/com/android/tools/r8/missingclasses/MissingClassesTestBase.java
index 8dfba19..3022ea4 100644
--- a/src/test/java/com/android/tools/r8/missingclasses/MissingClassesTestBase.java
+++ b/src/test/java/com/android/tools/r8/missingclasses/MissingClassesTestBase.java
@@ -169,7 +169,7 @@
             .append(" (referenced from: ")
             .append(referencedFrom);
     if (numberOfContexts > 1) {
-      builder.append(", and ").append(numberOfContexts - 1).append(" other context");
+      builder.append(" and ").append(numberOfContexts - 1).append(" other context");
       if (numberOfContexts > 2) {
         builder.append("s");
       }