Update smali disassembly

* Add implemented interfaces
* Create a separate command and command line parsing

R=ager@google.com

Change-Id: I234d131f6225748a7a51de7b34160483161ab6c4
diff --git a/src/main/java/com/android/tools/r8/Disassemble.java b/src/main/java/com/android/tools/r8/Disassemble.java
index 71daa5c..b183ecc 100644
--- a/src/main/java/com/android/tools/r8/Disassemble.java
+++ b/src/main/java/com/android/tools/r8/Disassemble.java
@@ -4,19 +4,145 @@
 package com.android.tools.r8;
 
 import com.android.tools.r8.shaking.ProguardRuleParserException;
+import com.android.tools.r8.utils.AndroidApp;
+import com.android.tools.r8.utils.InternalOptions;
+import com.android.tools.r8.utils.OutputMode;
+import com.google.common.collect.ImmutableList;
 import java.io.IOException;
 import java.nio.file.Path;
 import java.nio.file.Paths;
-import java.util.Arrays;
-import java.util.List;
 import java.util.concurrent.ExecutionException;
-import java.util.stream.Collectors;
 
 public class Disassemble {
+  public static class DisassembleCommand extends BaseCommand {
+
+    public static class Builder
+        extends BaseCommand.Builder<DisassembleCommand, DisassembleCommand.Builder> {
+      private boolean useSmali = false;
+
+      private Builder() {
+        super(CompilationMode.RELEASE);
+      }
+
+      @Override
+      DisassembleCommand.Builder self() {
+        return this;
+      }
+
+      public DisassembleCommand.Builder setProguardMapFile(Path path) {
+        getAppBuilder().setProguardMapFile(path);
+        return this;
+      }
+
+      public DisassembleCommand.Builder setUseSmali(boolean useSmali) {
+        this.useSmali = useSmali;
+        return this;
+      }
+
+      @Override
+      public DisassembleCommand build() throws CompilationException, IOException {
+        // If printing versions ignore everything else.
+        if (isPrintHelp() || isPrintVersion()) {
+          return new DisassembleCommand(isPrintHelp(), isPrintVersion());
+        }
+
+        return new DisassembleCommand(
+            getAppBuilder().build(),
+            getOutputPath(),
+            getOutputMode(),
+            getMode(),
+            getMinApiLevel(),
+            useSmali);
+      }
+    }
+
+    static final String USAGE_MESSAGE = String.join("\n", ImmutableList.of(
+        "Usage: disasm [options] <input-files>",
+        " where <input-files> are dex files",
+        " and options are:",
+        "  --smali                 # Disassemble using smali syntax.",
+        "  --pg-map <file>         # Proguard map <file> for mapping names.",
+        "  --version               # Print the version of r8.",
+        "  --help                  # Print this message."));
+
+
+    private final boolean useSmali;
+
+    public static DisassembleCommand.Builder builder() {
+      return new DisassembleCommand.Builder();
+    }
+
+    public static DisassembleCommand.Builder parse(String[] args)
+        throws CompilationException, IOException {
+      DisassembleCommand.Builder builder = builder();
+      parse(args, builder);
+      return builder;
+    }
+
+    private static void parse(String[] args, DisassembleCommand.Builder builder)
+        throws CompilationException, IOException {
+      for (int i = 0; i < args.length; i++) {
+        String arg = args[i].trim();
+        if (arg.length() == 0) {
+          continue;
+        } else if (arg.equals("--help")) {
+          builder.setPrintHelp(true);
+        } else if (arg.equals("--version")) {
+          builder.setPrintVersion(true);
+        } else if (arg.equals("--smali")) {
+          builder.setUseSmali(true);
+        } else if (arg.equals("--pg-map")) {
+          builder.setProguardMapFile(Paths.get(args[++i]));
+        } else {
+          if (arg.startsWith("--")) {
+            throw new CompilationException("Unknown option: " + arg);
+          }
+          builder.addProgramFiles(Paths.get(arg));
+        }
+      }
+    }
+
+    private DisassembleCommand(
+        AndroidApp inputApp,
+        Path outputPath,
+        OutputMode outputMode,
+        CompilationMode mode,
+        int minApiLevel,
+        boolean useSmali) {
+      super(inputApp, outputPath, outputMode, mode, minApiLevel);
+      //assert getOutputMode() == OutputMode.Indexed : "Only regular mode is supported in R8";
+      this.useSmali = useSmali;
+    }
+
+    private DisassembleCommand(boolean printHelp, boolean printVersion) {
+      super(printHelp, printVersion);
+      this.useSmali = false;
+    }
+
+    public boolean useSmali() {
+      return useSmali;
+    }
+
+    @Override
+    InternalOptions getInternalOptions() {
+      InternalOptions internal = new InternalOptions();
+      internal.useSmaliSyntax = useSmali;
+      return internal;
+    }
+  }
+
   public static void main(String[] args)
       throws IOException, ProguardRuleParserException, CompilationException, ExecutionException {
-    List<Path> files = Arrays.stream(args).map(s -> Paths.get(s)).collect(Collectors.toList());
-    R8Command command = R8Command.builder().addProgramFiles(files).build();
+    DisassembleCommand.Builder builder = DisassembleCommand.parse(args);
+    DisassembleCommand command = builder.build();
+    if (command.isPrintHelp()) {
+      System.out.println(DisassembleCommand.USAGE_MESSAGE);
+      return;
+    }
+    if (command.isPrintVersion()) {
+      System.out.println("R8 v0.0.1");
+      return;
+    }
     R8.disassemble(command);
   }
 }
diff --git a/src/main/java/com/android/tools/r8/R8.java b/src/main/java/com/android/tools/r8/R8.java
index 306f37b..caf7f46 100644
--- a/src/main/java/com/android/tools/r8/R8.java
+++ b/src/main/java/com/android/tools/r8/R8.java
@@ -145,7 +145,8 @@
     return result;
   }
 
-  public static void disassemble(R8Command command) throws IOException, ExecutionException {
+  public static void disassemble(Disassemble.DisassembleCommand command)
+      throws IOException, ExecutionException {
     Path output = command.getOutputPath();
     AndroidApp app = command.getInputApp();
     InternalOptions options = command.getInternalOptions();
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 acbc205..0941b44 100644
--- a/src/main/java/com/android/tools/r8/graph/DexApplication.java
+++ b/src/main/java/com/android/tools/r8/graph/DexApplication.java
@@ -202,8 +202,12 @@
     if (clazz.type != dexItemFactory.objectType) {
       builder.append(".super ");
       builder.append(clazz.superType.toSmaliString());
-      // TODO(sgjesse): Add implemented interfaces
       builder.append("\n");
+      for (DexType iface : clazz.interfaces.values) {
+        builder.append(".implements ");
+        builder.append(iface.toSmaliString());
+        builder.append("\n");
+      }
     }
     ps.append(builder.toString());
   }
diff --git a/src/test/java/com/android/tools/r8/internal/R8DisassemblerTest.java b/src/test/java/com/android/tools/r8/internal/R8DisassemblerTest.java
index 1f20559..d455841 100644
--- a/src/test/java/com/android/tools/r8/internal/R8DisassemblerTest.java
+++ b/src/test/java/com/android/tools/r8/internal/R8DisassemblerTest.java
@@ -6,8 +6,8 @@
 import static com.android.tools.r8.utils.AndroidApp.DEFAULT_PROGUARD_MAP_FILE;
 
 import com.android.tools.r8.CompilationException;
+import com.android.tools.r8.Disassemble;
 import com.android.tools.r8.R8;
-import com.android.tools.r8.R8Command;
 import com.android.tools.r8.shaking.ProguardRuleParserException;
 import com.android.tools.r8.utils.FileUtils;
 import java.io.IOException;
@@ -27,11 +27,8 @@
 
   static final String APP_DIR = "third_party/gmscore/v5/";
 
-  public boolean deobfuscate;
-
-  @Test
-  public void disassemble() throws IOException, ExecutionException, ProguardRuleParserException,
-      CompilationException {
+  public void testDisassemble(boolean deobfuscate, boolean smali)
+      throws IOException, ExecutionException, ProguardRuleParserException, CompilationException {
     // This test only ensures that we do not break disassembling of dex code. It does not
     // check the generated code. To make it fast, we get rid of the output.
     PrintStream originalOut = System.out;
@@ -40,7 +37,8 @@
     }));
 
     try {
-      R8Command.Builder builder = R8Command.builder();
+      Disassemble.DisassembleCommand.Builder builder = Disassemble.DisassembleCommand.builder();
+      builder.setUseSmali(smali);
       if (deobfuscate) {
         builder.setProguardMapFile(Paths.get(APP_DIR, DEFAULT_PROGUARD_MAP_FILE));
       }
@@ -54,4 +52,24 @@
       System.setOut(originalOut);
     }
   }
+
+  @Test
+  public void test1() throws  Exception {
+    testDisassemble(false, false);
+  }
+
+  @Test
+  public void test2() throws  Exception {
+    testDisassemble(false, true);
+  }
+
+  @Test
+  public void test3() throws  Exception {
+    testDisassemble(true, false);
+  }
+
+  @Test
+  public void test4() throws  Exception {
+    testDisassemble(true, true);
+  }
 }
diff --git a/src/test/java/com/android/tools/r8/smali/SmaliDisassembleTest.java b/src/test/java/com/android/tools/r8/smali/SmaliDisassembleTest.java
index b1fd38f..0c1d13d 100644
--- a/src/test/java/com/android/tools/r8/smali/SmaliDisassembleTest.java
+++ b/src/test/java/com/android/tools/r8/smali/SmaliDisassembleTest.java
@@ -347,7 +347,6 @@
     roundTripRawSmali(expected);
   }
 
-
   @Test
   public void interfaceClass() {
     SmaliBuilder builder = new SmaliBuilder();
@@ -368,4 +367,26 @@
 
     roundTripRawSmali(expected);
   }
+
+  @Test
+  public void implementsInterface() {
+    SmaliBuilder builder = new SmaliBuilder();
+    builder.addClass("Test", "java.lang.Object", ImmutableList.of("java.util.List"));
+    builder.addAbstractMethod("int", "test", ImmutableList.of());
+    DexApplication application = buildApplication(builder);
+    assertEquals(1, Iterables.size(application.classes()));
+
+    String expected =
+        ".class public LTest;\n" +
+            "\n" +
+            ".super Ljava/lang/Object;\n" +
+            ".implements Ljava/util/List;\n" +
+            "\n" +
+            ".method public abstract test()I\n" +
+            ".end method\n";
+
+    assertEquals(expected, application.smali(new InternalOptions()));
+
+    roundTripRawSmali(expected);
+  }
 }
diff --git a/src/test/java/com/android/tools/r8/smali/SmaliTestBase.java b/src/test/java/com/android/tools/r8/smali/SmaliTestBase.java
index 3665a86..6b377f4 100644
--- a/src/test/java/com/android/tools/r8/smali/SmaliTestBase.java
+++ b/src/test/java/com/android/tools/r8/smali/SmaliTestBase.java
@@ -31,6 +31,7 @@
 import com.android.tools.r8.utils.OutputMode;
 import com.android.tools.r8.utils.Smali;
 import com.android.tools.r8.utils.Timing;
+import com.google.common.collect.ImmutableList;
 import com.google.common.collect.Iterables;
 import java.io.IOException;
 import java.nio.file.Path;
@@ -77,11 +78,27 @@
     abstract class Builder {
       String name;
       String superName;
+      List<String> implementedInterfaces;
       List<String> source = new ArrayList<>();
 
-      Builder(String name, String superName) {
+      Builder(String name, String superName, List<String> implementedInterfaces) {
         this.name = name;
         this.superName = superName;
+        this.implementedInterfaces = implementedInterfaces;
+      }
+
+      protected void appendSuper(StringBuilder builder) {
+        builder.append(".super ");
+        builder.append(DescriptorUtils.javaTypeToDescriptor(superName));
+        builder.append("\n");
+      }
+
+      protected void appendImplementedInterfaces(StringBuilder builder) {
+        for (String implementedInterface : implementedInterfaces) {
+          builder.append(".implements ");
+          builder.append(DescriptorUtils.javaTypeToDescriptor(implementedInterface));
+          builder.append("\n");
+        }
       }
 
       protected void writeSource(StringBuilder builder) {
@@ -94,8 +111,8 @@
 
     public class ClassBuilder extends Builder {
 
-      ClassBuilder(String name, String superName) {
-        super(name, superName);
+      ClassBuilder(String name, String superName, List<String> implementedInterfaces) {
+        super(name, superName, implementedInterfaces);
       }
 
       public String toString() {
@@ -103,9 +120,9 @@
         builder.append(".class public ");
         builder.append(DescriptorUtils.javaTypeToDescriptor(name));
         builder.append("\n");
-        builder.append(".super ");
-        builder.append(DescriptorUtils.javaTypeToDescriptor(superName));
-        builder.append("\n\n");
+        appendSuper(builder);
+        appendImplementedInterfaces(builder);
+        builder.append("\n");
         writeSource(builder);
         return builder.toString();
       }
@@ -114,7 +131,7 @@
     public class InterfaceBuilder extends Builder {
 
       InterfaceBuilder(String name, String superName) {
-        super(name, superName);
+        super(name, superName, ImmutableList.of());
       }
 
       public String toString() {
@@ -122,9 +139,9 @@
         builder.append(".class public interface abstract ");
         builder.append(DescriptorUtils.javaTypeToDescriptor(name));
         builder.append("\n");
-        builder.append(".super ");
-        builder.append(DescriptorUtils.javaTypeToDescriptor(superName));
-        builder.append("\n\n");
+        appendSuper(builder);
+        appendImplementedInterfaces(builder);
+        builder.append("\n");
         writeSource(builder);
         return builder.toString();
       }
@@ -162,9 +179,13 @@
     }
 
     public void addClass(String name, String superName) {
+      addClass(name, superName, ImmutableList.of());
+    }
+
+    public void addClass(String name, String superName, List<String> implementedInterfaces) {
       assert !classes.containsKey(name);
       currentClassName = name;
-      classes.put(name, new ClassBuilder(name, superName));
+      classes.put(name, new ClassBuilder(name, superName, implementedInterfaces));
     }
 
     public void addInterface(String name) {