Introduce a parser for startup configuration lines

Change-Id: If75f18b5b4edb53e2f90bebeda6a6a9c32abab0a
diff --git a/src/main/java/com/android/tools/r8/experimental/startup/StartupConfiguration.java b/src/main/java/com/android/tools/r8/experimental/startup/StartupConfiguration.java
index d6da209..7e4df1f 100644
--- a/src/main/java/com/android/tools/r8/experimental/startup/StartupConfiguration.java
+++ b/src/main/java/com/android/tools/r8/experimental/startup/StartupConfiguration.java
@@ -6,9 +6,7 @@
 
 import com.android.tools.r8.graph.DexItemFactory;
 import com.android.tools.r8.graph.DexMethod;
-import com.android.tools.r8.graph.DexProto;
 import com.android.tools.r8.graph.DexType;
-import com.android.tools.r8.utils.DescriptorUtils;
 import com.android.tools.r8.utils.ExceptionDiagnostic;
 import com.android.tools.r8.utils.FileUtils;
 import com.android.tools.r8.utils.Reporter;
@@ -76,110 +74,31 @@
 
   public static StartupConfiguration createStartupConfigurationFromLines(
       DexItemFactory dexItemFactory, Reporter reporter, List<String> startupDescriptors) {
+    StartupConfigurationParser<DexType, DexMethod, DexType> parser =
+        StartupConfigurationParser.createDexParser(dexItemFactory);
     List<StartupClass<DexType, DexMethod>> startupClasses = new ArrayList<>();
     for (String startupDescriptor : startupDescriptors) {
       if (startupDescriptor.isEmpty()) {
         continue;
       }
-      StartupClass.Builder<DexType, DexMethod> startupClassBuilder = StartupClass.builder();
-      startupDescriptor = parseSyntheticFlag(startupDescriptor, startupClassBuilder);
-      parseStartupClassOrMethod(
+      parser.parseLine(
           startupDescriptor,
-          dexItemFactory,
-          startupClass ->
-              startupClasses.add(startupClassBuilder.setClassReference(startupClass).build()),
+          startupClasses::add,
           // TODO(b/238173796): Startup methods should be added as startup methods.
           startupMethod ->
               startupClasses.add(
-                  startupClassBuilder.setClassReference(startupMethod.getHolderType()).build()),
-          actual ->
+                  StartupClass.dexBuilder()
+                      .setClassReference(startupMethod.getReference().getHolderType())
+                      .setFlags(startupMethod.getFlags())
+                      .build()),
+          error ->
               reporter.warning(
                   new StringDiagnostic(
-                      "Invalid descriptor for startup class or method: " + actual)));
+                      "Invalid descriptor for startup class or method: " + error)));
     }
     return new StartupConfiguration(startupClasses);
   }
 
-  public static String parseSyntheticFlag(
-      String startupDescriptor, StartupItem.Builder<?, ?, ?> startupItemBuilder) {
-    if (!startupDescriptor.isEmpty() && startupDescriptor.charAt(0) == 'S') {
-      startupItemBuilder.setSynthetic();
-      return startupDescriptor.substring(1);
-    }
-    return startupDescriptor;
-  }
-
-  public static void parseStartupClassOrMethod(
-      String startupDescriptor,
-      DexItemFactory dexItemFactory,
-      Consumer<DexType> startupClassConsumer,
-      Consumer<DexMethod> startupMethodConsumer,
-      Consumer<String> parseErrorHandler) {
-    int arrowStartIndex = getArrowStartIndex(startupDescriptor);
-    if (arrowStartIndex >= 0) {
-      DexMethod startupMethod =
-          parseStartupMethodDescriptor(startupDescriptor, arrowStartIndex, dexItemFactory);
-      if (startupMethod != null) {
-        startupMethodConsumer.accept(startupMethod);
-      } else {
-        parseErrorHandler.accept(startupDescriptor);
-      }
-    } else {
-      DexType startupClass = parseStartupClassDescriptor(startupDescriptor, dexItemFactory);
-      if (startupClass != null) {
-        startupClassConsumer.accept(startupClass);
-      } else {
-        parseErrorHandler.accept(startupDescriptor);
-      }
-    }
-  }
-
-  private static int getArrowStartIndex(String startupDescriptor) {
-    return startupDescriptor.indexOf("->");
-  }
-
-  private static DexType parseStartupClassDescriptor(
-      String startupClassDescriptor, DexItemFactory dexItemFactory) {
-    if (DescriptorUtils.isClassDescriptor(startupClassDescriptor)) {
-      return dexItemFactory.createType(startupClassDescriptor);
-    } else {
-      return null;
-    }
-  }
-
-  private static DexMethod parseStartupMethodDescriptor(
-      String startupMethodDescriptor, int arrowStartIndex, DexItemFactory dexItemFactory) {
-    String classDescriptor = startupMethodDescriptor.substring(0, arrowStartIndex);
-    DexType classType = parseStartupClassDescriptor(classDescriptor, dexItemFactory);
-    if (classType == null) {
-      return null;
-    }
-
-    int methodNameStartIndex = arrowStartIndex + 2;
-    String protoWithNameDescriptor = startupMethodDescriptor.substring(methodNameStartIndex);
-    int methodNameEndIndex = protoWithNameDescriptor.indexOf('(');
-    if (methodNameEndIndex <= 0) {
-      return null;
-    }
-    String methodName = protoWithNameDescriptor.substring(0, methodNameEndIndex);
-
-    String protoDescriptor = protoWithNameDescriptor.substring(methodNameEndIndex);
-    DexProto proto = parseStartupMethodProto(protoDescriptor, dexItemFactory);
-    return dexItemFactory.createMethod(classType, proto, methodName);
-  }
-
-  private static DexProto parseStartupMethodProto(
-      String protoDescriptor, DexItemFactory dexItemFactory) {
-    List<DexType> parameterTypes = new ArrayList<>();
-    for (String parameterTypeDescriptor :
-        DescriptorUtils.getArgumentTypeDescriptors(protoDescriptor)) {
-      parameterTypes.add(dexItemFactory.createType(parameterTypeDescriptor));
-    }
-    String returnTypeDescriptor = DescriptorUtils.getReturnTypeDescriptor(protoDescriptor);
-    DexType returnType = dexItemFactory.createType(returnTypeDescriptor);
-    return dexItemFactory.createProto(returnType, parameterTypes);
-  }
-
   public boolean hasStartupClasses() {
     return !startupClasses.isEmpty();
   }
diff --git a/src/main/java/com/android/tools/r8/experimental/startup/StartupConfigurationParser.java b/src/main/java/com/android/tools/r8/experimental/startup/StartupConfigurationParser.java
new file mode 100644
index 0000000..ff88cf2
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/experimental/startup/StartupConfigurationParser.java
@@ -0,0 +1,150 @@
+// 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.experimental.startup;
+
+import com.android.tools.r8.graph.DexItemFactory;
+import com.android.tools.r8.graph.DexMethod;
+import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.references.ClassReference;
+import com.android.tools.r8.references.MethodReference;
+import com.android.tools.r8.references.Reference;
+import com.android.tools.r8.references.TypeReference;
+import com.android.tools.r8.utils.DescriptorUtils;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.function.Consumer;
+import java.util.function.Function;
+
+public class StartupConfigurationParser<C, M, T> {
+
+  interface MethodFactory<C, M, T> {
+
+    M createMethod(
+        C methodHolder, String methodName, List<T> methodParameterTypes, T methodReturnType);
+  }
+
+  private final Function<String, C> classFactory;
+  private final MethodFactory<C, M, T> methodFactory;
+  private final Function<String, T> typeFactory;
+
+  StartupConfigurationParser(
+      Function<String, C> classFactory,
+      MethodFactory<C, M, T> methodFactory,
+      Function<String, T> typeFactory) {
+    this.classFactory = classFactory;
+    this.methodFactory = methodFactory;
+    this.typeFactory = typeFactory;
+  }
+
+  public static StartupConfigurationParser<DexType, DexMethod, DexType> createDexParser(
+      DexItemFactory dexItemFactory) {
+    return new StartupConfigurationParser<>(
+        dexItemFactory::createType,
+        (methodHolder, methodName, methodParameters, methodReturnType) ->
+            dexItemFactory.createMethod(
+                methodHolder,
+                dexItemFactory.createProto(methodReturnType, methodParameters),
+                dexItemFactory.createString(methodName)),
+        dexItemFactory::createType);
+  }
+
+  public static StartupConfigurationParser<ClassReference, MethodReference, TypeReference>
+      createReferenceParser() {
+    return new StartupConfigurationParser<>(
+        Reference::classFromDescriptor, Reference::method, Reference::typeFromDescriptor);
+  }
+
+  public void parseLine(
+      String startupDescriptor,
+      Consumer<? super StartupClass<C, M>> startupClassConsumer,
+      Consumer<? super StartupMethod<C, M>> startupMethodConsumer,
+      Consumer<String> parseErrorHandler) {
+    StartupItem.Builder<C, M, ?> startupItemBuilder = StartupItem.builder();
+    startupDescriptor = parseSyntheticFlag(startupDescriptor, startupItemBuilder);
+    parseStartupClassOrMethod(
+        startupDescriptor,
+        startupItemBuilder,
+        startupClassConsumer,
+        startupMethodConsumer,
+        parseErrorHandler);
+  }
+
+  private static String parseSyntheticFlag(
+      String startupDescriptor, StartupItem.Builder<?, ?, ?> startupItemBuilder) {
+    if (!startupDescriptor.isEmpty() && startupDescriptor.charAt(0) == 'S') {
+      startupItemBuilder.setSynthetic();
+      return startupDescriptor.substring(1);
+    }
+    return startupDescriptor;
+  }
+
+  private void parseStartupClassOrMethod(
+      String startupDescriptor,
+      StartupItem.Builder<C, M, ?> startupItemBuilder,
+      Consumer<? super StartupClass<C, M>> startupClassConsumer,
+      Consumer<? super StartupMethod<C, M>> startupMethodConsumer,
+      Consumer<String> parseErrorHandler) {
+    int arrowStartIndex = getArrowStartIndex(startupDescriptor);
+    if (arrowStartIndex >= 0) {
+      M startupMethod = parseStartupMethodDescriptor(startupDescriptor, arrowStartIndex);
+      if (startupMethod != null) {
+        startupMethodConsumer.accept(
+            startupItemBuilder.setMethodReference(startupMethod).buildStartupMethod());
+      } else {
+        parseErrorHandler.accept(startupDescriptor);
+      }
+    } else {
+      C startupClass = parseStartupClassDescriptor(startupDescriptor);
+      if (startupClass != null) {
+        startupClassConsumer.accept(
+            startupItemBuilder.setClassReference(startupClass).buildStartupClass());
+      } else {
+        parseErrorHandler.accept(startupDescriptor);
+      }
+    }
+  }
+
+  private static int getArrowStartIndex(String startupDescriptor) {
+    return startupDescriptor.indexOf("->");
+  }
+
+  private C parseStartupClassDescriptor(String startupClassDescriptor) {
+    if (DescriptorUtils.isClassDescriptor(startupClassDescriptor)) {
+      return classFactory.apply(startupClassDescriptor);
+    } else {
+      return null;
+    }
+  }
+
+  private M parseStartupMethodDescriptor(String startupMethodDescriptor, int arrowStartIndex) {
+    String classDescriptor = startupMethodDescriptor.substring(0, arrowStartIndex);
+    C methodHolder = parseStartupClassDescriptor(classDescriptor);
+    if (methodHolder == null) {
+      return null;
+    }
+
+    int methodNameStartIndex = arrowStartIndex + 2;
+    String protoWithNameDescriptor = startupMethodDescriptor.substring(methodNameStartIndex);
+    int methodNameEndIndex = protoWithNameDescriptor.indexOf('(');
+    if (methodNameEndIndex <= 0) {
+      return null;
+    }
+    String methodName = protoWithNameDescriptor.substring(0, methodNameEndIndex);
+
+    String protoDescriptor = protoWithNameDescriptor.substring(methodNameEndIndex);
+    return parseStartupMethodProto(methodHolder, methodName, protoDescriptor);
+  }
+
+  private M parseStartupMethodProto(C methodHolder, String methodName, String protoDescriptor) {
+    List<T> parameterTypes = new ArrayList<>();
+    for (String parameterTypeDescriptor :
+        DescriptorUtils.getArgumentTypeDescriptors(protoDescriptor)) {
+      parameterTypes.add(typeFactory.apply(parameterTypeDescriptor));
+    }
+    String returnTypeDescriptor = DescriptorUtils.getReturnTypeDescriptor(protoDescriptor);
+    T returnType = typeFactory.apply(returnTypeDescriptor);
+    return methodFactory.createMethod(methodHolder, methodName, parameterTypes, returnType);
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/experimental/startup/StartupItem.java b/src/main/java/com/android/tools/r8/experimental/startup/StartupItem.java
index d38b1ce..c745c90 100644
--- a/src/main/java/com/android/tools/r8/experimental/startup/StartupItem.java
+++ b/src/main/java/com/android/tools/r8/experimental/startup/StartupItem.java
@@ -120,13 +120,22 @@
 
     public StartupItem<C, M, ?> build() {
       if (classReference != null) {
-        return new StartupClass<>(flags, classReference);
+        return buildStartupClass();
       } else {
-        assert methodReference != null;
-        return new StartupMethod<>(flags, methodReference);
+        return buildStartupMethod();
       }
     }
 
+    public StartupClass<C, M> buildStartupClass() {
+      assert classReference != null;
+      return new StartupClass<>(flags, classReference);
+    }
+
+    public StartupMethod<C, M> buildStartupMethod() {
+      assert methodReference != null;
+      return new StartupMethod<>(flags, methodReference);
+    }
+
     @SuppressWarnings("unchecked")
     public B self() {
       return (B) this;
diff --git a/src/test/java/com/android/tools/r8/startup/utils/StartupTestingUtils.java b/src/test/java/com/android/tools/r8/startup/utils/StartupTestingUtils.java
index 695bb3a..7962792 100644
--- a/src/test/java/com/android/tools/r8/startup/utils/StartupTestingUtils.java
+++ b/src/test/java/com/android/tools/r8/startup/utils/StartupTestingUtils.java
@@ -15,12 +15,14 @@
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.ThrowableConsumer;
 import com.android.tools.r8.experimental.startup.StartupConfiguration;
+import com.android.tools.r8.experimental.startup.StartupConfigurationParser;
 import com.android.tools.r8.experimental.startup.StartupItem;
 import com.android.tools.r8.graph.DexItemFactory;
 import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.references.ClassReference;
 import com.android.tools.r8.references.MethodReference;
+import com.android.tools.r8.references.TypeReference;
 import com.android.tools.r8.utils.AndroidApiLevel;
 import com.android.tools.r8.utils.ClassReferenceUtils;
 import com.android.tools.r8.utils.MethodReferenceUtils;
@@ -71,23 +73,18 @@
   public static void removeStartupListFromStdout(
       D8TestRunResult runResult,
       Consumer<StartupItem<ClassReference, MethodReference, ?>> startupItemConsumer) {
-    DexItemFactory dexItemFactory = new DexItemFactory();
+    StartupConfigurationParser<ClassReference, MethodReference, TypeReference> parser =
+        StartupConfigurationParser.createReferenceParser();
     StringBuilder stdoutBuilder = new StringBuilder();
     String startupDescriptorPrefix = "[" + startupInstrumentationTag + "] ";
     for (String line : StringUtils.splitLines(runResult.getStdOut(), true)) {
       if (line.startsWith(startupDescriptorPrefix)) {
-        StartupItem.Builder<ClassReference, MethodReference, ?> startupItemBuilder =
-            StartupItem.builder();
         String message = line.substring(startupDescriptorPrefix.length());
-        message = StartupConfiguration.parseSyntheticFlag(message, startupItemBuilder);
-        StartupConfiguration.parseStartupClassOrMethod(
+        parser.parseLine(
             message,
-            dexItemFactory,
-            startupClass -> startupItemBuilder.setClassReference(startupClass.asClassReference()),
-            startupMethod ->
-                startupItemBuilder.setMethodReference(startupMethod.asMethodReference()),
+            startupItemConsumer,
+            startupItemConsumer,
             error -> fail("Unexpected parse error: " + error));
-        startupItemConsumer.accept(startupItemBuilder.build());
       } else {
         stdoutBuilder.append(line).append(System.lineSeparator());
       }