Merge "Proposed minimal DiagnosticsHandler API."
diff --git a/src/main/java/com/android/tools/r8/BaseCompilerCommand.java b/src/main/java/com/android/tools/r8/BaseCompilerCommand.java
index e659231..72cfbab 100644
--- a/src/main/java/com/android/tools/r8/BaseCompilerCommand.java
+++ b/src/main/java/com/android/tools/r8/BaseCompilerCommand.java
@@ -3,9 +3,9 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8;
 
-import com.android.tools.r8.dex.Constants;
 import com.android.tools.r8.utils.AndroidApiLevel;
 import com.android.tools.r8.utils.AndroidApp;
+import com.android.tools.r8.utils.DefaultDiagnosticsHandler;
 import com.android.tools.r8.utils.FileUtils;
 import com.android.tools.r8.utils.OutputMode;
 import java.nio.file.Path;
@@ -21,14 +21,15 @@
   private final OutputMode outputMode;
   private final CompilationMode mode;
   private final int minApiLevel;
+  private final DiagnosticsHandler diagnosticsHandler;
 
   BaseCompilerCommand(boolean printHelp, boolean printVersion) {
     super(printHelp, printVersion);
-
-    this.outputPath = null;
-    this.outputMode = OutputMode.Indexed;
-    this.mode = null;
-    this.minApiLevel = 0;
+    outputPath = null;
+    outputMode = OutputMode.Indexed;
+    mode = null;
+    minApiLevel = 0;
+    diagnosticsHandler = new DefaultDiagnosticsHandler();
   }
 
   BaseCompilerCommand(
@@ -36,7 +37,8 @@
       Path outputPath,
       OutputMode outputMode,
       CompilationMode mode,
-      int minApiLevel) {
+      int minApiLevel,
+      DiagnosticsHandler diagnosticsHandler) {
     super(app);
     assert mode != null;
     assert minApiLevel > 0;
@@ -44,6 +46,7 @@
     this.outputMode = outputMode;
     this.mode = mode;
     this.minApiLevel = minApiLevel;
+    this.diagnosticsHandler = diagnosticsHandler;
   }
 
   public Path getOutputPath() {
@@ -62,6 +65,10 @@
     return outputMode;
   }
 
+  public DiagnosticsHandler getDiagnosticsHandler() {
+    return diagnosticsHandler;
+  }
+
   abstract public static class Builder<C extends BaseCompilerCommand, B extends Builder<C, B>>
       extends BaseCommand.Builder<C, B> {
 
@@ -69,6 +76,7 @@
     private OutputMode outputMode = OutputMode.Indexed;
     private CompilationMode mode;
     private int minApiLevel = AndroidApiLevel.getDefault().getLevel();
+    private DiagnosticsHandler diagnosticsHandler = new DefaultDiagnosticsHandler();
 
     protected Builder(CompilationMode mode) {
       this(AndroidApp.builder(), mode, false);
@@ -135,6 +143,15 @@
       return self();
     }
 
+    public DiagnosticsHandler getDiagnosticsHandler() {
+      return diagnosticsHandler;
+    }
+
+    public B setDiagnosticsHandler(DiagnosticsHandler diagnosticsHandler) {
+      this.diagnosticsHandler = diagnosticsHandler;
+      return self();
+    }
+
     protected void validate() throws CompilationException {
       super.validate();
       if (getAppBuilder().hasMainDexList() && outputMode == OutputMode.FilePerInputClass) {
diff --git a/src/main/java/com/android/tools/r8/CompilationException.java b/src/main/java/com/android/tools/r8/CompilationException.java
index 8e1e56c..c7477b6 100644
--- a/src/main/java/com/android/tools/r8/CompilationException.java
+++ b/src/main/java/com/android/tools/r8/CompilationException.java
@@ -9,7 +9,7 @@
  * This is always an expected error and considered a user input issue.
  * A user-understandable message must be provided.
  */
-public class CompilationException extends Exception {
+public class CompilationException extends Exception implements Diagnostic {
   private static final long serialVersionUID = 1L;
 
   /**
diff --git a/src/main/java/com/android/tools/r8/D8.java b/src/main/java/com/android/tools/r8/D8.java
index 9b79616..19f478a 100644
--- a/src/main/java/com/android/tools/r8/D8.java
+++ b/src/main/java/com/android/tools/r8/D8.java
@@ -60,18 +60,26 @@
   /**
    * Main API entry for the D8 dexer.
    *
+   * <p>If the D8Command contains a DiagnosticsHandler that does not throw a CompilationException
+   * on error this method returns null if the run fails.
+   *
    * @param command D8 command.
    * @return the compilation result.
    */
   public static D8Output run(D8Command command) throws IOException, CompilationException {
     InternalOptions options = command.getInternalOptions();
-    CompilationResult result = runForTesting(command.getInputApp(), options);
-    assert result != null;
-    D8Output output = new D8Output(result.androidApp, command.getOutputMode());
-    if (command.getOutputPath() != null) {
-      output.write(command.getOutputPath());
+    try {
+      CompilationResult result = runForTesting(command.getInputApp(), options);
+      assert result != null;
+      D8Output output = new D8Output(result.androidApp, command.getOutputMode());
+      if (command.getOutputPath() != null) {
+        output.write(command.getOutputPath());
+      }
+      return output;
+    } catch (CompilationException e) {
+      options.diagnosticsHandler.error(e);
+      return null;
     }
-    return output;
   }
 
   /**
@@ -80,21 +88,29 @@
    * <p>The D8 dexer API is intentionally limited and should "do the right thing" given a set of
    * inputs. If the API does not suffice please contact the R8 team.
    *
+   * <p>If the D8Command contains a DiagnosticsHandler that does not throw a CompilationException
+   * on error this method returns null if the run fails.
+   *
    * @param command D8 command.
    * @param executor executor service from which to get threads for multi-threaded processing.
-   * @return the compilation result.
+   * @return the compilation result
    */
   public static D8Output run(D8Command command, ExecutorService executor)
       throws IOException, CompilationException {
     InternalOptions options = command.getInternalOptions();
-    CompilationResult result = runForTesting(
-        command.getInputApp(), options, executor);
-    assert result != null;
-    D8Output output = new D8Output(result.androidApp, command.getOutputMode());
-    if (command.getOutputPath() != null) {
-      output.write(command.getOutputPath());
+    try {
+      CompilationResult result = runForTesting(
+          command.getInputApp(), options, executor);
+      assert result != null;
+      D8Output output = new D8Output(result.androidApp, command.getOutputMode());
+      if (command.getOutputPath() != null) {
+        output.write(command.getOutputPath());
+      }
+      return output;
+    } catch (CompilationException e) {
+      options.diagnosticsHandler.error(e);
+      return null;
     }
-    return output;
   }
 
   private static void run(String[] args) throws IOException, CompilationException {
diff --git a/src/main/java/com/android/tools/r8/D8Command.java b/src/main/java/com/android/tools/r8/D8Command.java
index f2aebc7..ac86781 100644
--- a/src/main/java/com/android/tools/r8/D8Command.java
+++ b/src/main/java/com/android/tools/r8/D8Command.java
@@ -7,7 +7,6 @@
 import com.android.tools.r8.graph.DexItemFactory;
 import com.android.tools.r8.utils.AndroidApp;
 import com.android.tools.r8.utils.InternalOptions;
-import com.android.tools.r8.utils.OffOrAuto;
 import com.android.tools.r8.utils.OutputMode;
 import com.google.common.collect.ImmutableList;
 import java.io.IOException;
@@ -99,6 +98,7 @@
           getOutputMode(),
           getMode(),
           getMinApiLevel(),
+          getDiagnosticsHandler(),
           intermediate);
     }
   }
@@ -195,8 +195,9 @@
       OutputMode outputMode,
       CompilationMode mode,
       int minApiLevel,
+      DiagnosticsHandler diagnosticsHandler,
       boolean intermediate) {
-    super(inputApp, outputPath, outputMode, mode, minApiLevel);
+    super(inputApp, outputPath, outputMode, mode, minApiLevel, diagnosticsHandler);
     this.intermediate = intermediate;
   }
 
@@ -224,6 +225,7 @@
     assert internal.outline.enabled;
     internal.outline.enabled = false;
     internal.outputMode = getOutputMode();
+    internal.diagnosticsHandler = getDiagnosticsHandler();
     return internal;
   }
 }
diff --git a/src/main/java/com/android/tools/r8/Diagnostic.java b/src/main/java/com/android/tools/r8/Diagnostic.java
new file mode 100644
index 0000000..6be2748
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/Diagnostic.java
@@ -0,0 +1,11 @@
+// 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;
+
+/**
+ * Interface for all diagnostic message produced by D8 and R8.
+ */
+public interface Diagnostic {
+  String toString();
+}
diff --git a/src/main/java/com/android/tools/r8/DiagnosticsHandler.java b/src/main/java/com/android/tools/r8/DiagnosticsHandler.java
new file mode 100644
index 0000000..4a89c6f
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/DiagnosticsHandler.java
@@ -0,0 +1,42 @@
+// 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;
+
+/**
+ * A DiagnosticsHandler can be provided to customize handling of diagnostics information.
+ *
+ * <p>During compilation the error, warning and info methods will be called.
+ */
+public interface DiagnosticsHandler {
+
+  /**
+   * Handle error diagnostics.
+   *
+   * <p>By default this throws the exception.
+   *
+   * @param error CompilationException containing error information.
+   * @throws CompilationException
+   */
+  default void error(CompilationException error) throws CompilationException {
+    throw error;
+  }
+
+  /**
+   * Handle warning diagnostics.
+   *
+   * @param warning Diagnostic containing warning information.
+   */
+  default void warning(Diagnostic warning) {
+    System.err.println(warning.toString());
+  }
+
+  /**
+   * Handle info diagnostics.
+   *
+   * @param info Diagnostic containing the information.
+   */
+  default void info(Diagnostic info) {
+    System.out.println(info.toString());
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/R8.java b/src/main/java/com/android/tools/r8/R8.java
index 45af2b3..899a194 100644
--- a/src/main/java/com/android/tools/r8/R8.java
+++ b/src/main/java/com/android/tools/r8/R8.java
@@ -57,7 +57,6 @@
 import java.io.PrintStream;
 import java.nio.charset.StandardCharsets;
 import java.nio.file.FileAlreadyExistsException;
-import java.nio.file.Files;
 import java.nio.file.NoSuchFileException;
 import java.nio.file.Path;
 import java.nio.file.Paths;
@@ -396,12 +395,15 @@
    * <p>The R8 API is intentionally limited and should "do the right thing" given a command. If this
    * API does not suffice please contact the R8 team.
    *
+   * <p>If the R8Command contains a DiagnosticsHandler that does not throw a CompilationException
+   * on error this method returns null if the run fails.
+   *
    * @param command R8 command.
    * @return the compilation result.
    */
-  public static AndroidApp run(R8Command command)
-      throws IOException, CompilationException {
-    ExecutorService executorService = ThreadUtils.getExecutorService(command.getInternalOptions());
+  public static AndroidApp run(R8Command command) throws IOException, CompilationException {
+    InternalOptions options = command.getInternalOptions();
+    ExecutorService executorService = ThreadUtils.getExecutorService(options);
     try {
       return run(command, executorService);
     } finally {
@@ -476,6 +478,9 @@
    * <p>The R8 API is intentionally limited and should "do the right thing" given a command. If this
    * API does not suffice please contact the R8 team.
    *
+   * <p>If the R8Command contains a DiagnosticsHandler that does not throw a CompilationException
+   * on error this method returns null if the run fails.
+   *
    * @param command R8 command.
    * @param executor executor service from which to get threads for multi-threaded processing.
    * @return the compilation result.
@@ -483,10 +488,15 @@
   public static AndroidApp run(R8Command command, ExecutorService executor)
       throws IOException, CompilationException {
     InternalOptions options = command.getInternalOptions();
-    AndroidApp outputApp =
-        runForTesting(command.getInputApp(), options, executor).androidApp;
-    writeOutputs(command, options, outputApp);
-    return outputApp;
+    try {
+      AndroidApp outputApp =
+          runForTesting(command.getInputApp(), options, executor).androidApp;
+      writeOutputs(command, options, outputApp);
+      return outputApp;
+    } catch (CompilationException e) {
+      options.diagnosticsHandler.error(e);
+      return null;
+    }
   }
 
   private static void run(String[] args)
diff --git a/src/main/java/com/android/tools/r8/R8Command.java b/src/main/java/com/android/tools/r8/R8Command.java
index 5e0b7d4..a6a1c33 100644
--- a/src/main/java/com/android/tools/r8/R8Command.java
+++ b/src/main/java/com/android/tools/r8/R8Command.java
@@ -237,6 +237,7 @@
           configuration,
           getMode(),
           getMinApiLevel(),
+          getDiagnosticsHandler(),
           useTreeShaking,
           useDiscardedChecker,
           useMinification,
@@ -392,12 +393,13 @@
       ProguardConfiguration proguardConfiguration,
       CompilationMode mode,
       int minApiLevel,
+      DiagnosticsHandler diagnosticsHandler,
       boolean useTreeShaking,
       boolean useDiscardedChecker,
       boolean useMinification,
       boolean ignoreMissingClasses,
       Path proguardMapOutput) {
-    super(inputApp, outputPath, outputMode, mode, minApiLevel);
+    super(inputApp, outputPath, outputMode, mode, minApiLevel, diagnosticsHandler);
     assert proguardConfiguration != null;
     assert mainDexKeepRules != null;
     assert getOutputMode() == OutputMode.Indexed : "Only regular mode is supported in R8";
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java b/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java
index 2ba4421..4405ef4 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java
@@ -101,7 +101,7 @@
     this.lambdaRewriter = enableDesugaring ? new LambdaRewriter(this) : null;
     this.interfaceMethodRewriter =
         (enableDesugaring && enableInterfaceMethodDesugaring())
-            ? new InterfaceMethodRewriter(this) : null;
+            ? new InterfaceMethodRewriter(this, options) : null;
     if (enableWholeProgramOptimizations) {
       assert appInfo.hasSubtyping();
       this.inliner = new Inliner(appInfo.withSubtyping(), graphLense, options);
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/InterfaceMethodRewriter.java b/src/main/java/com/android/tools/r8/ir/desugar/InterfaceMethodRewriter.java
index 99417c3..c5545f5 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/InterfaceMethodRewriter.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/InterfaceMethodRewriter.java
@@ -6,7 +6,6 @@
 
 import com.android.tools.r8.ApiLevelException;
 import com.android.tools.r8.Resource;
-import com.android.tools.r8.errors.CompilationError;
 import com.android.tools.r8.errors.Unimplemented;
 import com.android.tools.r8.graph.DexApplication.Builder;
 import com.android.tools.r8.graph.DexCallSite;
@@ -26,7 +25,8 @@
 import com.android.tools.r8.ir.code.InvokeStatic;
 import com.android.tools.r8.ir.code.InvokeSuper;
 import com.android.tools.r8.ir.conversion.IRConverter;
-import com.android.tools.r8.logging.Log;
+import com.android.tools.r8.utils.InternalOptions;
+import com.android.tools.r8.utils.StringDiagnostic;
 import com.google.common.collect.ImmutableSet;
 import com.google.common.collect.Sets;
 import java.util.ListIterator;
@@ -65,6 +65,7 @@
   private static final String DEFAULT_METHOD_PREFIX = "$default$";
 
   private final IRConverter converter;
+  private final InternalOptions options;
   final DexItemFactory factory;
 
   // All forwarding methods generated during desugaring. We don't synchronize access
@@ -84,10 +85,11 @@
     ExcludeDexResources
   }
 
-  public InterfaceMethodRewriter(IRConverter converter) {
+  public InterfaceMethodRewriter(IRConverter converter, InternalOptions options) {
     assert converter != null;
     this.converter = converter;
-    this.factory = converter.application.dexItemFactory;
+    this.options = options;
+    this.factory = options.itemFactory;
   }
 
   // Rewrites the references to static and default interface methods.
@@ -298,7 +300,7 @@
     // TODO replace by a proper warning mechanic (see b/65154707).
     // TODO think about using a common deduplicating mechanic with Enqueuer
     if (reportedMissing.add(missing)) {
-      System.err.println(message);
+      options.diagnosticsHandler.warning(new StringDiagnostic(message));
     }
   }
 
diff --git a/src/main/java/com/android/tools/r8/utils/DefaultDiagnosticsHandler.java b/src/main/java/com/android/tools/r8/utils/DefaultDiagnosticsHandler.java
new file mode 100644
index 0000000..68c2840
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/utils/DefaultDiagnosticsHandler.java
@@ -0,0 +1,9 @@
+// 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.utils;
+
+import com.android.tools.r8.DiagnosticsHandler;
+
+public class DefaultDiagnosticsHandler implements DiagnosticsHandler {
+}
diff --git a/src/main/java/com/android/tools/r8/utils/InternalOptions.java b/src/main/java/com/android/tools/r8/utils/InternalOptions.java
index f497ce5..a72d356 100644
--- a/src/main/java/com/android/tools/r8/utils/InternalOptions.java
+++ b/src/main/java/com/android/tools/r8/utils/InternalOptions.java
@@ -3,7 +3,7 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.utils;
 
-import com.android.tools.r8.dex.Constants;
+import com.android.tools.r8.DiagnosticsHandler;
 import com.android.tools.r8.dex.Marker;
 import com.android.tools.r8.errors.CompilationError;
 import com.android.tools.r8.errors.InvalidDebugInfoException;
@@ -126,6 +126,8 @@
 
   public Path proguardMapOutput = null;
 
+  public DiagnosticsHandler diagnosticsHandler = new DefaultDiagnosticsHandler();
+
   public void warningInvalidDebugInfo(DexEncodedMethod method, InvalidDebugInfoException e) {
     warningInvalidDebugInfoCount++;
   }
diff --git a/src/main/java/com/android/tools/r8/utils/StringDiagnostic.java b/src/main/java/com/android/tools/r8/utils/StringDiagnostic.java
new file mode 100644
index 0000000..0606be2
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/utils/StringDiagnostic.java
@@ -0,0 +1,19 @@
+// 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.utils;
+
+import com.android.tools.r8.Diagnostic;
+
+public class StringDiagnostic implements Diagnostic {
+  private final String message;
+
+  public StringDiagnostic(String message) {
+    this.message = message;
+  }
+
+  @Override
+  public String toString() {
+    return message;
+  }
+}