Extend class generator to create empty DexProgramClass

Change-Id: I19ea4e03a675ffe3c06e0961ab691609fb700db1
diff --git a/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java b/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java
index 51a4a4a..8f74d49 100644
--- a/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java
+++ b/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java
@@ -573,11 +573,11 @@
   }
 
   public boolean belongsToDirectPool() {
-    return accessFlags.isStatic() || accessFlags.isPrivate() || accessFlags.isConstructor();
+    return accessFlags.belongsToDirectPool();
   }
 
   public boolean belongsToVirtualPool() {
-    return !belongsToDirectPool();
+    return accessFlags.belongsToVirtualPool();
   }
 
   @Override
diff --git a/src/main/java/com/android/tools/r8/graph/MethodAccessFlags.java b/src/main/java/com/android/tools/r8/graph/MethodAccessFlags.java
index a20ab1d..12da839 100644
--- a/src/main/java/com/android/tools/r8/graph/MethodAccessFlags.java
+++ b/src/main/java/com/android/tools/r8/graph/MethodAccessFlags.java
@@ -65,6 +65,14 @@
     return new Builder();
   }
 
+  public boolean belongsToDirectPool() {
+    return isStatic() || isPrivate() || isConstructor();
+  }
+
+  public boolean belongsToVirtualPool() {
+    return !belongsToDirectPool();
+  }
+
   @Override
   public MethodAccessFlags copy() {
     return new MethodAccessFlags(originalFlags, modifiedFlags);
diff --git a/src/main/java/com/android/tools/r8/startup/InstrumentationServer.java b/src/main/java/com/android/tools/r8/startup/InstrumentationServer.java
index 98555f6..58f4d0f 100644
--- a/src/main/java/com/android/tools/r8/startup/InstrumentationServer.java
+++ b/src/main/java/com/android/tools/r8/startup/InstrumentationServer.java
@@ -8,4 +8,59 @@
 
 package com.android.tools.r8.startup;
 
-public class InstrumentationServer {}
+import com.android.tools.r8.ProgramResource.Kind;
+import com.android.tools.r8.graph.ClassAccessFlags;
+import com.android.tools.r8.graph.DexAnnotationSet;
+import com.android.tools.r8.graph.DexEncodedField;
+import com.android.tools.r8.graph.DexEncodedMethod;
+import com.android.tools.r8.graph.DexItemFactory;
+import com.android.tools.r8.graph.DexProgramClass;
+import com.android.tools.r8.graph.DexTypeList;
+import com.android.tools.r8.graph.EnclosingMethodAttribute;
+import com.android.tools.r8.graph.GenericSignature.ClassSignature;
+import com.android.tools.r8.graph.MethodCollection.MethodCollectionFactory;
+import com.android.tools.r8.graph.NestHostClassAttribute;
+import com.android.tools.r8.origin.Origin;
+import java.util.Collections;
+
+public final class InstrumentationServer {
+  public static DexProgramClass createClass(DexItemFactory dexItemFactory) {
+    return new DexProgramClass(
+        dexItemFactory.createType("Lcom/android/tools/r8/startup/InstrumentationServer;"),
+        Kind.CF,
+        Origin.unknown(),
+        ClassAccessFlags.fromCfAccessFlags(1025),
+        null,
+        DexTypeList.empty(),
+        dexItemFactory.createString("InstrumentationServer"),
+        NestHostClassAttribute.none(),
+        Collections.emptyList(),
+        Collections.emptyList(),
+        EnclosingMethodAttribute.none(),
+        Collections.emptyList(),
+        ClassSignature.noSignature(),
+        DexAnnotationSet.empty(),
+        createStaticFields(dexItemFactory),
+        createInstanceFields(dexItemFactory),
+        MethodCollectionFactory.fromMethods(
+            createDirectMethods(dexItemFactory), createVirtualMethods(dexItemFactory)),
+        dexItemFactory.getSkipNameValidationForTesting(),
+        DexProgramClass::invalidChecksumRequest);
+  }
+
+  private static DexEncodedField[] createInstanceFields(DexItemFactory dexItemFactory) {
+    return new DexEncodedField[0];
+  }
+
+  private static DexEncodedField[] createStaticFields(DexItemFactory dexItemFactory) {
+    return new DexEncodedField[0];
+  }
+
+  private static DexEncodedMethod[] createDirectMethods(DexItemFactory dexItemFactory) {
+    return new DexEncodedMethod[0];
+  }
+
+  private static DexEncodedMethod[] createVirtualMethods(DexItemFactory dexItemFactory) {
+    return new DexEncodedMethod[0];
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/startup/InstrumentationServerImpl.java b/src/main/java/com/android/tools/r8/startup/InstrumentationServerImpl.java
index e9a3ebf..c51fed7 100644
--- a/src/main/java/com/android/tools/r8/startup/InstrumentationServerImpl.java
+++ b/src/main/java/com/android/tools/r8/startup/InstrumentationServerImpl.java
@@ -8,4 +8,59 @@
 
 package com.android.tools.r8.startup;
 
-public class InstrumentationServerImpl {}
+import com.android.tools.r8.ProgramResource.Kind;
+import com.android.tools.r8.graph.ClassAccessFlags;
+import com.android.tools.r8.graph.DexAnnotationSet;
+import com.android.tools.r8.graph.DexEncodedField;
+import com.android.tools.r8.graph.DexEncodedMethod;
+import com.android.tools.r8.graph.DexItemFactory;
+import com.android.tools.r8.graph.DexProgramClass;
+import com.android.tools.r8.graph.DexTypeList;
+import com.android.tools.r8.graph.EnclosingMethodAttribute;
+import com.android.tools.r8.graph.GenericSignature.ClassSignature;
+import com.android.tools.r8.graph.MethodCollection.MethodCollectionFactory;
+import com.android.tools.r8.graph.NestHostClassAttribute;
+import com.android.tools.r8.origin.Origin;
+import java.util.Collections;
+
+public final class InstrumentationServerImpl {
+  public static DexProgramClass createClass(DexItemFactory dexItemFactory) {
+    return new DexProgramClass(
+        dexItemFactory.createType("Lcom/android/tools/r8/startup/InstrumentationServerImpl;"),
+        Kind.CF,
+        Origin.unknown(),
+        ClassAccessFlags.fromCfAccessFlags(1),
+        null,
+        DexTypeList.empty(),
+        dexItemFactory.createString("InstrumentationServerImpl"),
+        NestHostClassAttribute.none(),
+        Collections.emptyList(),
+        Collections.emptyList(),
+        EnclosingMethodAttribute.none(),
+        Collections.emptyList(),
+        ClassSignature.noSignature(),
+        DexAnnotationSet.empty(),
+        createStaticFields(dexItemFactory),
+        createInstanceFields(dexItemFactory),
+        MethodCollectionFactory.fromMethods(
+            createDirectMethods(dexItemFactory), createVirtualMethods(dexItemFactory)),
+        dexItemFactory.getSkipNameValidationForTesting(),
+        DexProgramClass::invalidChecksumRequest);
+  }
+
+  private static DexEncodedField[] createInstanceFields(DexItemFactory dexItemFactory) {
+    return new DexEncodedField[0];
+  }
+
+  private static DexEncodedField[] createStaticFields(DexItemFactory dexItemFactory) {
+    return new DexEncodedField[0];
+  }
+
+  private static DexEncodedMethod[] createDirectMethods(DexItemFactory dexItemFactory) {
+    return new DexEncodedMethod[0];
+  }
+
+  private static DexEncodedMethod[] createVirtualMethods(DexItemFactory dexItemFactory) {
+    return new DexEncodedMethod[0];
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/cfmethodgeneration/CfClassGenerator.java b/src/test/java/com/android/tools/r8/cfmethodgeneration/CfClassGenerator.java
index 353c560..e5cab71 100644
--- a/src/test/java/com/android/tools/r8/cfmethodgeneration/CfClassGenerator.java
+++ b/src/test/java/com/android/tools/r8/cfmethodgeneration/CfClassGenerator.java
@@ -4,32 +4,172 @@
 
 package com.android.tools.r8.cfmethodgeneration;
 
+import static com.android.tools.r8.utils.PredicateUtils.not;
+
+import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.graph.FieldAccessFlags;
+import com.android.tools.r8.graph.MethodAccessFlags;
 import com.android.tools.r8.utils.FileUtils;
-import java.io.File;
+import com.android.tools.r8.utils.StringUtils;
 import java.io.IOException;
-import java.io.PrintStream;
-import java.nio.file.Files;
-import java.nio.file.Path;
+import java.util.function.Predicate;
 
 public abstract class CfClassGenerator extends CodeGenerationBase {
 
-  public String generateClass() throws IOException {
-    File temporaryFile = File.createTempFile("output-", ".java");
-    generateRawOutput(temporaryFile.toPath());
-    String result = formatRawOutput(temporaryFile.toPath());
-    temporaryFile.deleteOnExit();
-    return result;
+  private final CfCodeGeneratorImportCollection imports = new CfCodeGeneratorImportCollection();
+
+  public abstract Class<?> getGeneratedClass();
+
+  @Override
+  protected DexType getGeneratedType() {
+    return factory.createType(descriptor(getGeneratedClass()));
   }
 
-  private void generateRawOutput(Path temporaryFile) throws IOException {
-    try (PrintStream printer = new PrintStream(Files.newOutputStream(temporaryFile))) {
-      printer.print(getHeaderString());
-      printer.println("public class " + getGeneratedClassName() + " {");
-      printer.println("}");
+  public String generateClass() throws IOException {
+    return formatRawOutput(generateRawOutput());
+  }
+
+  private String generateRawOutput() {
+    String classDeclaration = generateClassDeclaration();
+    return StringUtils.lines(getHeaderString(), imports.generateImports(), classDeclaration);
+  }
+
+  private String generateClassDeclaration() {
+    JavaStringBuilder builder = new JavaStringBuilder();
+    builder.append("public final class " + getGeneratedClassName() + " ").appendOpeningBrace();
+    generateCreateClassMethod(builder);
+    generateCreateFieldsMethod(builder, "createInstanceFields", not(FieldAccessFlags::isStatic));
+    generateCreateFieldsMethod(builder, "createStaticFields", FieldAccessFlags::isStatic);
+    generateCreateMethodsMethod(
+        builder, "createDirectMethods", MethodAccessFlags::belongsToDirectPool);
+    generateCreateMethodsMethod(
+        builder, "createVirtualMethods", MethodAccessFlags::belongsToVirtualPool);
+    builder.appendClosingBrace();
+    return builder.toString();
+  }
+
+  private void generateCreateClassMethod(JavaStringBuilder builder) {
+    builder
+        .startLine()
+        .append("public static ")
+        .append(imports.getDexProgramClass())
+        .append(" createClass(")
+        .append(imports.getDexItemFactory())
+        .append(" dexItemFactory) ")
+        .appendOpeningBrace();
+
+    builder
+        .startLine()
+        .append("return new ")
+        .append(imports.getDexProgramClass())
+        .appendOpeningMultiLineParenthesis();
+
+    builder
+        .startLine()
+        .append("dexItemFactory.createType(\"")
+        .append(getGeneratedClassDescriptor())
+        .appendLine("\"),");
+
+    builder.startLine().append(imports.getProgramResourceKind()).appendLine(".CF,");
+
+    builder.startLine().append(imports.getOrigin()).appendLine(".unknown(),");
+
+    builder
+        .startLine()
+        .append(imports.getClassAccessFlags())
+        .append(".fromCfAccessFlags(")
+        .append(getGeneratedClass().getModifiers())
+        .appendLine("),");
+
+    builder.startLine().appendLine("null,");
+
+    builder.startLine().append(imports.getDexTypeList()).appendLine(".empty(),");
+
+    builder
+        .startLine()
+        .append("dexItemFactory.createString(\"")
+        .append(getGeneratedClassName())
+        .appendLine("\"),");
+
+    builder.startLine().append(imports.getNestHostClassAttribute()).appendLine(".none(),");
+
+    for (int i = 0; i < 2; i++) {
+      builder.startLine().append(imports.getJavaUtilCollections()).appendLine(".emptyList(),");
     }
+
+    builder.startLine().append(imports.getEnclosingMethodAttribute()).appendLine(".none(),");
+
+    builder.startLine().append(imports.getJavaUtilCollections()).appendLine(".emptyList(),");
+
+    builder.startLine().append(imports.getClassSignature()).appendLine(".noSignature(),");
+
+    builder.startLine().append(imports.getDexAnnotationSet()).appendLine(".empty(),");
+
+    builder.startLine().appendLine("createStaticFields(dexItemFactory),");
+
+    builder.startLine().appendLine("createInstanceFields(dexItemFactory),");
+
+    builder
+        .startLine()
+        .append(imports.getMethodCollectionFactory())
+        .appendLine(
+            ".fromMethods(createDirectMethods(dexItemFactory),"
+                + " createVirtualMethods(dexItemFactory)),");
+
+    builder.startLine().appendLine("dexItemFactory.getSkipNameValidationForTesting(),");
+
+    builder.startLine().append(imports.getDexProgramClass()).append("::invalidChecksumRequest");
+
+    builder.appendClosingMultiLineParenthesis().appendLine(';');
+    builder.appendClosingBrace();
+  }
+
+  private void generateCreateFieldsMethod(
+      JavaStringBuilder builder, String methodName, Predicate<FieldAccessFlags> predicate) {
+    builder
+        .startLine()
+        .append("private static ")
+        .append(imports.getDexEncodedField())
+        .append("[] ")
+        .append(methodName)
+        .append("(")
+        .append(imports.getDexItemFactory())
+        .append(" dexItemFactory) ")
+        .appendOpeningBrace();
+
+    builder
+        .startLine()
+        .append("return new ")
+        .append(imports.getDexEncodedField())
+        .appendLine("[0];");
+
+    builder.appendClosingBrace();
+  }
+
+  private void generateCreateMethodsMethod(
+      JavaStringBuilder builder, String methodName, Predicate<MethodAccessFlags> predicate) {
+    builder
+        .startLine()
+        .append("private static ")
+        .append(imports.getDexEncodedMethod())
+        .append("[] ")
+        .append(methodName)
+        .append("(")
+        .append(imports.getDexItemFactory())
+        .append(" dexItemFactory) ")
+        .appendOpeningBrace();
+
+    builder
+        .startLine()
+        .append("return new ")
+        .append(imports.getDexEncodedMethod())
+        .appendLine("[0];");
+
+    builder.appendClosingBrace();
   }
 
   public void writeClassToFile() throws IOException {
     FileUtils.writeToFile(getGeneratedFile(), null, generateClass().getBytes());
   }
 }
+
diff --git a/src/test/java/com/android/tools/r8/cfmethodgeneration/CfCodeGeneratorImportCollection.java b/src/test/java/com/android/tools/r8/cfmethodgeneration/CfCodeGeneratorImportCollection.java
new file mode 100644
index 0000000..9da5527
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/cfmethodgeneration/CfCodeGeneratorImportCollection.java
@@ -0,0 +1,90 @@
+// Copyright (c) 2022, 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.cfmethodgeneration;
+
+import java.util.LinkedHashSet;
+import java.util.Set;
+
+public class CfCodeGeneratorImportCollection {
+
+  private final Set<String> imports = new LinkedHashSet<>();
+
+  String generateImports() {
+    StringBuilder builder = new StringBuilder();
+    for (String className : imports) {
+      builder.append("import ").append(className).append(";").append('\n');
+    }
+    return builder.toString();
+  }
+
+  String getClassAccessFlags() {
+    return getR8ClassName("graph", "ClassAccessFlags");
+  }
+
+  String getClassSignature() {
+    return getR8ClassName("graph.GenericSignature", "ClassSignature");
+  }
+
+  String getDexAnnotationSet() {
+    return getR8ClassName("graph", "DexAnnotationSet");
+  }
+
+  String getDexEncodedField() {
+    return getR8ClassName("graph", "DexEncodedField");
+  }
+
+  String getDexEncodedMethod() {
+    return getR8ClassName("graph", "DexEncodedMethod");
+  }
+
+  String getDexItemFactory() {
+    return getR8ClassName("graph", "DexItemFactory");
+  }
+
+  String getDexProgramClass() {
+    return getR8ClassName("graph", "DexProgramClass");
+  }
+
+  String getDexTypeList() {
+    return getR8ClassName("graph", "DexTypeList");
+  }
+
+  String getEnclosingMethodAttribute() {
+    return getR8ClassName("graph", "EnclosingMethodAttribute");
+  }
+
+  String getJavaUtilCollections() {
+    addImport("java.util.Collections");
+    return "Collections";
+  }
+
+  String getMethodCollectionFactory() {
+    return getR8ClassName("graph.MethodCollection", "MethodCollectionFactory");
+  }
+
+  String getNestHostClassAttribute() {
+    return getR8ClassName("graph", "NestHostClassAttribute");
+  }
+
+  String getOrigin() {
+    return getR8ClassName("origin", "Origin");
+  }
+
+  String getProgramResourceKind() {
+    return getR8ClassName("ProgramResource", "Kind");
+  }
+
+  private String getR8ClassName(String context, String name) {
+    String canonicalName =
+        "com.android.tools.r8." + (context != null ? (context + ".") : "") + name;
+    addImport(canonicalName);
+    return name;
+  }
+
+  private String addImport(String name) {
+    imports.add(name);
+    return name;
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/cfmethodgeneration/CodeGenerationBase.java b/src/test/java/com/android/tools/r8/cfmethodgeneration/CodeGenerationBase.java
index f1aa400..ee1a991 100644
--- a/src/test/java/com/android/tools/r8/cfmethodgeneration/CodeGenerationBase.java
+++ b/src/test/java/com/android/tools/r8/cfmethodgeneration/CodeGenerationBase.java
@@ -11,7 +11,9 @@
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.utils.StringUtils;
 import com.google.common.collect.ImmutableList;
+import java.io.File;
 import java.io.IOException;
+import java.nio.file.Files;
 import java.nio.file.Path;
 import java.nio.file.Paths;
 
@@ -24,6 +26,14 @@
 
   protected final DexItemFactory factory = new DexItemFactory();
 
+  public static String formatRawOutput(String rawOutput) throws IOException {
+    File temporaryFile = File.createTempFile("output-", ".java");
+    Files.write(temporaryFile.toPath(), rawOutput.getBytes());
+    String result = formatRawOutput(temporaryFile.toPath());
+    temporaryFile.deleteOnExit();
+    return result;
+  }
+
   public static String formatRawOutput(Path tempFile) throws IOException {
     // Apply google format.
     ProcessBuilder builder =
@@ -48,6 +58,10 @@
     return content;
   }
 
+  public String getGeneratedClassDescriptor() {
+    return getGeneratedType().toDescriptorString();
+  }
+
   public String getGeneratedClassName() {
     return getGeneratedType().getName();
   }
diff --git a/src/test/java/com/android/tools/r8/cfmethodgeneration/JavaStringBuilder.java b/src/test/java/com/android/tools/r8/cfmethodgeneration/JavaStringBuilder.java
new file mode 100644
index 0000000..13f2347
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/cfmethodgeneration/JavaStringBuilder.java
@@ -0,0 +1,71 @@
+// Copyright (c) 2022, 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.cfmethodgeneration;
+
+public class JavaStringBuilder {
+
+  private final StringBuilder builder = new StringBuilder();
+  private int indentation = 0;
+
+  public JavaStringBuilder append(char c) {
+    builder.append(c);
+    return this;
+  }
+
+  public JavaStringBuilder append(int i) {
+    builder.append(i);
+    return this;
+  }
+
+  public JavaStringBuilder append(String string) {
+    builder.append(string);
+    return this;
+  }
+
+  public JavaStringBuilder appendLine() {
+    return append('\n');
+  }
+
+  public JavaStringBuilder appendLine(char c) {
+    return append(c).appendLine();
+  }
+
+  public JavaStringBuilder appendLine(String string) {
+    return append(string).appendLine();
+  }
+
+  public JavaStringBuilder appendOpeningBrace() {
+    return appendLine('{').indent(2);
+  }
+
+  public JavaStringBuilder appendClosingBrace() {
+    return indent(-2).startLine().appendLine('}');
+  }
+
+  public JavaStringBuilder appendOpeningMultiLineParenthesis() {
+    return appendLine('(').indent(4);
+  }
+
+  public JavaStringBuilder appendClosingMultiLineParenthesis() {
+    return append(')').indent(-4);
+  }
+
+  public JavaStringBuilder indent(int change) {
+    indentation += change;
+    return this;
+  }
+
+  public JavaStringBuilder startLine() {
+    for (int i = 0; i < indentation; i++) {
+      append(' ');
+    }
+    return this;
+  }
+
+  @Override
+  public String toString() {
+    return builder.toString();
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/startup/GenerateInstrumentationServerClassesTest.java b/src/test/java/com/android/tools/r8/startup/GenerateInstrumentationServerClassesTest.java
index 72c73ec..23f3436 100644
--- a/src/test/java/com/android/tools/r8/startup/GenerateInstrumentationServerClassesTest.java
+++ b/src/test/java/com/android/tools/r8/startup/GenerateInstrumentationServerClassesTest.java
@@ -11,7 +11,6 @@
 import com.android.tools.r8.TestParametersCollection;
 import com.android.tools.r8.TestRuntime.CfVm;
 import com.android.tools.r8.cfmethodgeneration.CfClassGenerator;
-import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.utils.FileUtils;
 import com.google.common.collect.ImmutableList;
 import java.io.IOException;
@@ -64,8 +63,8 @@
     }
 
     @Override
-    protected DexType getGeneratedType() {
-      return factory.createType(descriptor(clazz));
+    public Class<?> getGeneratedClass() {
+      return clazz;
     }
 
     @Override