Modify printuses to also print keeprules

Change-Id: Id1ded74a5f6d2346e285234548a81636002fd940
diff --git a/src/main/java/com/android/tools/r8/PrintUses.java b/src/main/java/com/android/tools/r8/PrintUses.java
index 42fed08..15f5662 100644
--- a/src/main/java/com/android/tools/r8/PrintUses.java
+++ b/src/main/java/com/android/tools/r8/PrintUses.java
@@ -22,7 +22,6 @@
 import com.google.common.collect.Maps;
 import com.google.common.collect.Sets;
 import java.io.IOException;
-import java.io.PrintStream;
 import java.nio.file.Path;
 import java.nio.file.Paths;
 import java.util.ArrayList;
@@ -46,19 +45,20 @@
 public class PrintUses {
 
   private static final String USAGE =
-      "Arguments: <rt.jar> <r8.jar> <sample.jar>\n"
+      "Arguments: [--keeprules] <rt.jar> <r8.jar> <sample.jar>\n"
           + "\n"
           + "PrintUses prints the classes, interfaces, methods and fields used by <sample.jar>,\n"
           + "restricted to classes and interfaces in <r8.jar> that are not in <sample.jar>.\n"
           + "<rt.jar> and <r8.jar> should point to libraries used by <sample.jar>.\n"
           + "\n"
           + "The output is in the same format as what is printed when specifying -printseeds in\n"
-          + "a ProGuard configuration file. See also the "
+          + "a ProGuard configuration file. Use --keeprules for outputting proguard keep rules. "
+          + "See also the "
           + PrintSeeds.class.getSimpleName()
           + " program in R8.";
 
   private final Set<String> descriptors;
-  private final PrintStream out;
+  private final Printer printer;
   private Set<DexType> types = Sets.newIdentityHashSet();
   private Map<DexType, Set<DexMethod>> methods = Maps.newIdentityHashMap();
   private Map<DexType, Set<DexField>> fields = Maps.newIdentityHashMap();
@@ -210,21 +210,28 @@
     }
   }
 
-  public static void main(String[] args) throws Exception {
-    if (args.length != 3) {
+  public static void main(String... args) throws Exception {
+    if (args.length != 3 && args.length != 4) {
       System.out.println(USAGE.replace("\n", System.lineSeparator()));
       return;
     }
+    int argumentIndex = 0;
+    boolean printKeep = false;
+    if (args[0].equals("--keeprules")) {
+      printKeep = true;
+      argumentIndex++;
+    }
     AndroidApp.Builder builder = AndroidApp.builder();
-    Path rtJar = Paths.get(args[0]);
+    Path rtJar = Paths.get(args[argumentIndex++]);
     builder.addLibraryFile(rtJar);
-    Path r8Jar = Paths.get(args[1]);
+    Path r8Jar = Paths.get(args[argumentIndex++]);
     builder.addLibraryFile(r8Jar);
-    Path sampleJar = Paths.get(args[2]);
+    Path sampleJar = Paths.get(args[argumentIndex++]);
     builder.addProgramFile(sampleJar);
     Set<String> descriptors = new HashSet<>(getDescriptors(r8Jar));
     descriptors.removeAll(getDescriptors(sampleJar));
-    PrintUses printUses = new PrintUses(descriptors, builder.build(), System.out);
+    Printer printer = printKeep ? new KeepPrinter() : new DefaultPrinter();
+    PrintUses printUses = new PrintUses(descriptors, builder.build(), printer);
     printUses.analyze();
     printUses.print();
     if (printUses.errors > 0) {
@@ -237,10 +244,10 @@
     return new ArchiveClassFileProvider(path).getClassDescriptors();
   }
 
-  private PrintUses(Set<String> descriptors, AndroidApp inputApp, PrintStream out)
+  private PrintUses(Set<String> descriptors, AndroidApp inputApp, Printer printer)
       throws Exception {
     this.descriptors = descriptors;
-    this.out = out;
+    this.printer = printer;
     InternalOptions options = new InternalOptions();
     application =
         new ApplicationReader(inputApp, options, new Timing("PrintUses")).read().toDirect();
@@ -260,69 +267,178 @@
   }
 
   private void print() {
-    List<DexType> types = new ArrayList<>(this.types);
-    types.sort(Comparator.comparing(DexType::toSourceString));
-    for (DexType type : types) {
-      String typeName = type.toSourceString();
-      DexClass dexClass = application.definitionFor(type);
-      if (dexClass == null) {
-        error("Could not find definition for type " + type.toSourceString());
-        continue;
+    errors = printer.print(application, types, methods, fields);
+  }
+
+  private abstract static class Printer {
+
+    void append(String string) {
+      System.out.print(string);
+    }
+
+    void appendLine(String string) {
+      System.out.println(string);
+    }
+
+    void printArguments(DexMethod method) {
+      append("(");
+      for (int i = 0; i < method.getArity(); i++) {
+        if (i != 0) {
+          append(",");
+        }
+        append(method.proto.parameters.values[i].toSourceString());
       }
-      out.println(typeName);
-      List<DexMethod> methods = new ArrayList<>(this.methods.get(type));
-      List<String> methodDefinitions = new ArrayList<>(methods.size());
-      for (DexMethod method : methods) {
-        DexEncodedMethod encodedMethod = dexClass.lookupMethod(method);
-        if (encodedMethod == null) {
-          error("Could not find definition for method " + method.toSourceString());
+      append(")");
+    }
+
+    abstract void printConstructorName(DexEncodedMethod encodedMethod);
+
+    void printError(String message) {
+      appendLine("# Error: " + message);
+    }
+
+    abstract void printField(DexClass dexClass, DexField field);
+
+    abstract void printMethod(DexEncodedMethod encodedMethod, String typeName);
+
+    void printNameAndReturn(DexEncodedMethod encodedMethod) {
+      if (encodedMethod.accessFlags.isConstructor()) {
+        printConstructorName(encodedMethod);
+      } else {
+        DexMethod method = encodedMethod.method;
+        append(method.proto.returnType.toSourceString());
+        append(" ");
+        append(method.name.toSourceString());
+      }
+    }
+
+    abstract void printTypeHeader(DexClass dexClass);
+
+    abstract void printTypeFooter();
+
+    int print(
+        DexApplication application,
+        Set<DexType> types,
+        Map<DexType, Set<DexMethod>> methods,
+        Map<DexType, Set<DexField>> fields) {
+      int errors = 0;
+      List<DexType> sortedTypes = new ArrayList<>(types);
+      sortedTypes.sort(Comparator.comparing(DexType::toSourceString));
+      for (DexType type : sortedTypes) {
+        DexClass dexClass = application.definitionFor(type);
+        if (dexClass == null) {
+          printError("Could not find definition for type " + type.toSourceString());
+          errors++;
           continue;
         }
-        methodDefinitions.add(getMethodSourceString(encodedMethod));
+        printTypeHeader(dexClass);
+        List<DexEncodedMethod> methodDefinitions = new ArrayList<>(methods.size());
+        for (DexMethod method : methods.get(type)) {
+          DexEncodedMethod encodedMethod = dexClass.lookupMethod(method);
+          if (encodedMethod == null) {
+            printError("Could not find definition for method " + method.toSourceString());
+            errors++;
+            continue;
+          }
+          methodDefinitions.add(encodedMethod);
+        }
+        methodDefinitions.sort(Comparator.comparing(x -> x.method.name.toSourceString()));
+        for (DexEncodedMethod encodedMethod : methodDefinitions) {
+          printMethod(encodedMethod, dexClass.type.toSourceString());
+        }
+        List<DexField> sortedFields = new ArrayList<>(fields.get(type));
+        sortedFields.sort(Comparator.comparing(DexField::toSourceString));
+        for (DexField field : sortedFields) {
+          printField(dexClass, field);
+        }
+        printTypeFooter();
       }
-      methodDefinitions.sort(Comparator.naturalOrder());
-      for (String encodedMethod : methodDefinitions) {
-        out.println(typeName + ": " + encodedMethod);
-      }
-      List<DexField> fields = new ArrayList<>(this.fields.get(type));
-      fields.sort(Comparator.comparing(DexField::toSourceString));
-      for (DexField field : fields) {
-        out.println(
-            typeName + ": " + field.type.toSourceString() + " " + field.name.toSourceString());
-      }
+      return errors;
     }
   }
 
-  private void error(String message) {
-    out.println("# Error: " + message);
-    errors += 1;
-  }
+  private static class DefaultPrinter extends Printer {
 
-  private static String getMethodSourceString(DexEncodedMethod encodedMethod) {
-    DexMethod method = encodedMethod.method;
-    StringBuilder builder = new StringBuilder();
-    if (encodedMethod.accessFlags.isConstructor()) {
+    @Override
+    public void printConstructorName(DexEncodedMethod encodedMethod) {
       if (encodedMethod.accessFlags.isStatic()) {
-        builder.append("<clinit>");
+        append("<clinit>");
       } else {
-        String holderName = method.holder.toSourceString();
+        String holderName = encodedMethod.method.holder.toSourceString();
         String constructorName = holderName.substring(holderName.lastIndexOf('.') + 1);
-        builder.append(constructorName);
+        append(constructorName);
       }
-    } else {
-      builder
-          .append(method.proto.returnType.toSourceString())
-          .append(" ")
-          .append(method.name.toSourceString());
     }
-    builder.append("(");
-    for (int i = 0; i < method.getArity(); i++) {
-      if (i != 0) {
-        builder.append(",");
+
+    @Override
+    void printMethod(DexEncodedMethod encodedMethod, String typeName) {
+      append(typeName + ": ");
+      printNameAndReturn(encodedMethod);
+      printArguments(encodedMethod.method);
+      appendLine("");
+    }
+
+    @Override
+    void printTypeHeader(DexClass dexClass) {
+      appendLine(dexClass.type.toSourceString());
+    }
+
+    @Override
+    void printTypeFooter() {}
+
+    @Override
+    void printField(DexClass dexClass, DexField field) {
+      appendLine(
+          dexClass.type.toSourceString()
+              + ": "
+              + field.type.toSourceString()
+              + " "
+              + field.name.toString());
+    }
+  }
+
+  private static class KeepPrinter extends Printer {
+
+    @Override
+    public void printTypeHeader(DexClass dexClass) {
+      if (dexClass.isInterface()) {
+        append("-keep interface " + dexClass.type.toSourceString() + " {\n");
+      } else if (dexClass.accessFlags.isEnum()) {
+        append("-keep enum " + dexClass.type.toSourceString() + " {\n");
+      } else {
+        append("-keep class " + dexClass.type.toSourceString() + " {\n");
       }
-      builder.append(method.proto.parameters.values[i].toSourceString());
     }
-    builder.append(")");
-    return builder.toString();
+
+    @Override
+    public void printConstructorName(DexEncodedMethod encodedMethod) {
+      append("<init>");
+    }
+
+    @Override
+    public void printField(DexClass dexClass, DexField field) {
+      append("  " + field.type.toSourceString() + " " + field.name.toString() + ";\n");
+    }
+
+    @Override
+    public void printMethod(DexEncodedMethod encodedMethod, String typeName) {
+      append("  ");
+      if (encodedMethod.isPublicMethod()) {
+        append("public ");
+      } else if (encodedMethod.isPrivateMethod()) {
+        append("private ");
+      }
+      if (encodedMethod.isStatic()) {
+        append("static ");
+      }
+      printNameAndReturn(encodedMethod);
+      printArguments(encodedMethod.method);
+      appendLine(";");
+    }
+
+    @Override
+    public void printTypeFooter() {
+      appendLine("}");
+    }
   }
 }
diff --git a/src/test/java/com/android/tools/r8/Dummy.java b/src/test/java/com/android/tools/r8/Dummy.java
new file mode 100644
index 0000000..caf8bb9
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/Dummy.java
@@ -0,0 +1,23 @@
+// Copyright (c) 2018, 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 org.junit.Test;
+
+public class Dummy extends TestBase {
+
+  @Test
+  public void testKeep() throws Exception {
+    PrintUses.main(
+        ToolHelper.getJava8RuntimeJar().toString(),
+        "build/libs/r8_without_deps.jar",
+        "build/libs/r8tests.jar");
+    PrintUses.main(
+        "--keeprules",
+        ToolHelper.getJava8RuntimeJar().toString(),
+        "build/libs/r8_without_deps.jar",
+        "build/libs/r8tests.jar");
+  }
+}