Split disassembly writing out of DexApplication.

Change-Id: Ib89c6ed48f5e7c54e21e859c609fb70634bc30c8
Bug:
diff --git a/src/main/java/com/android/tools/r8/Disassemble.java b/src/main/java/com/android/tools/r8/Disassemble.java
index a0770bc..27bcac2 100644
--- a/src/main/java/com/android/tools/r8/Disassemble.java
+++ b/src/main/java/com/android/tools/r8/Disassemble.java
@@ -3,13 +3,21 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8;
 
+import com.android.tools.r8.dex.ApplicationReader;
+import com.android.tools.r8.graph.AssemblyWriter;
+import com.android.tools.r8.graph.DexByteCodeWriter;
+import com.android.tools.r8.graph.DexApplication;
+import com.android.tools.r8.graph.SmaliWriter;
 import com.android.tools.r8.utils.AndroidApp;
 import com.android.tools.r8.utils.InternalOptions;
+import com.android.tools.r8.utils.ThreadUtils;
+import com.android.tools.r8.utils.Timing;
 import com.google.common.collect.ImmutableList;
 import java.io.IOException;
 import java.nio.file.Path;
 import java.nio.file.Paths;
 import java.util.concurrent.ExecutionException;
+import java.util.concurrent.ExecutorService;
 
 public class Disassemble {
   public static class DisassembleCommand extends BaseCommand {
@@ -64,6 +72,7 @@
         " and options are:",
         "  --smali                 # Disassemble using smali syntax.",
         "  --pg-map <file>         # Proguard map <file> for mapping names.",
+        "  --output                # Specify a file or directory to write to.",
         "  --version               # Print the version of r8.",
         "  --help                  # Print this message."));
 
@@ -144,9 +153,30 @@
       return;
     }
     if (command.isPrintVersion()) {
-      System.out.println("R8 " + Version.LABEL);
+      System.out.println("Disassemble (R8) " + Version.LABEL);
       return;
     }
-    R8.disassemble(command);
+    disassemble(command);
+  }
+
+  public static void disassemble(DisassembleCommand command)
+      throws IOException, ExecutionException {
+    AndroidApp app = command.getInputApp();
+    InternalOptions options = command.getInternalOptions();
+    ExecutorService executor = ThreadUtils.getExecutorService(options);
+    Timing timing = new Timing("disassemble");
+    try {
+      DexApplication application = new ApplicationReader(app, options, timing).read(executor);
+      DexByteCodeWriter writer = command.useSmali()
+          ? new SmaliWriter(application, options)
+          : new AssemblyWriter(application, options);
+      if (command.getOutputPath() != null) {
+        writer.write(command.getOutputPath());
+      } else {
+        writer.write(System.out);
+      }
+    } finally {
+      executor.shutdown();
+    }
   }
 }
diff --git a/src/main/java/com/android/tools/r8/R8.java b/src/main/java/com/android/tools/r8/R8.java
index d0badf2..0c367bd 100644
--- a/src/main/java/com/android/tools/r8/R8.java
+++ b/src/main/java/com/android/tools/r8/R8.java
@@ -164,33 +164,6 @@
     return result;
   }
 
-  public static void disassemble(Disassemble.DisassembleCommand command)
-      throws IOException, ExecutionException {
-    Path output = command.getOutputPath();
-    AndroidApp app = command.getInputApp();
-    InternalOptions options = command.getInternalOptions();
-    ExecutorService executor = ThreadUtils.getExecutorService(options);
-    Timing timing = new Timing("disassemble");
-    try {
-      DexApplication application = new ApplicationReader(app, options, timing).read(executor);
-      if (options.useSmaliSyntax) {
-        if (output != null) {
-          Files.createDirectories(output);
-          try (PrintStream ps = new PrintStream(
-              Files.newOutputStream(output.resolve("classes.smali")))) {
-            application.smali(options, ps);
-          }
-        } else {
-          application.smali(options, System.out);
-        }
-      } else {
-        application.disassemble(output, options);
-      }
-    } finally {
-      executor.shutdown();
-    }
-  }
-
   static CompilationResult runForTesting(AndroidApp app, InternalOptions options)
       throws IOException, CompilationException {
     ExecutorService executor = ThreadUtils.getExecutorService(options);
diff --git a/src/main/java/com/android/tools/r8/graph/AssemblyWriter.java b/src/main/java/com/android/tools/r8/graph/AssemblyWriter.java
new file mode 100644
index 0000000..50ff578
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/graph/AssemblyWriter.java
@@ -0,0 +1,59 @@
+// 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.graph;
+
+import com.android.tools.r8.naming.ClassNameMapper;
+import com.android.tools.r8.utils.InternalOptions;
+import java.io.PrintStream;
+
+public class AssemblyWriter extends DexByteCodeWriter {
+
+  public AssemblyWriter(DexApplication application, InternalOptions options) {
+    super(application, options);
+  }
+
+  @Override
+  String getFileEnding() {
+    return ".dump";
+  }
+
+  @Override
+  void writeClassHeader(DexProgramClass clazz, PrintStream ps) {
+    String clazzName;
+    if (application.getProguardMap() != null) {
+      clazzName = application.getProguardMap().originalNameOf(clazz.type);
+    } else {
+      clazzName = clazz.type.toSourceString();
+    }
+    ps.println("# Bytecode for");
+    ps.println("# Class: '" + clazzName + "'");
+    ps.println();
+  }
+
+  @Override
+  void writeField(DexEncodedField field, PrintStream ps) {
+    // Not implemented, yet.
+  }
+
+  @Override
+  void writeMethod(DexEncodedMethod method, PrintStream ps) {
+    ClassNameMapper naming = application.getProguardMap();
+    String methodName = naming != null
+        ? naming.originalSignatureOf(method.method).toString()
+        : method.method.name.toString();
+    ps.println("#");
+    ps.println("# Method: '" + methodName + "':");
+    ps.println("#");
+    ps.println();
+    Code code = method.getCode();
+    if (code != null) {
+      ps.println(code.toString(method, naming));
+    }
+  }
+
+  @Override
+  void writeClassFooter(DexProgramClass clazz, PrintStream ps) {
+
+  }
+}
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 b79f30e..a84c90b 100644
--- a/src/main/java/com/android/tools/r8/graph/DexApplication.java
+++ b/src/main/java/com/android/tools/r8/graph/DexApplication.java
@@ -8,23 +8,14 @@
 
 import com.android.tools.r8.errors.Unreachable;
 import com.android.tools.r8.naming.ClassNameMapper;
-import com.android.tools.r8.utils.InternalOptions;
 import com.android.tools.r8.utils.ProgramClassCollection;
-import com.android.tools.r8.utils.StringUtils;
 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.io.ByteArrayOutputStream;
-import java.io.IOException;
-import java.io.PrintStream;
-import java.nio.charset.StandardCharsets;
-import java.nio.file.Files;
-import java.nio.file.Path;
 import java.util.ArrayList;
 import java.util.Collection;
 import java.util.Collections;
-import java.util.Comparator;
 import java.util.List;
 import java.util.Set;
 
@@ -99,148 +90,6 @@
     return proguardMap;
   }
 
-  private void disassemble(DexEncodedMethod method, ClassNameMapper naming, Path outputDir) {
-    if (method.getCode() != null) {
-      PrintStream ps = System.out;
-      try {
-        String clazzName;
-        String methodName;
-        if (naming != null) {
-          clazzName = naming.originalNameOf(method.method.holder);
-          methodName = naming.originalSignatureOf(method.method).toString();
-        } else {
-          clazzName = method.method.holder.toSourceString();
-          methodName = method.method.name.toString();
-        }
-        if (outputDir != null) {
-          Path directory = outputDir.resolve(clazzName.replace('.', '/'));
-          String name = methodName + ".dump";
-          if (name.length() > 200) {
-            name = StringUtils.computeMD5Hash(name);
-          }
-          Files.createDirectories(directory);
-          ps = new PrintStream(Files.newOutputStream(directory.resolve(name)));
-        }
-        ps.println("Bytecode for");
-        ps.println("Class: '" + clazzName + "'");
-        ps.println("Method: '" + methodName + "':");
-        ps.println(method.getCode().toString(method, naming));
-      } catch (IOException e) {
-        e.printStackTrace();
-      } finally {
-        if (outputDir != null) {
-          ps.flush();
-          ps.close();
-        }
-      }
-    }
-  }
-
-  /**
-   * Write disassembly for the application code in the provided directory.
-   *
-   * <p>If no directory is provided everything is written to System.out.
-   */
-  public void disassemble(Path outputDir, InternalOptions options) {
-    for (DexProgramClass clazz : programClasses.getAllClasses()) {
-      clazz.forEachMethod(method -> {
-        if (options.methodMatchesFilter(method)) {
-          disassemble(method, getProguardMap(), outputDir);
-        }
-      });
-    }
-  }
-
-  /**
-   * Return smali source for the application code.
-   */
-  public String smali(InternalOptions options) {
-    ByteArrayOutputStream os = new ByteArrayOutputStream();
-    PrintStream ps = new PrintStream(os);
-    smali(options, ps);
-    return new String(os.toByteArray(), StandardCharsets.UTF_8);
-  }
-
-  private void writeClassHeader(DexClass clazz, PrintStream ps) {
-    StringBuilder builder = new StringBuilder();
-    builder.append(".class ");
-    builder.append(clazz.accessFlags.toSmaliString());
-    builder.append(" ");
-    builder.append(clazz.type.toSmaliString());
-    builder.append("\n\n");
-    if (clazz.type != dexItemFactory.objectType) {
-      builder.append(".super ");
-      builder.append(clazz.superType.toSmaliString());
-      builder.append("\n");
-      for (DexType iface : clazz.interfaces.values) {
-        builder.append(".implements ");
-        builder.append(iface.toSmaliString());
-        builder.append("\n");
-      }
-    }
-    ps.append(builder.toString());
-  }
-
-  private void writeClassFooter(DexClass clazz, PrintStream ps) {
-    StringBuilder builder = new StringBuilder();
-    builder.append("# End of class ");
-    builder.append(clazz.type.toSmaliString());
-    builder.append("\n");
-    ps.append(builder.toString());
-  }
-
-  /**
-   * Write smali source for the application code on the provided PrintStream.
-   */
-  public void smali(InternalOptions options, PrintStream ps) {
-    List<DexProgramClass> classes = programClasses.getAllClasses();
-    classes.sort(Comparator.comparing(DexProgramClass::toSourceString));
-    boolean firstClass = true;
-    for (DexClass clazz : classes) {
-      boolean classHeaderWritten = false;
-      if (!options.hasMethodsFilter()) {
-        if (!firstClass) {
-          ps.append("\n");
-          firstClass = false;
-        }
-        writeClassHeader(clazz, ps);
-        classHeaderWritten = true;
-      }
-      for (DexEncodedMethod method : clazz.virtualMethods()) {
-        if (options.methodMatchesFilter(method)) {
-          if (!classHeaderWritten) {
-            if (!firstClass) {
-              ps.append("\n");
-              firstClass = false;
-            }
-            writeClassHeader(clazz, ps);
-            classHeaderWritten = true;
-          }
-          ps.append("\n");
-          ps.append(method.toSmaliString(getProguardMap()));
-        }
-      }
-      for (DexEncodedMethod method : clazz.directMethods()) {
-        if (options.methodMatchesFilter(method)) {
-          if (!classHeaderWritten) {
-            if (!firstClass) {
-              ps.append("\n");
-              firstClass = false;
-            }
-            writeClassHeader(clazz, ps);
-            classHeaderWritten = true;
-          }
-          ps.append("\n");
-          ps.append(method.toSmaliString(getProguardMap()));
-        }
-      }
-      if (classHeaderWritten) {
-        ps.append("\n");
-        writeClassFooter(clazz, ps);
-      }
-    }
-  }
-
   public abstract static class Builder<T extends Builder> {
     // We handle program class collection separately from classpath
     // and library class collections. Since while we assume program
diff --git a/src/main/java/com/android/tools/r8/graph/DexByteCodeWriter.java b/src/main/java/com/android/tools/r8/graph/DexByteCodeWriter.java
new file mode 100644
index 0000000..025a502
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/graph/DexByteCodeWriter.java
@@ -0,0 +1,99 @@
+// 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.graph;
+
+import com.android.tools.r8.utils.DescriptorUtils;
+import com.android.tools.r8.utils.InternalOptions;
+import com.android.tools.r8.utils.ThrowingFunction;
+import java.io.File;
+import java.io.IOException;
+import java.io.PrintStream;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.Arrays;
+import java.util.function.Consumer;
+
+public abstract class DexByteCodeWriter {
+
+  final DexApplication application;
+  final InternalOptions options;
+
+  DexByteCodeWriter(DexApplication application,
+      InternalOptions options) {
+    this.application = application;
+    this.options = options;
+  }
+
+  private void ensureParentExists(Path path) throws IOException {
+    Path parent = path.getParent();
+    if (parent != null) {
+      Files.createDirectories(parent);
+    }
+  }
+
+  private ThrowingFunction<DexClass, PrintStream, IOException> oneFilePerClass(Path path) {
+    return (clazz) -> {
+      String className = DescriptorUtils.descriptorToJavaType(clazz.type.toDescriptorString(),
+          application.getProguardMap());
+      Path classOutput = path.resolve(className.replace('.', File.separatorChar)
+          + getFileEnding());
+      ensureParentExists(classOutput);
+      return new PrintStream(Files.newOutputStream(classOutput));
+    };
+  }
+
+  public void write(Path path) throws IOException {
+    if (Files.isDirectory(path)) {
+      write(oneFilePerClass(path), PrintStream::close);
+    } else {
+      ensureParentExists(path);
+      try (PrintStream ps = new PrintStream(Files.newOutputStream(path))) {
+        write(ps);
+      }
+    }
+  }
+
+  public void write(PrintStream output) throws IOException {
+    write(x -> output, x -> {
+    });
+  }
+
+  private void write(ThrowingFunction<DexClass, PrintStream, IOException> outputStreamProvider,
+      Consumer<PrintStream> closer)
+      throws IOException {
+    for (DexProgramClass clazz : application.classes()) {
+      if (anyMethodMatches(clazz)) {
+        PrintStream ps = outputStreamProvider.apply(clazz);
+        try {
+          writeClass(clazz, outputStreamProvider.apply(clazz));
+        } finally {
+          closer.accept(ps);
+        }
+      }
+    }
+  }
+
+  private boolean anyMethodMatches(DexClass clazz) {
+    return !options.hasMethodsFilter()
+        || Arrays.stream(clazz.virtualMethods()).anyMatch(options::methodMatchesFilter)
+        || Arrays.stream(clazz.directMethods()).anyMatch(options::methodMatchesFilter);
+  }
+
+  private void writeClass(DexProgramClass clazz, PrintStream ps) {
+    writeClassHeader(clazz, ps);
+    clazz.forEachField(field -> writeField(field, ps));
+    clazz.forEachMethod(method -> writeMethod(method, ps));
+    writeClassFooter(clazz, ps);
+  }
+
+  abstract String getFileEnding();
+
+  abstract void writeClassHeader(DexProgramClass clazz, PrintStream ps);
+
+  abstract void writeField(DexEncodedField field, PrintStream ps);
+
+  abstract void writeMethod(DexEncodedMethod method, PrintStream ps);
+
+  abstract void writeClassFooter(DexProgramClass clazz, PrintStream ps);
+}
diff --git a/src/main/java/com/android/tools/r8/graph/SmaliWriter.java b/src/main/java/com/android/tools/r8/graph/SmaliWriter.java
new file mode 100644
index 0000000..480e549
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/graph/SmaliWriter.java
@@ -0,0 +1,70 @@
+package com.android.tools.r8.graph;
+
+import com.android.tools.r8.errors.CompilationError;
+import com.android.tools.r8.utils.InternalOptions;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.PrintStream;
+import java.nio.charset.StandardCharsets;
+
+public class SmaliWriter extends DexByteCodeWriter {
+
+  public SmaliWriter(DexApplication application,
+      InternalOptions options) {
+    super(application, options);
+  }
+
+  /** Return smali source for the application code. */
+  public static String smali(DexApplication application, InternalOptions options) {
+    SmaliWriter writer = new SmaliWriter(application, options);
+    ByteArrayOutputStream os = new ByteArrayOutputStream();
+    try (PrintStream ps = new PrintStream(os)) {
+      writer.write(ps);
+    } catch (IOException e) {
+      throw new CompilationError("Failed to generate smali sting", e);
+    }
+    return new String(os.toByteArray(), StandardCharsets.UTF_8);
+  }
+
+  @Override
+  String getFileEnding() {
+    return ".smali";
+  }
+
+  @Override
+  void writeClassHeader(DexProgramClass clazz, PrintStream ps) {
+    ps.append(".class ");
+    ps.append(clazz.accessFlags.toSmaliString());
+    ps.append(" ");
+    ps.append(clazz.type.toSmaliString());
+    ps.append("\n\n");
+    if (clazz.type != application.dexItemFactory.objectType) {
+      ps.append(".super ");
+      ps.append(clazz.superType.toSmaliString());
+      ps.append("\n");
+      for (DexType iface : clazz.interfaces.values) {
+        ps.append(".implements ");
+        ps.append(iface.toSmaliString());
+        ps.append("\n");
+      }
+    }
+  }
+
+  @Override
+  void writeClassFooter(DexProgramClass clazz, PrintStream ps) {
+    ps.append("# End of class ");
+    ps.append(clazz.type.toSmaliString());
+    ps.append("\n");
+  }
+
+  @Override
+  void writeMethod(DexEncodedMethod method, PrintStream ps) {
+    ps.println();
+    ps.println(method.toSmaliString(application.getProguardMap()));
+  }
+
+  @Override
+  void writeField(DexEncodedField field, PrintStream ps) {
+    // Not yet implemented.
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/utils/ThrowingFunction.java b/src/main/java/com/android/tools/r8/utils/ThrowingFunction.java
new file mode 100644
index 0000000..ab33e81
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/utils/ThrowingFunction.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 java.util.function.Function;
+
+/**
+ * Similar to a {@link Function} but throws a single {@link Throwable}.
+ *
+ * @param <T> the type of the first argument
+ * @param <R> the return type
+ * @param <E> the type of the {@link Throwable}
+ */
+@FunctionalInterface
+public interface ThrowingFunction<T, R, E extends Throwable> {
+
+  R apply(T t) throws E;
+}
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 d455841..0d2c5af 100644
--- a/src/test/java/com/android/tools/r8/internal/R8DisassemblerTest.java
+++ b/src/test/java/com/android/tools/r8/internal/R8DisassemblerTest.java
@@ -7,7 +7,6 @@
 
 import com.android.tools.r8.CompilationException;
 import com.android.tools.r8.Disassemble;
-import com.android.tools.r8.R8;
 import com.android.tools.r8.shaking.ProguardRuleParserException;
 import com.android.tools.r8.utils.FileUtils;
 import java.io.IOException;
@@ -15,19 +14,35 @@
 import java.io.PrintStream;
 import java.nio.file.Files;
 import java.nio.file.Paths;
+import java.util.Arrays;
 import java.util.concurrent.ExecutionException;
 import java.util.stream.Collectors;
 import org.junit.Test;
-import org.junit.experimental.theories.Theories;
 import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameter;
+import org.junit.runners.Parameterized.Parameters;
 
 // Invoke R8 on the dex files extracted from GMSCore.apk to disassemble the dex code.
-@RunWith(Theories.class)
+@RunWith(Parameterized.class)
 public class R8DisassemblerTest {
 
   static final String APP_DIR = "third_party/gmscore/v5/";
 
-  public void testDisassemble(boolean deobfuscate, boolean smali)
+  @Parameters(name = "deobfuscate: {0} smali: {1}")
+  public static Iterable<Object[]> data() {
+    return Arrays
+        .asList(new Object[][]{{false, false}, {false, true}, {true, false}, {true, true}});
+  }
+
+  @Parameter(0)
+  public boolean deobfuscate;
+
+  @Parameter(1)
+  public boolean smali;
+
+  @Test
+  public void testDisassemble()
       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.
@@ -46,30 +61,11 @@
           Files.list(Paths.get(APP_DIR))
               .filter(FileUtils::isDexFile)
               .collect(Collectors.toList()));
-      R8.disassemble(builder.build());
+      Disassemble.DisassembleCommand command = builder.build();
+      Disassemble.disassemble(command);
     } finally {
       // Restore System.out for good measure.
       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 ea0d767..b35e768 100644
--- a/src/test/java/com/android/tools/r8/smali/SmaliDisassembleTest.java
+++ b/src/test/java/com/android/tools/r8/smali/SmaliDisassembleTest.java
@@ -9,6 +9,7 @@
 import com.android.tools.r8.dex.ApplicationReader;
 import com.android.tools.r8.errors.DexOverflowException;
 import com.android.tools.r8.graph.DexApplication;
+import com.android.tools.r8.graph.SmaliWriter;
 import com.android.tools.r8.utils.AndroidApp;
 import com.android.tools.r8.utils.InternalOptions;
 import com.android.tools.r8.utils.Smali;
@@ -32,7 +33,7 @@
                   new InternalOptions(),
                   new Timing("SmaliTest"))
               .read();
-      assertEquals(smali, application.smali(new InternalOptions()));
+      assertEquals(smali, SmaliWriter.smali(application, new InternalOptions()));
     } catch (IOException | RecognitionException | ExecutionException | DexOverflowException e) {
       throw new RuntimeException(e);
     }
@@ -73,7 +74,7 @@
             "\n" +
             "# End of class LTest;\n";
 
-    assertEquals(expected, application.smali(new InternalOptions()));
+    assertEquals(expected, SmaliWriter.smali(application, new InternalOptions()));
 
     roundTripRawSmali(expected);
   }
@@ -129,7 +130,7 @@
             "\n" +
             "# End of class LTest;\n";
 
-    assertEquals(expected, application.smali(new InternalOptions()));
+    assertEquals(expected, SmaliWriter.smali(application, new InternalOptions()));
 
     roundTripRawSmali(expected);
   }
@@ -185,7 +186,7 @@
             "\n" +
             "# End of class LTest;\n";
 
-    assertEquals(expected, application.smali(new InternalOptions()));
+    assertEquals(expected, SmaliWriter.smali(application, new InternalOptions()));
 
     roundTripRawSmali(expected);
   }
@@ -228,7 +229,7 @@
             "\n" +
             "# End of class LTest;\n";
 
-    assertEquals(expected, application.smali(new InternalOptions()));
+    assertEquals(expected, SmaliWriter.smali(application, new InternalOptions()));
 
     roundTripRawSmali(expected);
   }
@@ -271,7 +272,7 @@
             "\n" +
             "# End of class LTest;\n";
 
-    assertEquals(expected, application.smali(new InternalOptions()));
+    assertEquals(expected, SmaliWriter.smali(application, new InternalOptions()));
 
     roundTripRawSmali(expected);
   }
@@ -314,7 +315,7 @@
             "\n" +
             "# End of class LTest;\n";
 
-    assertEquals(expected, application.smali(new InternalOptions()));
+    assertEquals(expected, SmaliWriter.smali(application, new InternalOptions()));
 
     roundTripRawSmali(expected);
   }
@@ -357,7 +358,7 @@
             "\n" +
             "# End of class LTest;\n";
 
-    assertEquals(expected, application.smali(new InternalOptions()));
+    assertEquals(expected, SmaliWriter.smali(application, new InternalOptions()));
 
     roundTripRawSmali(expected);
   }
@@ -380,7 +381,7 @@
             "\n" +
             "# End of class LTest;\n";
 
-    assertEquals(expected, application.smali(new InternalOptions()));
+    assertEquals(expected, SmaliWriter.smali(application, new InternalOptions()));
 
     roundTripRawSmali(expected);
   }
@@ -404,7 +405,7 @@
             "\n" +
             "# End of class LTest;\n";
 
-    assertEquals(expected, application.smali(new InternalOptions()));
+    assertEquals(expected, SmaliWriter.smali(application, new InternalOptions()));
 
     roundTripRawSmali(expected);
   }