Move method optimizer utils to shared class

Change-Id: I979a58d27e73ae5afe22cd2ba97f992841dccd16
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/library/MethodOptimizerCapabilities.java b/src/main/java/com/android/tools/r8/ir/optimize/library/MethodOptimizerCapabilities.java
new file mode 100644
index 0000000..9549c17
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/optimize/library/MethodOptimizerCapabilities.java
@@ -0,0 +1,240 @@
+// Copyright (c) 2026, 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.ir.optimize.library;
+
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexString;
+import com.android.tools.r8.ir.code.IRCode;
+import com.android.tools.r8.ir.code.InstructionListIterator;
+import com.android.tools.r8.ir.code.InvokeMethod;
+import com.android.tools.r8.ir.optimize.AffectedValues;
+import java.util.function.BiPredicate;
+import java.util.function.Predicate;
+import java.util.function.UnaryOperator;
+
+public interface MethodOptimizerCapabilities {
+
+  AppView<?> getAppView();
+
+  default void optimizeStringToBooleanFunction(
+      IRCode code,
+      InstructionListIterator instructionIterator,
+      InvokeMethod invoke,
+      Predicate<DexString> fn) {
+    DexString firstArg = invoke.getFirstArgument().getConstStringOrNull();
+    if (firstArg != null) {
+      boolean replacement = fn.test(firstArg);
+      instructionIterator.replaceCurrentInstructionWithConstBoolean(code, replacement);
+    }
+  }
+
+  interface StringToIntFunction {
+
+    int apply(DexString s);
+  }
+
+  default void optimizeStringToIntFunction(
+      IRCode code,
+      InstructionListIterator instructionIterator,
+      InvokeMethod invoke,
+      StringToIntFunction fn) {
+    DexString firstArg = invoke.getFirstArgument().getConstStringOrNull();
+    if (firstArg != null) {
+      int replacement = fn.apply(firstArg);
+      instructionIterator.replaceCurrentInstructionWithConstInt(code, replacement);
+    }
+  }
+
+  default void optimizeStringToStringFunction(
+      IRCode code,
+      InstructionListIterator instructionIterator,
+      InvokeMethod invoke,
+      AffectedValues affectedValues,
+      UnaryOperator<DexString> fn) {
+    DexString firstArg = invoke.getFirstArgument().getConstStringOrNull();
+    if (firstArg != null) {
+      DexString replacement = fn.apply(firstArg);
+      replaceCurrentInstructionWithConstString(
+          code, instructionIterator, invoke, affectedValues, replacement);
+    }
+  }
+
+  interface StringIntToIntFunction {
+
+    int apply(DexString s, int i);
+  }
+
+  default void optimizeStringIntToIntFunction(
+      IRCode code,
+      InstructionListIterator instructionIterator,
+      InvokeMethod invoke,
+      StringIntToIntFunction fn) {
+    DexString firstArg = invoke.getFirstArgument().getConstStringOrNull();
+    if (firstArg != null && invoke.getSecondArgument().isConstInt()) {
+      int replacement = fn.apply(firstArg, invoke.getSecondArgument().getConstInt());
+      instructionIterator.replaceCurrentInstructionWithConstInt(code, replacement);
+    }
+  }
+
+  interface StringIntToStringFunction {
+
+    DexString apply(DexString s, int i);
+  }
+
+  default void optimizeStringIntToStringFunction(
+      IRCode code,
+      InstructionListIterator instructionIterator,
+      InvokeMethod invoke,
+      AffectedValues affectedValues,
+      StringIntToStringFunction fn) {
+    DexString firstArg = invoke.getFirstArgument().getConstStringOrNull();
+    if (firstArg != null && invoke.getSecondArgument().isConstInt()) {
+      DexString replacement = fn.apply(firstArg, invoke.getSecondArgument().getConstInt());
+      replaceCurrentInstructionWithConstString(
+          code, instructionIterator, invoke, affectedValues, replacement);
+    }
+  }
+
+  interface StringIntIntToIntFunction {
+
+    int apply(DexString s, int i, int j);
+  }
+
+  default void optimizeStringIntIntToIntFunction(
+      IRCode code,
+      InstructionListIterator instructionIterator,
+      InvokeMethod invoke,
+      StringIntIntToIntFunction fn) {
+    DexString firstArg = invoke.getFirstArgument().getConstStringOrNull();
+    if (firstArg != null
+        && invoke.getSecondArgument().isConstInt()
+        && invoke.getThirdArgument().isConstInt()) {
+      int replacement =
+          fn.apply(
+              firstArg,
+              invoke.getSecondArgument().getConstInt(),
+              invoke.getThirdArgument().getConstInt());
+      instructionIterator.replaceCurrentInstructionWithConstInt(code, replacement);
+    }
+  }
+
+  interface StringIntIntToStringFunction {
+
+    DexString apply(DexString s, int i, int j);
+  }
+
+  default void optimizeStringIntIntToStringFunction(
+      IRCode code,
+      InstructionListIterator instructionIterator,
+      InvokeMethod invoke,
+      AffectedValues affectedValues,
+      StringIntIntToStringFunction fn) {
+    DexString firstArg = invoke.getFirstArgument().getConstStringOrNull();
+    if (firstArg != null
+        && invoke.getSecondArgument().isConstInt()
+        && invoke.getThirdArgument().isConstInt()) {
+      DexString replacement =
+          fn.apply(
+              firstArg,
+              invoke.getSecondArgument().getConstInt(),
+              invoke.getThirdArgument().getConstInt());
+      replaceCurrentInstructionWithConstString(
+          code, instructionIterator, invoke, affectedValues, replacement);
+    }
+  }
+
+  default void optimizeStringStringToBooleanFunction(
+      IRCode code,
+      InstructionListIterator instructionIterator,
+      InvokeMethod invoke,
+      BiPredicate<DexString, DexString> fn) {
+    DexString firstArg = invoke.getFirstArgument().getConstStringOrNull();
+    DexString secondArg = invoke.getSecondArgument().getConstStringOrNull();
+    if (firstArg != null && secondArg != null) {
+      boolean replacement = fn.test(firstArg, secondArg);
+      instructionIterator.replaceCurrentInstructionWithConstBoolean(code, replacement);
+    }
+  }
+
+  interface StringStringToIntFunction {
+
+    int apply(DexString s, DexString t);
+  }
+
+  default void optimizeStringStringToIntFunction(
+      IRCode code,
+      InstructionListIterator instructionIterator,
+      InvokeMethod invoke,
+      StringStringToIntFunction fn) {
+    DexString firstArg = invoke.getFirstArgument().getConstStringOrNull();
+    DexString secondArg = invoke.getSecondArgument().getConstStringOrNull();
+    if (firstArg != null && secondArg != null) {
+      int replacement = fn.apply(firstArg, secondArg);
+      instructionIterator.replaceCurrentInstructionWithConstInt(code, replacement);
+    }
+  }
+
+  interface StringStringToStringFunction {
+
+    DexString apply(DexString s1, DexString s2);
+  }
+
+  default void optimizeStringStringToStringFunction(
+      IRCode code,
+      InstructionListIterator instructionIterator,
+      InvokeMethod invoke,
+      AffectedValues affectedValues,
+      StringStringToStringFunction fn) {
+    DexString firstArg = invoke.getFirstArgument().getConstStringOrNull();
+    DexString secondArg = invoke.getSecondArgument().getConstStringOrNull();
+    if (firstArg != null && secondArg != null) {
+      DexString replacement = fn.apply(firstArg, secondArg);
+      replaceCurrentInstructionWithConstString(
+          code, instructionIterator, invoke, affectedValues, replacement);
+    }
+  }
+
+  interface StringStringIntToIntFunction {
+
+    int apply(DexString s, DexString t, int i);
+  }
+
+  default void optimizeStringStringIntToIntFunction(
+      IRCode code,
+      InstructionListIterator instructionIterator,
+      InvokeMethod invoke,
+      StringStringIntToIntFunction fn) {
+    DexString firstArg = invoke.getFirstArgument().getConstStringOrNull();
+    DexString secondArg = invoke.getSecondArgument().getConstStringOrNull();
+    if (firstArg != null && secondArg != null && invoke.getThirdArgument().isConstInt()) {
+      int replacement = fn.apply(firstArg, secondArg, invoke.getThirdArgument().getConstInt());
+      instructionIterator.replaceCurrentInstructionWithConstInt(code, replacement);
+    }
+  }
+
+  private void replaceCurrentInstructionWithConstString(
+      IRCode code,
+      InstructionListIterator instructionIterator,
+      InvokeMethod invoke,
+      AffectedValues affectedValues,
+      DexString replacement) {
+    assert invoke.getFirstArgument().isConstString();
+    if (replacement == null) {
+      return;
+    }
+    if (replacement.isIdenticalTo(invoke.getFirstArgument().getConstStringOrNull())) {
+      if (invoke.hasOutValue()) {
+        invoke.outValue().replaceUsers(invoke.getFirstArgument(), affectedValues);
+        invoke
+            .getFirstArgument()
+            .uniquePhiUsers()
+            .forEach(phi -> phi.removeTrivialPhi(null, affectedValues));
+      }
+      instructionIterator.removeOrReplaceByDebugLocalRead();
+    } else {
+      instructionIterator.replaceCurrentInstructionWithConstString(
+          getAppView(), code, replacement, affectedValues);
+    }
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/library/StringMethodOptimizer.java b/src/main/java/com/android/tools/r8/ir/optimize/library/StringMethodOptimizer.java
index f810581..98de100 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/library/StringMethodOptimizer.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/library/StringMethodOptimizer.java
@@ -48,12 +48,11 @@
 import java.util.Collections;
 import java.util.List;
 import java.util.Set;
-import java.util.function.BiPredicate;
-import java.util.function.Predicate;
-import java.util.function.UnaryOperator;
 
-public class StringMethodOptimizer extends StatelessLibraryMethodModelCollection {
-  private static boolean DEBUG =
+public class StringMethodOptimizer extends StatelessLibraryMethodModelCollection
+    implements MethodOptimizerCapabilities {
+
+  private static final boolean DEBUG =
       System.getProperty("com.android.tools.r8.debug.StringMethodOptimizer") != null;
 
   private final AppView<?> appView;
@@ -96,6 +95,11 @@
   }
 
   @Override
+  public AppView<?> getAppView() {
+    return appView;
+  }
+
+  @Override
   public DexType getType() {
     return dexItemFactory.stringType;
   }
@@ -255,227 +259,6 @@
     return false;
   }
 
-  private void optimizeStringToBooleanFunction(
-      IRCode code,
-      InstructionListIterator instructionIterator,
-      InvokeMethod invoke,
-      Predicate<DexString> fn) {
-    DexString firstArg = invoke.getFirstArgument().getConstStringOrNull();
-    if (firstArg != null) {
-      boolean replacement = fn.test(firstArg);
-      instructionIterator.replaceCurrentInstructionWithConstBoolean(code, replacement);
-    }
-  }
-
-  private interface StringToIntFunction {
-
-    int apply(DexString s);
-  }
-
-  private void optimizeStringToIntFunction(
-      IRCode code,
-      InstructionListIterator instructionIterator,
-      InvokeMethod invoke,
-      StringToIntFunction fn) {
-    DexString firstArg = invoke.getFirstArgument().getConstStringOrNull();
-    if (firstArg != null) {
-      int replacement = fn.apply(firstArg);
-      instructionIterator.replaceCurrentInstructionWithConstInt(code, replacement);
-    }
-  }
-
-  private void optimizeStringToStringFunction(
-      IRCode code,
-      InstructionListIterator instructionIterator,
-      InvokeMethod invoke,
-      AffectedValues affectedValues,
-      UnaryOperator<DexString> fn) {
-    DexString firstArg = invoke.getFirstArgument().getConstStringOrNull();
-    if (firstArg != null) {
-      DexString replacement = fn.apply(firstArg);
-      replaceCurrentInstructionWithConstString(
-          code, instructionIterator, invoke, affectedValues, replacement);
-    }
-  }
-
-  private interface StringIntToIntFunction {
-
-    int apply(DexString s, int i);
-  }
-
-  private void optimizeStringIntToIntFunction(
-      IRCode code,
-      InstructionListIterator instructionIterator,
-      InvokeMethod invoke,
-      StringIntToIntFunction fn) {
-    DexString firstArg = invoke.getFirstArgument().getConstStringOrNull();
-    if (firstArg != null && invoke.getSecondArgument().isConstInt()) {
-      int replacement = fn.apply(firstArg, invoke.getSecondArgument().getConstInt());
-      instructionIterator.replaceCurrentInstructionWithConstInt(code, replacement);
-    }
-  }
-
-  private interface StringIntToStringFunction {
-
-    DexString apply(DexString s, int i);
-  }
-
-  private void optimizeStringIntToStringFunction(
-      IRCode code,
-      InstructionListIterator instructionIterator,
-      InvokeMethod invoke,
-      AffectedValues affectedValues,
-      StringIntToStringFunction fn) {
-    DexString firstArg = invoke.getFirstArgument().getConstStringOrNull();
-    if (firstArg != null && invoke.getSecondArgument().isConstInt()) {
-      DexString replacement = fn.apply(firstArg, invoke.getSecondArgument().getConstInt());
-      replaceCurrentInstructionWithConstString(
-          code, instructionIterator, invoke, affectedValues, replacement);
-    }
-  }
-
-  private interface StringIntIntToIntFunction {
-
-    int apply(DexString s, int i, int j);
-  }
-
-  private void optimizeStringIntIntToIntFunction(
-      IRCode code,
-      InstructionListIterator instructionIterator,
-      InvokeMethod invoke,
-      StringIntIntToIntFunction fn) {
-    DexString firstArg = invoke.getFirstArgument().getConstStringOrNull();
-    if (firstArg != null
-        && invoke.getSecondArgument().isConstInt()
-        && invoke.getThirdArgument().isConstInt()) {
-      int replacement =
-          fn.apply(
-              firstArg,
-              invoke.getSecondArgument().getConstInt(),
-              invoke.getThirdArgument().getConstInt());
-      instructionIterator.replaceCurrentInstructionWithConstInt(code, replacement);
-    }
-  }
-
-  private interface StringIntIntToStringFunction {
-
-    DexString apply(DexString s, int i, int j);
-  }
-
-  private void optimizeStringIntIntToStringFunction(
-      IRCode code,
-      InstructionListIterator instructionIterator,
-      InvokeMethod invoke,
-      AffectedValues affectedValues,
-      StringIntIntToStringFunction fn) {
-    DexString firstArg = invoke.getFirstArgument().getConstStringOrNull();
-    if (firstArg != null
-        && invoke.getSecondArgument().isConstInt()
-        && invoke.getThirdArgument().isConstInt()) {
-      DexString replacement =
-          fn.apply(
-              firstArg,
-              invoke.getSecondArgument().getConstInt(),
-              invoke.getThirdArgument().getConstInt());
-      replaceCurrentInstructionWithConstString(
-          code, instructionIterator, invoke, affectedValues, replacement);
-    }
-  }
-
-  private void optimizeStringStringToBooleanFunction(
-      IRCode code,
-      InstructionListIterator instructionIterator,
-      InvokeMethod invoke,
-      BiPredicate<DexString, DexString> fn) {
-    DexString firstArg = invoke.getFirstArgument().getConstStringOrNull();
-    DexString secondArg = invoke.getSecondArgument().getConstStringOrNull();
-    if (firstArg != null && secondArg != null) {
-      boolean replacement = fn.test(firstArg, secondArg);
-      instructionIterator.replaceCurrentInstructionWithConstBoolean(code, replacement);
-    }
-  }
-
-  private interface StringStringToIntFunction {
-
-    int apply(DexString s, DexString t);
-  }
-
-  private void optimizeStringStringToIntFunction(
-      IRCode code,
-      InstructionListIterator instructionIterator,
-      InvokeMethod invoke,
-      StringStringToIntFunction fn) {
-    DexString firstArg = invoke.getFirstArgument().getConstStringOrNull();
-    DexString secondArg = invoke.getSecondArgument().getConstStringOrNull();
-    if (firstArg != null && secondArg != null) {
-      int replacement = fn.apply(firstArg, secondArg);
-      instructionIterator.replaceCurrentInstructionWithConstInt(code, replacement);
-    }
-  }
-
-  private interface StringStringToStringFunction {
-
-    DexString apply(DexString s1, DexString s2);
-  }
-
-  private void optimizeStringStringToStringFunction(
-      IRCode code,
-      InstructionListIterator instructionIterator,
-      InvokeMethod invoke,
-      AffectedValues affectedValues,
-      StringStringToStringFunction fn) {
-    DexString firstArg = invoke.getFirstArgument().getConstStringOrNull();
-    DexString secondArg = invoke.getSecondArgument().getConstStringOrNull();
-    if (firstArg != null && secondArg != null) {
-      DexString replacement = fn.apply(firstArg, secondArg);
-      replaceCurrentInstructionWithConstString(
-          code, instructionIterator, invoke, affectedValues, replacement);
-    }
-  }
-
-  private interface StringStringIntToIntFunction {
-
-    int apply(DexString s, DexString t, int i);
-  }
-
-  private void optimizeStringStringIntToIntFunction(
-      IRCode code,
-      InstructionListIterator instructionIterator,
-      InvokeMethod invoke,
-      StringStringIntToIntFunction fn) {
-    DexString firstArg = invoke.getFirstArgument().getConstStringOrNull();
-    DexString secondArg = invoke.getSecondArgument().getConstStringOrNull();
-    if (firstArg != null && secondArg != null && invoke.getThirdArgument().isConstInt()) {
-      int replacement = fn.apply(firstArg, secondArg, invoke.getThirdArgument().getConstInt());
-      instructionIterator.replaceCurrentInstructionWithConstInt(code, replacement);
-    }
-  }
-
-  private void replaceCurrentInstructionWithConstString(
-      IRCode code,
-      InstructionListIterator instructionIterator,
-      InvokeMethod invoke,
-      AffectedValues affectedValues,
-      DexString replacement) {
-    assert invoke.getFirstArgument().isConstString();
-    if (replacement == null) {
-      return;
-    }
-    if (replacement.isIdenticalTo(invoke.getFirstArgument().getConstStringOrNull())) {
-      if (invoke.hasOutValue()) {
-        invoke.outValue().replaceUsers(invoke.getFirstArgument(), affectedValues);
-        invoke
-            .getFirstArgument()
-            .uniquePhiUsers()
-            .forEach(phi -> phi.removeTrivialPhi(null, affectedValues));
-      }
-      instructionIterator.removeOrReplaceByDebugLocalRead();
-    } else {
-      instructionIterator.replaceCurrentInstructionWithConstString(
-          appView, code, replacement, affectedValues);
-    }
-  }
-
   private static class SimpleStringFormatSpec {
     private static class Part {
       final String value;