Introduce MainDexOverflowDiagnostic.

This CL also moves specialized messages to custom D8/R8 diagnostics handlers.

Change-Id: I2639df8aa30cdd2fb5880a30b864d812c791f66e
diff --git a/src/main/java/com/android/tools/r8/BaseCompilerCommand.java b/src/main/java/com/android/tools/r8/BaseCompilerCommand.java
index 6ae611c..297d6df 100644
--- a/src/main/java/com/android/tools/r8/BaseCompilerCommand.java
+++ b/src/main/java/com/android/tools/r8/BaseCompilerCommand.java
@@ -36,7 +36,7 @@
     programConsumer = null;
     mode = null;
     minApiLevel = 0;
-    reporter = new Reporter(new DefaultDiagnosticsHandler(), this);
+    reporter = new Reporter(new DefaultDiagnosticsHandler());
     enableDesugaring = true;
     optimizeMultidexForLinearAlloc = false;
   }
diff --git a/src/main/java/com/android/tools/r8/D8Command.java b/src/main/java/com/android/tools/r8/D8Command.java
index 19c1bd6..4c5e0b9 100644
--- a/src/main/java/com/android/tools/r8/D8Command.java
+++ b/src/main/java/com/android/tools/r8/D8Command.java
@@ -3,11 +3,14 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8;
 
+import com.android.tools.r8.errors.DexFileOverflowDiagnostic;
 import com.android.tools.r8.graph.DexItemFactory;
 import com.android.tools.r8.origin.Origin;
 import com.android.tools.r8.utils.AndroidApp;
+import com.android.tools.r8.utils.DefaultDiagnosticsHandler;
 import com.android.tools.r8.utils.InternalOptions;
 import com.android.tools.r8.utils.Reporter;
+import com.android.tools.r8.utils.StringDiagnostic;
 import java.nio.file.Path;
 import java.util.Arrays;
 import java.util.Collection;
@@ -35,6 +38,23 @@
     }
   }
 
+  private static class DefaultD8DiagnosticsHandler extends DefaultDiagnosticsHandler {
+
+    @Override
+    public void error(Diagnostic error) {
+      if (error instanceof DexFileOverflowDiagnostic) {
+        DexFileOverflowDiagnostic overflowDiagnostic = (DexFileOverflowDiagnostic) error;
+        if (!overflowDiagnostic.hasMainDexSpecification()) {
+          super.error(
+              new StringDiagnostic(
+                  overflowDiagnostic.getDiagnosticMessage() + ". Try supplying a main-dex list"));
+          return;
+        }
+      }
+      super.error(error);
+    }
+  }
+
   /**
    * Builder for constructing a D8Command.
    *
@@ -45,7 +65,9 @@
 
     private boolean intermediate = false;
 
-    private Builder() {}
+    private Builder() {
+      this(new DefaultD8DiagnosticsHandler());
+    }
 
     private Builder(DiagnosticsHandler diagnosticsHandler) {
       super(diagnosticsHandler);
diff --git a/src/main/java/com/android/tools/r8/R8Command.java b/src/main/java/com/android/tools/r8/R8Command.java
index c53f367..0970a0c 100644
--- a/src/main/java/com/android/tools/r8/R8Command.java
+++ b/src/main/java/com/android/tools/r8/R8Command.java
@@ -4,6 +4,7 @@
 package com.android.tools.r8;
 
 import com.android.tools.r8.ProgramResource.Kind;
+import com.android.tools.r8.errors.DexFileOverflowDiagnostic;
 import com.android.tools.r8.graph.DexItemFactory;
 import com.android.tools.r8.origin.Origin;
 import com.android.tools.r8.origin.PathOrigin;
@@ -16,6 +17,7 @@
 import com.android.tools.r8.shaking.ProguardConfigurationSourceFile;
 import com.android.tools.r8.shaking.ProguardConfigurationSourceStrings;
 import com.android.tools.r8.utils.AndroidApp;
+import com.android.tools.r8.utils.DefaultDiagnosticsHandler;
 import com.android.tools.r8.utils.ExceptionDiagnostic;
 import com.android.tools.r8.utils.FileUtils;
 import com.android.tools.r8.utils.InternalOptions;
@@ -56,6 +58,24 @@
   @Keep
   public static class Builder extends BaseCompilerCommand.Builder<R8Command, Builder> {
 
+    private static class DefaultR8DiagnosticsHandler extends DefaultDiagnosticsHandler {
+
+      @Override
+      public void error(Diagnostic error) {
+        if (error instanceof DexFileOverflowDiagnostic) {
+          DexFileOverflowDiagnostic overflowDiagnostic = (DexFileOverflowDiagnostic) error;
+          if (!overflowDiagnostic.hasMainDexSpecification()) {
+            super.error(
+                new StringDiagnostic(
+                    overflowDiagnostic.getDiagnosticMessage()
+                        + ". Try supplying a main-dex list or main-dex rules"));
+            return;
+          }
+        }
+        super.error(error);
+      }
+    }
+
     private final List<ProguardConfigurationSource> mainDexRules = new ArrayList<>();
     private Consumer<ProguardConfiguration.Builder> proguardConfigurationConsumer = null;
     private final List<ProguardConfigurationSource> proguardConfigs = new ArrayList<>();
@@ -74,7 +94,9 @@
     private StringConsumer mainDexListConsumer = null;
 
     // TODO(zerny): Consider refactoring CompatProguardCommandBuilder to avoid subclassing.
-    Builder() {}
+    Builder() {
+      this(new DefaultR8DiagnosticsHandler());
+    }
 
     Builder(DiagnosticsHandler diagnosticsHandler) {
       super(diagnosticsHandler);
diff --git a/src/main/java/com/android/tools/r8/dex/VirtualFile.java b/src/main/java/com/android/tools/r8/dex/VirtualFile.java
index 7b012ce..d085859 100644
--- a/src/main/java/com/android/tools/r8/dex/VirtualFile.java
+++ b/src/main/java/com/android/tools/r8/dex/VirtualFile.java
@@ -3,8 +3,8 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.dex;
 
+import com.android.tools.r8.errors.DexFileOverflowDiagnostic;
 import com.android.tools.r8.errors.InternalCompilerError;
-import com.android.tools.r8.errors.MainDexOverflow;
 import com.android.tools.r8.graph.DexApplication;
 import com.android.tools.r8.graph.DexCallSite;
 import com.android.tools.r8.graph.DexClass;
@@ -190,11 +190,8 @@
       return;
     }
     throw reporter.fatalError(
-        new MainDexOverflow(
-            hasMainDexList,
-            transaction.getNumberOfMethods(),
-            transaction.getNumberOfFields(),
-            MAX_ENTRIES));
+        new DexFileOverflowDiagnostic(
+            hasMainDexList, transaction.getNumberOfMethods(), transaction.getNumberOfFields()));
   }
 
   private boolean isFilledEnough(FillStrategy fillStrategy) {
diff --git a/src/main/java/com/android/tools/r8/errors/DexFileOverflowDiagnostic.java b/src/main/java/com/android/tools/r8/errors/DexFileOverflowDiagnostic.java
new file mode 100644
index 0000000..9427603
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/errors/DexFileOverflowDiagnostic.java
@@ -0,0 +1,98 @@
+// Copyright (c) 2017, 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.errors;
+
+import com.android.tools.r8.Diagnostic;
+import com.android.tools.r8.Keep;
+import com.android.tools.r8.dex.VirtualFile;
+import com.android.tools.r8.origin.Origin;
+import com.android.tools.r8.position.Position;
+
+/**
+ * Diagnostic information about errors when classes cannot fit in a DEX file.
+ *
+ * <p>This can happen when compiling to a single DEX file but not all classes can fit in it; or when
+ * compiling for legacy multidex but there are too many classes that need to fit in the main DEX
+ * file, e.g., classes.dex.
+ */
+@Keep
+public class DexFileOverflowDiagnostic implements Diagnostic {
+  private final boolean hasMainDexSpecification;
+  private final long numOfMethods;
+  private final long numOfFields;
+
+  public DexFileOverflowDiagnostic(
+      boolean hasMainDexSpecification, long numOfMethods, long numOfFields) {
+    this.hasMainDexSpecification = hasMainDexSpecification;
+    this.numOfMethods = numOfMethods;
+    this.numOfFields = numOfFields;
+  }
+
+  /** The number of fields that the application needs to include in the main DEX file. */
+  public long getNumberOfFields() {
+    return numOfFields;
+  }
+
+  /** The number of methods that the application needs to include in the main DEX file. */
+  public long getNumberOfMethods() {
+    return numOfMethods;
+  }
+
+  /** The maximum number of fields that can be included in a DEX file. */
+  public long getMaximumNumberOfFields() {
+    return VirtualFile.MAX_ENTRIES;
+  }
+
+  /** The maximum number of methods that can be included in a DEX file. */
+  public long getMaximumNumberOfMethods() {
+    return VirtualFile.MAX_ENTRIES;
+  }
+
+  /** True if the application has specified lists and/or rules for computing the main DEX file. */
+  public boolean hasMainDexSpecification() {
+    return hasMainDexSpecification;
+  }
+
+  /** The origin of a main DEX file overflow is not unique. (The whole app is to blame.) */
+  @Override
+  public Origin getOrigin() {
+    return Origin.unknown();
+  }
+
+  /** The position of the main DEX error is not specified. */
+  @Override
+  public Position getPosition() {
+    return null;
+  }
+
+  @Override
+  public String getDiagnosticMessage() {
+    StringBuilder builder = new StringBuilder();
+    // General message: Cannot fit.
+    builder
+        .append("Cannot fit requested classes in ")
+        .append(hasMainDexSpecification() ? "the main-" : "a single ")
+        .append("dex file")
+        .append(" (");
+    // Show the numbers of methods and/or fields that exceed the limit.
+    if (getNumberOfMethods() > getMaximumNumberOfMethods()) {
+      builder
+          .append("# methods: ")
+          .append(getNumberOfMethods())
+          .append(" > ")
+          .append(getMaximumNumberOfMethods());
+      if (getNumberOfFields() > getMaximumNumberOfFields()) {
+        builder.append(" ; ");
+      }
+    }
+    if (getNumberOfFields() > getMaximumNumberOfFields()) {
+      builder
+          .append("# fields: ")
+          .append(getNumberOfFields())
+          .append(" > ")
+          .append(getMaximumNumberOfFields());
+    }
+    return builder.append(")").toString();
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/errors/MainDexOverflow.java b/src/main/java/com/android/tools/r8/errors/MainDexOverflow.java
deleted file mode 100644
index e33cf59..0000000
--- a/src/main/java/com/android/tools/r8/errors/MainDexOverflow.java
+++ /dev/null
@@ -1,85 +0,0 @@
-// Copyright (c) 2017, 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.errors;
-
-/**
- * Info about error when running mono dex and not all classes can fit in a dex or when running for
- * multidex legacy and there are too many classes to fit in the main dex.
- */
-public class MainDexOverflow {
-  private final boolean hasMainDexList;
-  private final long numOfMethods;
-  private final long numOfFields;
-  private final long maxNumOfEntries;
-
-  public MainDexOverflow(
-      boolean hasMainDexList, long numOfMethods, long numOfFields, long maxNumOfEntries) {
-    super();
-    this.hasMainDexList = hasMainDexList;
-    this.numOfMethods = numOfMethods;
-    this.numOfFields = numOfFields;
-    this.maxNumOfEntries = maxNumOfEntries;
-  }
-
-  private StringBuilder getGeneralMessage() {
-    StringBuilder messageBuilder = new StringBuilder();
-    // General message: Cannot fit.
-    messageBuilder.append("Cannot fit requested classes in ");
-    messageBuilder.append(hasMainDexList ? "the main-" : "a single ");
-    messageBuilder.append("dex file");
-
-    return messageBuilder;
-  }
-
-  private String getNumberRelatedMessage() {
-    StringBuilder messageBuilder = new StringBuilder();
-    // Show the numbers of methods and/or fields that exceed the limit.
-    if (numOfMethods > maxNumOfEntries) {
-      messageBuilder.append("# methods: ");
-      messageBuilder.append(numOfMethods);
-      messageBuilder.append(" > ").append(maxNumOfEntries);
-      if (numOfFields > maxNumOfEntries) {
-        messageBuilder.append(" ; ");
-      }
-    }
-    if (numOfFields > maxNumOfEntries) {
-      messageBuilder.append("# fields: ");
-      messageBuilder.append(numOfFields);
-      messageBuilder.append(" > ").append(maxNumOfEntries);
-    }
-
-    return messageBuilder.toString();
-  }
-
-  public String getMessage() {
-    // Default message
-    return getGeneralMessage()
-        .append(" (")
-        .append(getNumberRelatedMessage())
-        .append(")")
-        .toString();
-  }
-
-  public String getMessageForD8() {
-    StringBuilder messageBuilder = getGeneralMessage();
-    if (!hasMainDexList) {
-      messageBuilder.append(". ");
-      messageBuilder.append("Try supplying a main-dex list");
-    }
-    messageBuilder.append(".").append(System.getProperty("line.separator"));
-    messageBuilder.append(getNumberRelatedMessage());
-    return messageBuilder.toString();
-  }
-
-  public String getMessageForR8() {
-    StringBuilder messageBuilder = getGeneralMessage();
-    if (!hasMainDexList) {
-      messageBuilder.append(". ");
-      messageBuilder.append("Try supplying a main-dex list or main dex rules");
-    }
-    messageBuilder.append(".").append(System.getProperty("line.separator"));
-    messageBuilder.append(getNumberRelatedMessage());
-    return messageBuilder.toString();
-  }
-}
diff --git a/src/main/java/com/android/tools/r8/utils/AndroidApp.java b/src/main/java/com/android/tools/r8/utils/AndroidApp.java
index 664a065..9e9e584 100644
--- a/src/main/java/com/android/tools/r8/utils/AndroidApp.java
+++ b/src/main/java/com/android/tools/r8/utils/AndroidApp.java
@@ -331,16 +331,12 @@
       return this;
     }
 
-    /**
-     * Add classpath file resources.
-     */
+    /** Add classpath file resources. */
     public Builder addClasspathFiles(Path... files) {
       return addClasspathFiles(Arrays.asList(files));
     }
 
-    /**
-     * Add classpath file resources.
-     */
+    /** Add classpath file resources. */
     public Builder addClasspathFiles(Collection<Path> files) {
       for (Path file : files) {
         addClasspathFile(file);
@@ -348,9 +344,7 @@
       return this;
     }
 
-    /**
-     * Add classpath file resources.
-     */
+    /** Add classpath file resources. */
     public Builder addClasspathFile(Path file) {
       addClasspathOrLibraryProvider(file, classpathResourceProviders);
       return this;
diff --git a/src/main/java/com/android/tools/r8/utils/Reporter.java b/src/main/java/com/android/tools/r8/utils/Reporter.java
index b09f164..ebb7ce6 100644
--- a/src/main/java/com/android/tools/r8/utils/Reporter.java
+++ b/src/main/java/com/android/tools/r8/utils/Reporter.java
@@ -3,14 +3,10 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.utils;
 
-import com.android.tools.r8.BaseCompilerCommand;
 import com.android.tools.r8.CompilationFailedException;
-import com.android.tools.r8.D8Command;
 import com.android.tools.r8.Diagnostic;
 import com.android.tools.r8.DiagnosticsHandler;
-import com.android.tools.r8.R8Command;
 import com.android.tools.r8.errors.CompilationError;
-import com.android.tools.r8.errors.MainDexOverflow;
 import com.android.tools.r8.errors.Unreachable;
 import com.android.tools.r8.origin.Origin;
 import com.android.tools.r8.position.Position;
@@ -20,18 +16,12 @@
 public class Reporter implements DiagnosticsHandler {
 
   private final DiagnosticsHandler clientHandler;
-  private final BaseCompilerCommand command;
   private int errorCount = 0;
   private Diagnostic lastError;
   private final Collection<Throwable> suppressedExceptions = new ArrayList<>();
 
   public Reporter(DiagnosticsHandler clientHandler) {
-    this(clientHandler, null);
-  }
-
-  public Reporter(DiagnosticsHandler clientHandler, BaseCompilerCommand command) {
     this.clientHandler = clientHandler;
-    this.command = command;
   }
 
   @Override
@@ -85,19 +75,6 @@
   }
 
   /**
-   * @throws AbortException always.
-   */
-  public RuntimeException fatalError(MainDexOverflow e) {
-    if (command instanceof R8Command) {
-      return fatalError(new StringDiagnostic(e.getMessageForR8()));
-    } else if (command instanceof D8Command) {
-      return fatalError(new StringDiagnostic(e.getMessageForD8()));
-    } else {
-      return fatalError(new StringDiagnostic(e.getMessage()));
-    }
-  }
-
-  /**
    * @throws AbortException if any error was reported.
    */
   public void failIfPendingErrors() {