Merge "Introduce dedicated proguard usage-info consumer."
diff --git a/src/main/java/com/android/tools/r8/OutputSink.java b/src/main/java/com/android/tools/r8/OutputSink.java
index d18e04f..2ca5e73 100644
--- a/src/main/java/com/android/tools/r8/OutputSink.java
+++ b/src/main/java/com/android/tools/r8/OutputSink.java
@@ -69,14 +69,6 @@
       throws IOException;
 
   /**
-   * Provides the raw bytes that would be generated for the <code>-printusage</code> flag.
-   * <p>
-   * This method is only invoked by R8 and only if R8 is instructed to generate printusage
-   * information.
-   */
-  void writePrintUsedInformation(byte[] contents) throws IOException;
-
-  /**
    * Provides the raw bytes that would be generated for the <code>-printseeds</code> flag.
    * <p>
    * This method is only invoked by R8 and only if R8 is instructed to generate seeds information.
diff --git a/src/main/java/com/android/tools/r8/R8.java b/src/main/java/com/android/tools/r8/R8.java
index 58e92e7..73a5b1a 100644
--- a/src/main/java/com/android/tools/r8/R8.java
+++ b/src/main/java/com/android/tools/r8/R8.java
@@ -104,7 +104,7 @@
       ExecutorService executorService,
       DexApplication application,
       OutputSink outputSink,
-      byte[] deadCode,
+      String deadCode,
       NamingLens namingLens,
       byte[] proguardSeedsData,
       InternalOptions options,
diff --git a/src/main/java/com/android/tools/r8/R8Command.java b/src/main/java/com/android/tools/r8/R8Command.java
index 0a5efc5..b5db328 100644
--- a/src/main/java/com/android/tools/r8/R8Command.java
+++ b/src/main/java/com/android/tools/r8/R8Command.java
@@ -560,6 +560,13 @@
       internal.inlineAccessors = false;
     }
 
+    // Setup a usage information consumer.
+    if (proguardConfiguration.isPrintUsage()) {
+      internal.usageInformationConsumer = proguardConfiguration.getPrintUsageFile() != null
+          ? new StringConsumer.FileConsumer(proguardConfiguration.getPrintUsageFile())
+          : new StringConsumer.StreamConsumer(StandardOutOrigin.instance(), System.out);
+    }
+
     // Amend the proguard-map consumer with options from the proguard configuration.
     {
       StringConsumer wrappedConsumer;
diff --git a/src/main/java/com/android/tools/r8/UsageInformationConsumer.java b/src/main/java/com/android/tools/r8/UsageInformationConsumer.java
new file mode 100644
index 0000000..fb4fa1e
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/UsageInformationConsumer.java
@@ -0,0 +1,132 @@
+// 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;
+
+import com.android.tools.r8.origin.Origin;
+import com.android.tools.r8.origin.PathOrigin;
+import com.android.tools.r8.utils.FileUtils;
+import com.android.tools.r8.utils.IOExceptionDiagnostic;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.nio.file.Path;
+
+/**
+ * Interface for receiving usage feedback from R8.
+ *
+ * The data is in the format defined for Proguard's <code>-printusage</code> flag. The information
+ * will be produced if a consumer is provided. A consumer is automatically setup when running R8
+ * with the Proguard <code>-printusage</code> flag set.
+ */
+public interface UsageInformationConsumer {
+
+  /**
+   * Callback to receive the usage-information data.
+   *
+   * <p>The consumer is expected not to throw, but instead report any errors via the diagnostics
+   * {@param handler}. If an error is reported via {@param handler} and no exceptions are thrown,
+   * then the compiler guaranties to exit with an error.
+   *
+   * @param data UTF-8 encoded usage information.
+   * @param handler Diagnostics handler for reporting.
+   */
+  void acceptUsageInformation(byte[] data, DiagnosticsHandler handler);
+
+  static EmptyConsumer emptyConsumer() {
+    return EmptyConsumer.EMPTY_CONSUMER;
+  }
+
+  /** Empty consumer to request usage information but ignore the result. */
+  class EmptyConsumer implements UsageInformationConsumer {
+
+    private static EmptyConsumer EMPTY_CONSUMER = new EmptyConsumer();
+
+    private EmptyConsumer() {}
+
+    @Override
+    public void acceptUsageInformation(byte[] data, DiagnosticsHandler handler) {
+      // Ignore content.
+    }
+  }
+
+    /** Forwarding consumer to delegate to an optional existing consumer. */
+  class ForwardingConsumer implements UsageInformationConsumer {
+
+    private final UsageInformationConsumer consumer;
+
+    /** @param consumer Consumer to forward to, if null, nothing will be forwarded. */
+    public ForwardingConsumer(UsageInformationConsumer consumer) {
+      this.consumer = consumer;
+    }
+
+    @Override
+    public void acceptUsageInformation(byte[] data, DiagnosticsHandler handler) {
+      if (consumer != null) {
+        consumer.acceptUsageInformation(data, handler);
+      }
+    }
+  }
+
+  /** File consumer to write contents to a file-system file. */
+  class FileConsumer extends ForwardingConsumer {
+
+    private final Path outputPath;
+
+    /** Consumer that writes to {@param outputPath}. */
+    public FileConsumer(Path outputPath) {
+      this(outputPath, null);
+    }
+
+    /** Consumer that forwards to {@param consumer} and also writes to {@param outputPath}. */
+    public FileConsumer(Path outputPath, UsageInformationConsumer consumer) {
+      super(consumer);
+      this.outputPath = outputPath;
+    }
+
+    @Override
+    public void acceptUsageInformation(byte[] data, DiagnosticsHandler handler) {
+      super.acceptUsageInformation(data, handler);
+      try {
+        FileUtils.writeToFile(outputPath, null, data);
+      } catch (IOException e) {
+        handler.error(new IOExceptionDiagnostic(e, new PathOrigin(outputPath)));
+      }
+    }
+  }
+
+  /**
+   * Stream consumer to write contents to an output stream.
+   *
+   * <p>Note: No close events are given to this stream so it should either be a permanent stream or
+   * the closing needs to happen outside of the compilation itself. If the stream is not one of the
+   * standard streams, i.e., System.out or System.err, you should likely implement yor own consumer.
+   */
+  class StreamConsumer extends ForwardingConsumer {
+
+    private final Origin origin;
+    private final OutputStream outputStream;
+
+    /** Consumer that writes to {@param outputStream}. */
+    public StreamConsumer(Origin origin, OutputStream outputStream) {
+      this(origin, outputStream, null);
+    }
+
+    /** Consumer that forwards to {@param consumer} and also writes to {@param outputStream}. */
+    public StreamConsumer(
+        Origin origin, OutputStream outputStream, UsageInformationConsumer consumer) {
+      super(consumer);
+      this.origin = origin;
+      this.outputStream = outputStream;
+    }
+
+    @Override
+    public void acceptUsageInformation(byte[] data, DiagnosticsHandler handler) {
+      super.acceptUsageInformation(data, handler);
+      try {
+        outputStream.write(data);
+      } catch (IOException e) {
+        handler.error(new IOExceptionDiagnostic(e, origin));
+      }
+    }
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/dex/ApplicationWriter.java b/src/main/java/com/android/tools/r8/dex/ApplicationWriter.java
index 11de83c..a20f3ed 100644
--- a/src/main/java/com/android/tools/r8/dex/ApplicationWriter.java
+++ b/src/main/java/com/android/tools/r8/dex/ApplicationWriter.java
@@ -45,7 +45,7 @@
 public class ApplicationWriter {
 
   public final DexApplication application;
-  public final byte[] deadCode;
+  public final String deadCode;
   public final NamingLens namingLens;
   public final byte[] proguardSeedsData;
   public final InternalOptions options;
@@ -113,7 +113,7 @@
       DexApplication application,
       InternalOptions options,
       Marker marker,
-      byte[] deadCode,
+      String deadCode,
       NamingLens namingLens,
       byte[] proguardSeedsData,
       ProguardMapSupplier proguardMapSupplier) {
@@ -208,10 +208,9 @@
       // Wait for all files to be processed before moving on.
       ThreadUtils.awaitFutures(dexDataFutures);
 
-      if (options.proguardConfiguration.isPrintUsage()) {
-        if (deadCode != null) {
-          outputSink.writePrintUsedInformation(deadCode);
-        }
+      if (options.usageInformationConsumer != null && deadCode != null) {
+        ExceptionUtils.withConsumeResourceHandler(
+            options.reporter, deadCode, options.usageInformationConsumer);
       }
       // Write the proguard map file after writing the dex files, as the map writer traverses
       // the DexProgramClass structures, which are destructively updated during dex file writing.
diff --git a/src/main/java/com/android/tools/r8/graph/DexApplication.java b/src/main/java/com/android/tools/r8/graph/DexApplication.java
index 242add5..c05b025 100644
--- a/src/main/java/com/android/tools/r8/graph/DexApplication.java
+++ b/src/main/java/com/android/tools/r8/graph/DexApplication.java
@@ -12,7 +12,6 @@
 import com.android.tools.r8.utils.Timing;
 import com.google.common.collect.ImmutableSet;
 import com.google.common.collect.Sets;
-import com.google.common.primitives.Bytes;
 import java.util.ArrayList;
 import java.util.Collection;
 import java.util.Collections;
@@ -25,7 +24,7 @@
   ProgramClassCollection programClasses;
 
   public final ImmutableSet<DexType> mainDexList;
-  public final byte[] deadCode;
+  public final String deadCode;
 
   private final ClassNameMapper proguardMap;
 
@@ -43,7 +42,7 @@
       ClassNameMapper proguardMap,
       ProgramClassCollection programClasses,
       ImmutableSet<DexType> mainDexList,
-      byte[] deadCode,
+      String deadCode,
       DexItemFactory dexItemFactory,
       DexString highestSortingString,
       Timing timing) {
@@ -106,7 +105,7 @@
     final Timing timing;
 
     DexString highestSortingString;
-    byte[] deadCode;
+    String deadCode;
     final Set<DexType> mainDexList = Sets.newIdentityHashSet();
     private final Collection<DexProgramClass> synthesizedClasses;
 
@@ -144,7 +143,7 @@
       return self();
     }
 
-    public T appendDeadCode(byte[] deadCodeAtAnotherRound) {
+    public T appendDeadCode(String deadCodeAtAnotherRound) {
       if (deadCodeAtAnotherRound == null) {
         return self();
       }
@@ -152,8 +151,8 @@
         this.deadCode = deadCodeAtAnotherRound;
         return self();
       }
-      // Concatenate existing byte[] and the given byte[].
-      this.deadCode = Bytes.concat(this.deadCode, deadCodeAtAnotherRound);
+      // Concatenate existing deadCode info with next round.
+      this.deadCode += deadCodeAtAnotherRound;
       return self();
     }
 
diff --git a/src/main/java/com/android/tools/r8/graph/DirectMappedDexApplication.java b/src/main/java/com/android/tools/r8/graph/DirectMappedDexApplication.java
index f67e808..1e0b1e2 100644
--- a/src/main/java/com/android/tools/r8/graph/DirectMappedDexApplication.java
+++ b/src/main/java/com/android/tools/r8/graph/DirectMappedDexApplication.java
@@ -24,7 +24,7 @@
   private DirectMappedDexApplication(ClassNameMapper proguardMap,
       ProgramClassCollection programClasses,
       ImmutableMap<DexType, DexLibraryClass> libraryClasses,
-      ImmutableSet<DexType> mainDexList, byte[] deadCode,
+      ImmutableSet<DexType> mainDexList, String deadCode,
       DexItemFactory dexItemFactory, DexString highestSortingString,
       Timing timing) {
     super(proguardMap, programClasses, mainDexList, deadCode,
diff --git a/src/main/java/com/android/tools/r8/graph/LazyLoadedDexApplication.java b/src/main/java/com/android/tools/r8/graph/LazyLoadedDexApplication.java
index c35b10e..97911a1 100644
--- a/src/main/java/com/android/tools/r8/graph/LazyLoadedDexApplication.java
+++ b/src/main/java/com/android/tools/r8/graph/LazyLoadedDexApplication.java
@@ -27,7 +27,7 @@
       ProgramClassCollection programClasses,
       ClasspathClassCollection classpathClasses,
       LibraryClassCollection libraryClasses,
-      ImmutableSet<DexType> mainDexList, byte[] deadCode,
+      ImmutableSet<DexType> mainDexList, String deadCode,
       DexItemFactory dexItemFactory, DexString highestSortingString,
       Timing timing) {
     super(proguardMap, programClasses, mainDexList, deadCode,
diff --git a/src/main/java/com/android/tools/r8/shaking/TreePruner.java b/src/main/java/com/android/tools/r8/shaking/TreePruner.java
index 8adfc10..f496133 100644
--- a/src/main/java/com/android/tools/r8/shaking/TreePruner.java
+++ b/src/main/java/com/android/tools/r8/shaking/TreePruner.java
@@ -51,7 +51,7 @@
     }
     DexApplication result;
     try {
-      result = removeUnused(application).appendDeadCode(usagePrinter.toByteArray()).build();
+      result = removeUnused(application).appendDeadCode(usagePrinter.toStringContent()).build();
     } finally {
       application.timing.end();
     }
diff --git a/src/main/java/com/android/tools/r8/shaking/UsagePrinter.java b/src/main/java/com/android/tools/r8/shaking/UsagePrinter.java
index 7e41671..30021c1 100644
--- a/src/main/java/com/android/tools/r8/shaking/UsagePrinter.java
+++ b/src/main/java/com/android/tools/r8/shaking/UsagePrinter.java
@@ -21,6 +21,10 @@
     writer = new StringBuilder();
   }
 
+  String toStringContent() {
+    return writer.toString();
+  }
+
   byte[] toByteArray() {
     return writer.toString().getBytes(StandardCharsets.UTF_8);
   }
diff --git a/src/main/java/com/android/tools/r8/utils/AndroidAppOutputSink.java b/src/main/java/com/android/tools/r8/utils/AndroidAppOutputSink.java
index a45dbe9..84c7e2d 100644
--- a/src/main/java/com/android/tools/r8/utils/AndroidAppOutputSink.java
+++ b/src/main/java/com/android/tools/r8/utils/AndroidAppOutputSink.java
@@ -8,6 +8,7 @@
 import com.android.tools.r8.StringConsumer;
 import com.android.tools.r8.origin.Origin;
 import java.io.IOException;
+import java.nio.charset.StandardCharsets;
 import java.util.ArrayList;
 import java.util.List;
 import java.util.Set;
@@ -22,10 +23,13 @@
   private boolean closed = false;
 
   private StringConsumer proguardMapConsumer = null;
+  private StringConsumer usageInformationConsumer = null;
 
   public AndroidAppOutputSink(OutputSink forwardTo, InternalOptions options) {
     super(forwardTo);
     options.proguardMapConsumer = wrapProguardMapConsumer(options.proguardMapConsumer);
+    options.usageInformationConsumer =
+        wrapUsageInformationConsumer(options.usageInformationConsumer);
   }
 
   public AndroidAppOutputSink() {
@@ -38,15 +42,29 @@
       proguardMapConsumer =
           new StringConsumer.ForwardingConsumer(consumer) {
             @Override
-            public void accept(String data, DiagnosticsHandler handler) {
-              super.accept(data, handler);
-              builder.setProguardMapData(data);
+            public void accept(String string, DiagnosticsHandler handler) {
+              super.accept(string, handler);
+              builder.setProguardMapData(string);
             }
           };
     }
     return proguardMapConsumer;
   }
 
+  private StringConsumer wrapUsageInformationConsumer(StringConsumer consumer) {
+    assert usageInformationConsumer == null;
+    if (consumer != null) {
+      usageInformationConsumer = new StringConsumer.ForwardingConsumer(consumer) {
+        @Override
+        public void accept(String string, DiagnosticsHandler handler) {
+          super.accept(string, handler);
+          builder.setDeadCode(string.getBytes(StandardCharsets.UTF_8));
+        }
+      };
+    }
+    return usageInformationConsumer;
+  }
+
   @Override
   public synchronized void writeDexFile(byte[] contents, Set<String> classDescriptors, int fileId)
       throws IOException {
@@ -76,12 +94,6 @@
   }
 
   @Override
-  public void writePrintUsedInformation(byte[] contents) throws IOException {
-    builder.setDeadCode(contents);
-    super.writePrintUsedInformation(contents);
-  }
-
-  @Override
   public void writeProguardSeedsFile(byte[] contents) throws IOException {
     builder.setProguardSeedsData(contents);
     super.writeProguardSeedsFile(contents);
diff --git a/src/main/java/com/android/tools/r8/utils/FileSystemOutputSink.java b/src/main/java/com/android/tools/r8/utils/FileSystemOutputSink.java
index 0941b75..6623bda 100644
--- a/src/main/java/com/android/tools/r8/utils/FileSystemOutputSink.java
+++ b/src/main/java/com/android/tools/r8/utils/FileSystemOutputSink.java
@@ -34,12 +34,6 @@
     return DescriptorUtils.getClassBinaryNameFromDescriptor(classDescriptor) + extension;
   }
 
-
-  @Override
-  public void writePrintUsedInformation(byte[] contents) throws IOException {
-    FileUtils.writeToFile(options.proguardConfiguration.getPrintUsageFile(), System.out, contents);
-  }
-
   @Override
   public void writeProguardSeedsFile(byte[] contents) throws IOException {
     FileUtils.writeToFile(options.proguardConfiguration.getSeedFile(), System.out, contents);
diff --git a/src/main/java/com/android/tools/r8/utils/ForwardingOutputSink.java b/src/main/java/com/android/tools/r8/utils/ForwardingOutputSink.java
index d6867eb..31191e3 100644
--- a/src/main/java/com/android/tools/r8/utils/ForwardingOutputSink.java
+++ b/src/main/java/com/android/tools/r8/utils/ForwardingOutputSink.java
@@ -39,11 +39,6 @@
   }
 
   @Override
-  public void writePrintUsedInformation(byte[] contents) throws IOException {
-    forwardTo.writePrintUsedInformation(contents);
-  }
-
-  @Override
   public void writeProguardSeedsFile(byte[] contents) throws IOException {
     forwardTo.writeProguardSeedsFile(contents);
   }
diff --git a/src/main/java/com/android/tools/r8/utils/IgnoreContentsOutputSink.java b/src/main/java/com/android/tools/r8/utils/IgnoreContentsOutputSink.java
index d0eb364..8359244 100644
--- a/src/main/java/com/android/tools/r8/utils/IgnoreContentsOutputSink.java
+++ b/src/main/java/com/android/tools/r8/utils/IgnoreContentsOutputSink.java
@@ -26,11 +26,6 @@
   }
 
   @Override
-  public void writePrintUsedInformation(byte[] contents) {
-    // Intentionally left empty.
-  }
-
-  @Override
   public void writeProguardSeedsFile(byte[] contents) {
     // Intentionally left empty.
   }
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 c54317a..6c2b63e 100644
--- a/src/main/java/com/android/tools/r8/utils/InternalOptions.java
+++ b/src/main/java/com/android/tools/r8/utils/InternalOptions.java
@@ -175,6 +175,10 @@
   // If non null it must be and passed to the consumer.
   public StringConsumer proguardMapConsumer = null;
 
+  // If null, no usage information needs to be computed.
+  // If non-null, it must be and is passed to the consumer.
+  public StringConsumer usageInformationConsumer = null;
+
   public Path proguardCompatibilityRulesOutput = null;
 
   public void warningMissingEnclosingMember(DexType clazz, Origin origin, int version) {