[KeepAnno] Support string patterns for field names

This CL also simplifies the method variant to not use an inner subclass.

Bug: b/248408342
Change-Id: I58723cebb83548144ef293e6751a3f38ad8c79e5
diff --git a/src/keepanno/java/com/android/tools/r8/keepanno/annotations/KeepBinding.java b/src/keepanno/java/com/android/tools/r8/keepanno/annotations/KeepBinding.java
index b98f4ab..177990e 100644
--- a/src/keepanno/java/com/android/tools/r8/keepanno/annotations/KeepBinding.java
+++ b/src/keepanno/java/com/android/tools/r8/keepanno/annotations/KeepBinding.java
@@ -431,11 +431,27 @@
    * <p>If none, and other properties define this item as a field, the default matches any field
    * name.
    *
+   * <p>Mutually exclusive with the property `fieldNamePattern` also defining field-name.
+   *
    * @return The exact field name of the field.
    */
   String fieldName() default "";
 
   /**
+   * Define the field-name pattern by a string pattern.
+   *
+   * <p>Mutually exclusive with all method properties.
+   *
+   * <p>If none, and other properties define this item as a field, the default matches any field
+   * name.
+   *
+   * <p>Mutually exclusive with the property `fieldName` also defining field-name.
+   *
+   * @return The string pattern of the field name.
+   */
+  StringPattern fieldNamePattern() default @StringPattern(exact = "");
+
+  /**
    * Define the field-type pattern by a fully qualified type.
    *
    * <p>Mutually exclusive with all method properties.
diff --git a/src/keepanno/java/com/android/tools/r8/keepanno/annotations/KeepCondition.java b/src/keepanno/java/com/android/tools/r8/keepanno/annotations/KeepCondition.java
index bab5ca7..a35d08b 100644
--- a/src/keepanno/java/com/android/tools/r8/keepanno/annotations/KeepCondition.java
+++ b/src/keepanno/java/com/android/tools/r8/keepanno/annotations/KeepCondition.java
@@ -432,13 +432,37 @@
    * <p>If none, and other properties define this item as a field, the default matches any field
    * name.
    *
-   * <p>Mutually exclusive with the property `memberFromBinding` also defining field-name.
+   * <p>Mutually exclusive with the following other properties defining field-name:
+   *
+   * <ul>
+   *   <li>fieldNamePattern
+   *   <li>memberFromBinding
+   * </ul>
    *
    * @return The exact field name of the field.
    */
   String fieldName() default "";
 
   /**
+   * Define the field-name pattern by a string pattern.
+   *
+   * <p>Mutually exclusive with all method properties.
+   *
+   * <p>If none, and other properties define this item as a field, the default matches any field
+   * name.
+   *
+   * <p>Mutually exclusive with the following other properties defining field-name:
+   *
+   * <ul>
+   *   <li>fieldName
+   *   <li>memberFromBinding
+   * </ul>
+   *
+   * @return The string pattern of the field name.
+   */
+  StringPattern fieldNamePattern() default @StringPattern(exact = "");
+
+  /**
    * Define the field-type pattern by a fully qualified type.
    *
    * <p>Mutually exclusive with all method properties.
diff --git a/src/keepanno/java/com/android/tools/r8/keepanno/annotations/KeepForApi.java b/src/keepanno/java/com/android/tools/r8/keepanno/annotations/KeepForApi.java
index 9d097f0..4b56b2e 100644
--- a/src/keepanno/java/com/android/tools/r8/keepanno/annotations/KeepForApi.java
+++ b/src/keepanno/java/com/android/tools/r8/keepanno/annotations/KeepForApi.java
@@ -210,11 +210,27 @@
    * <p>If none, and other properties define this item as a field, the default matches any field
    * name.
    *
+   * <p>Mutually exclusive with the property `fieldNamePattern` also defining field-name.
+   *
    * @return The exact field name of the field.
    */
   String fieldName() default "";
 
   /**
+   * Define the field-name pattern by a string pattern.
+   *
+   * <p>Mutually exclusive with all method properties.
+   *
+   * <p>If none, and other properties define this item as a field, the default matches any field
+   * name.
+   *
+   * <p>Mutually exclusive with the property `fieldName` also defining field-name.
+   *
+   * @return The string pattern of the field name.
+   */
+  StringPattern fieldNamePattern() default @StringPattern(exact = "");
+
+  /**
    * Define the field-type pattern by a fully qualified type.
    *
    * <p>Mutually exclusive with all method properties.
diff --git a/src/keepanno/java/com/android/tools/r8/keepanno/annotations/KeepTarget.java b/src/keepanno/java/com/android/tools/r8/keepanno/annotations/KeepTarget.java
index d601a6a..e2ebc3b 100644
--- a/src/keepanno/java/com/android/tools/r8/keepanno/annotations/KeepTarget.java
+++ b/src/keepanno/java/com/android/tools/r8/keepanno/annotations/KeepTarget.java
@@ -529,13 +529,37 @@
    * <p>If none, and other properties define this item as a field, the default matches any field
    * name.
    *
-   * <p>Mutually exclusive with the property `memberFromBinding` also defining field-name.
+   * <p>Mutually exclusive with the following other properties defining field-name:
+   *
+   * <ul>
+   *   <li>fieldNamePattern
+   *   <li>memberFromBinding
+   * </ul>
    *
    * @return The exact field name of the field.
    */
   String fieldName() default "";
 
   /**
+   * Define the field-name pattern by a string pattern.
+   *
+   * <p>Mutually exclusive with all method properties.
+   *
+   * <p>If none, and other properties define this item as a field, the default matches any field
+   * name.
+   *
+   * <p>Mutually exclusive with the following other properties defining field-name:
+   *
+   * <ul>
+   *   <li>fieldName
+   *   <li>memberFromBinding
+   * </ul>
+   *
+   * @return The string pattern of the field name.
+   */
+  StringPattern fieldNamePattern() default @StringPattern(exact = "");
+
+  /**
    * Define the field-type pattern by a fully qualified type.
    *
    * <p>Mutually exclusive with all method properties.
diff --git a/src/keepanno/java/com/android/tools/r8/keepanno/annotations/UsedByNative.java b/src/keepanno/java/com/android/tools/r8/keepanno/annotations/UsedByNative.java
index 229d0f1..f025c0e 100644
--- a/src/keepanno/java/com/android/tools/r8/keepanno/annotations/UsedByNative.java
+++ b/src/keepanno/java/com/android/tools/r8/keepanno/annotations/UsedByNative.java
@@ -253,11 +253,27 @@
    * <p>If none, and other properties define this item as a field, the default matches any field
    * name.
    *
+   * <p>Mutually exclusive with the property `fieldNamePattern` also defining field-name.
+   *
    * @return The exact field name of the field.
    */
   String fieldName() default "";
 
   /**
+   * Define the field-name pattern by a string pattern.
+   *
+   * <p>Mutually exclusive with all method properties.
+   *
+   * <p>If none, and other properties define this item as a field, the default matches any field
+   * name.
+   *
+   * <p>Mutually exclusive with the property `fieldName` also defining field-name.
+   *
+   * @return The string pattern of the field name.
+   */
+  StringPattern fieldNamePattern() default @StringPattern(exact = "");
+
+  /**
    * Define the field-type pattern by a fully qualified type.
    *
    * <p>Mutually exclusive with all method properties.
diff --git a/src/keepanno/java/com/android/tools/r8/keepanno/annotations/UsedByReflection.java b/src/keepanno/java/com/android/tools/r8/keepanno/annotations/UsedByReflection.java
index 7b8fa8e..8076e51 100644
--- a/src/keepanno/java/com/android/tools/r8/keepanno/annotations/UsedByReflection.java
+++ b/src/keepanno/java/com/android/tools/r8/keepanno/annotations/UsedByReflection.java
@@ -253,11 +253,27 @@
    * <p>If none, and other properties define this item as a field, the default matches any field
    * name.
    *
+   * <p>Mutually exclusive with the property `fieldNamePattern` also defining field-name.
+   *
    * @return The exact field name of the field.
    */
   String fieldName() default "";
 
   /**
+   * Define the field-name pattern by a string pattern.
+   *
+   * <p>Mutually exclusive with all method properties.
+   *
+   * <p>If none, and other properties define this item as a field, the default matches any field
+   * name.
+   *
+   * <p>Mutually exclusive with the property `fieldName` also defining field-name.
+   *
+   * @return The string pattern of the field name.
+   */
+  StringPattern fieldNamePattern() default @StringPattern(exact = "");
+
+  /**
    * Define the field-type pattern by a fully qualified type.
    *
    * <p>Mutually exclusive with all method properties.
diff --git a/src/keepanno/java/com/android/tools/r8/keepanno/asm/KeepEdgeReader.java b/src/keepanno/java/com/android/tools/r8/keepanno/asm/KeepEdgeReader.java
index 7f03de2..d4b3f1d 100644
--- a/src/keepanno/java/com/android/tools/r8/keepanno/asm/KeepEdgeReader.java
+++ b/src/keepanno/java/com/android/tools/r8/keepanno/asm/KeepEdgeReader.java
@@ -1567,6 +1567,7 @@
   private static class FieldDeclaration extends Declaration<KeepFieldPattern> {
 
     private final ParsingContext parsingContext;
+    private final StringPatternParser nameParser;
     private final FieldTypeParser typeParser;
     private KeepFieldAccessPattern.Builder accessBuilder = null;
     private KeepFieldPattern.Builder builder = null;
@@ -1574,12 +1575,16 @@
 
     public FieldDeclaration(ParsingContext parsingContext) {
       this.parsingContext = parsingContext;
+      nameParser = new StringPatternParser(parsingContext.group(Item.fieldNameGroup));
+      nameParser.setProperty(Item.fieldName, StringProperty.EXACT);
+      nameParser.setProperty(Item.fieldNamePattern, StringProperty.PATTERN);
+
       typeParser = new FieldTypeParser(parsingContext.group(Item.fieldTypeGroup));
       typeParser.setProperty(Item.fieldTypePattern, TypeProperty.TYPE_PATTERN);
       typeParser.setProperty(Item.fieldType, TypeProperty.TYPE_NAME);
       typeParser.setProperty(Item.fieldTypeConstant, TypeProperty.TYPE_CONSTANT);
 
-      parsers = Collections.singletonList(typeParser);
+      parsers = ImmutableList.of(nameParser, typeParser);
     }
 
     @Override
@@ -1603,6 +1608,9 @@
       if (accessBuilder != null) {
         getBuilder().setAccessPattern(accessBuilder.build());
       }
+      if (!nameParser.isDefault()) {
+        getBuilder().setNamePattern(KeepFieldNamePattern.fromStringPattern(nameParser.getValue()));
+      }
       if (!typeParser.isDefault()) {
         getBuilder().setTypePattern(typeParser.getValue());
       }
@@ -1610,15 +1618,6 @@
     }
 
     @Override
-    boolean tryParse(String name, Object value) {
-      if (name.equals(Item.fieldName) && value instanceof String) {
-        getBuilder().setNamePattern(KeepFieldNamePattern.exact((String) value));
-        return true;
-      }
-      return super.tryParse(name, value);
-    }
-
-    @Override
     AnnotationVisitor tryParseArray(String name) {
       if (name.equals(Item.fieldAccess)) {
         accessBuilder = KeepFieldAccessPattern.builder();
diff --git a/src/keepanno/java/com/android/tools/r8/keepanno/asm/KeepEdgeWriter.java b/src/keepanno/java/com/android/tools/r8/keepanno/asm/KeepEdgeWriter.java
index c714960..332e101 100644
--- a/src/keepanno/java/com/android/tools/r8/keepanno/asm/KeepEdgeWriter.java
+++ b/src/keepanno/java/com/android/tools/r8/keepanno/asm/KeepEdgeWriter.java
@@ -12,7 +12,6 @@
 import com.android.tools.r8.keepanno.ast.KeepConsequences;
 import com.android.tools.r8.keepanno.ast.KeepEdge;
 import com.android.tools.r8.keepanno.ast.KeepEdgeException;
-import com.android.tools.r8.keepanno.ast.KeepFieldNamePattern.KeepFieldNameExactPattern;
 import com.android.tools.r8.keepanno.ast.KeepFieldPattern;
 import com.android.tools.r8.keepanno.ast.KeepItemPattern;
 import com.android.tools.r8.keepanno.ast.KeepMemberItemPattern;
@@ -187,9 +186,9 @@
   }
 
   private void writeField(KeepFieldPattern field, AnnotationVisitor targetVisitor) {
-    KeepFieldNameExactPattern exactFieldName = field.getNamePattern().asExact();
+    String exactFieldName = field.getNamePattern().asExactString();
     if (exactFieldName != null) {
-      targetVisitor.visit(Item.fieldName, exactFieldName.getName());
+      targetVisitor.visit(Item.fieldName, exactFieldName);
     } else {
       throw new Unimplemented();
     }
diff --git a/src/keepanno/java/com/android/tools/r8/keepanno/ast/AnnotationConstants.java b/src/keepanno/java/com/android/tools/r8/keepanno/ast/AnnotationConstants.java
index c489701..f0bdccf 100644
--- a/src/keepanno/java/com/android/tools/r8/keepanno/ast/AnnotationConstants.java
+++ b/src/keepanno/java/com/android/tools/r8/keepanno/ast/AnnotationConstants.java
@@ -94,7 +94,9 @@
     public static final String methodParameters = "methodParameters";
     public static final String methodParameterTypePatterns = "methodParameterTypePatterns";
     public static final String fieldAccess = "fieldAccess";
+    public static final String fieldNameGroup = "field-name";
     public static final String fieldName = "fieldName";
+    public static final String fieldNamePattern = "fieldNamePattern";
     public static final String fieldTypeGroup = "field-type";
     public static final String fieldType = "fieldType";
     public static final String fieldTypeConstant = "fieldTypeConstant";
diff --git a/src/keepanno/java/com/android/tools/r8/keepanno/ast/KeepFieldNamePattern.java b/src/keepanno/java/com/android/tools/r8/keepanno/ast/KeepFieldNamePattern.java
index 0945764..5223e60 100644
--- a/src/keepanno/java/com/android/tools/r8/keepanno/ast/KeepFieldNamePattern.java
+++ b/src/keepanno/java/com/android/tools/r8/keepanno/ast/KeepFieldNamePattern.java
@@ -3,96 +3,44 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.keepanno.ast;
 
-public abstract class KeepFieldNamePattern {
+public class KeepFieldNamePattern {
+
+  private static final KeepFieldNamePattern ANY = new KeepFieldNamePattern(KeepStringPattern.any());
 
   public static KeepFieldNamePattern any() {
-    return Any.getInstance();
+    return ANY;
   }
 
   public static KeepFieldNamePattern exact(String methodName) {
-    return new KeepFieldNameExactPattern(methodName);
+    return fromStringPattern(KeepStringPattern.exact(methodName));
   }
 
-  private KeepFieldNamePattern() {}
+  public static KeepFieldNamePattern fromStringPattern(KeepStringPattern pattern) {
+    if (pattern.isAny()) {
+      return ANY;
+    }
+    return new KeepFieldNamePattern(pattern);
+  }
+
+  private final KeepStringPattern pattern;
+
+  private KeepFieldNamePattern(KeepStringPattern pattern) {
+    this.pattern = pattern;
+  }
+
+  public KeepStringPattern asStringPattern() {
+    return pattern;
+  }
 
   public boolean isAny() {
-    return false;
+    return ANY == this;
   }
 
   public final boolean isExact() {
-    return asExact() != null;
+    return pattern.isExact();
   }
 
-  public KeepFieldNameExactPattern asExact() {
-    return null;
-  }
-
-  private static class Any extends KeepFieldNamePattern {
-    private static final Any INSTANCE = new Any();
-
-    public static Any getInstance() {
-      return INSTANCE;
-    }
-
-    @Override
-    public boolean isAny() {
-      return true;
-    }
-
-    @Override
-    public boolean equals(Object obj) {
-      return this == obj;
-    }
-
-    @Override
-    public int hashCode() {
-      return System.identityHashCode(this);
-    }
-
-    @Override
-    public String toString() {
-      return "*";
-    }
-  }
-
-  public static class KeepFieldNameExactPattern extends KeepFieldNamePattern {
-    private final String name;
-
-    public KeepFieldNameExactPattern(String name) {
-      assert name != null;
-      this.name = name;
-    }
-
-    @Override
-    public KeepFieldNameExactPattern asExact() {
-      return this;
-    }
-
-    public String getName() {
-      return name;
-    }
-
-    @Override
-    @SuppressWarnings("EqualsGetClass")
-    public boolean equals(Object o) {
-      if (this == o) {
-        return true;
-      }
-      if (o == null || getClass() != o.getClass()) {
-        return false;
-      }
-      KeepFieldNameExactPattern that = (KeepFieldNameExactPattern) o;
-      return name.equals(that.name);
-    }
-
-    @Override
-    public int hashCode() {
-      return name.hashCode();
-    }
-
-    @Override
-    public String toString() {
-      return name;
-    }
+  public String asExactString() {
+    return pattern.asExactString();
   }
 }
diff --git a/src/keepanno/java/com/android/tools/r8/keepanno/ast/KeepMethodNamePattern.java b/src/keepanno/java/com/android/tools/r8/keepanno/ast/KeepMethodNamePattern.java
index 6843e61..b79b616 100644
--- a/src/keepanno/java/com/android/tools/r8/keepanno/ast/KeepMethodNamePattern.java
+++ b/src/keepanno/java/com/android/tools/r8/keepanno/ast/KeepMethodNamePattern.java
@@ -3,17 +3,27 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.keepanno.ast;
 
+public class KeepMethodNamePattern {
 
-public abstract class KeepMethodNamePattern {
   private static final String INIT_STRING = "<init>";
   private static final String CLINIT_STRING = "<clinit>";
+  private static final KeepMethodNamePattern ANY =
+      new KeepMethodNamePattern(KeepStringPattern.any());
+  private static final KeepMethodNamePattern INSTANCE_INIT =
+      new KeepMethodNamePattern(KeepStringPattern.exact(INIT_STRING));
+  private static final KeepMethodNamePattern CLASS_INIT =
+      new KeepMethodNamePattern(KeepStringPattern.exact(CLINIT_STRING));
 
   public static KeepMethodNamePattern any() {
-    return SomePattern.ANY;
+    return KeepMethodNamePattern.ANY;
   }
 
-  public static KeepMethodNamePattern initializer() {
-    return SomePattern.INSTANCE_INIT;
+  public static KeepMethodNamePattern instanceInitializer() {
+    return KeepMethodNamePattern.INSTANCE_INIT;
+  }
+
+  public static KeepMethodNamePattern classInitializer() {
+    return KeepMethodNamePattern.CLASS_INIT;
   }
 
   public static KeepMethodNamePattern exact(String methodName) {
@@ -22,98 +32,70 @@
 
   public static KeepMethodNamePattern fromStringPattern(KeepStringPattern pattern) {
     if (pattern.isAny()) {
-      return SomePattern.ANY;
+      return KeepMethodNamePattern.ANY;
     }
     if (pattern.isExact()) {
       String exact = pattern.asExactString();
       if (INIT_STRING.equals(exact)) {
-        return SomePattern.INSTANCE_INIT;
+        return KeepMethodNamePattern.INSTANCE_INIT;
       }
       if (CLINIT_STRING.equals(exact)) {
-        return SomePattern.CLASS_INIT;
+        return KeepMethodNamePattern.CLASS_INIT;
       }
     }
-    return new SomePattern(pattern);
+    return new KeepMethodNamePattern(pattern);
   }
 
-  private KeepMethodNamePattern() {}
+  private final KeepStringPattern pattern;
 
-  public abstract boolean isAny();
+  private KeepMethodNamePattern(KeepStringPattern pattern) {
+    assert pattern != null;
+    this.pattern = pattern;
+  }
 
-  public abstract boolean isInstanceInitializer();
+  public KeepStringPattern asStringPattern() {
+    return pattern;
+  }
 
-  public abstract boolean isClassInitializer();
-
-  public abstract boolean isExact();
-
-  public abstract String asExactString();
-
-  public abstract KeepStringPattern asStringPattern();
-
-  private static class SomePattern extends KeepMethodNamePattern {
-    private static final SomePattern ANY = new SomePattern(KeepStringPattern.any());
-    private static final KeepMethodNamePattern INSTANCE_INIT =
-        new SomePattern(KeepStringPattern.exact("<init>"));
-    private static final KeepMethodNamePattern CLASS_INIT =
-        new SomePattern(KeepStringPattern.exact("<clinit>"));
-
-    private final KeepStringPattern pattern;
-
-    public SomePattern(KeepStringPattern pattern) {
-      assert pattern != null;
-      this.pattern = pattern;
+  @Override
+  public boolean equals(Object o) {
+    if (this == o) {
+      return true;
     }
-
-    @Override
-    public KeepStringPattern asStringPattern() {
-      return pattern;
+    if (!(o instanceof KeepMethodNamePattern)) {
+      return false;
     }
+    KeepMethodNamePattern that = (KeepMethodNamePattern) o;
+    return pattern.equals(that.pattern);
+  }
 
-    @Override
-    public boolean equals(Object o) {
-      if (this == o) {
-        return true;
-      }
-      if (!(o instanceof SomePattern)) {
-        return false;
-      }
-      SomePattern that = (SomePattern) o;
-      return pattern.equals(that.pattern);
-    }
+  @Override
+  public int hashCode() {
+    return pattern.hashCode();
+  }
 
-    @Override
-    public int hashCode() {
-      return pattern.hashCode();
-    }
+  @Override
+  public String toString() {
+    return pattern.toString();
+  }
 
-    @Override
-    public String toString() {
-      return pattern.toString();
-    }
+  public boolean isAny() {
+    return ANY == this;
+  }
 
-    @Override
-    public boolean isAny() {
-      return ANY == this;
-    }
+  public boolean isClassInitializer() {
+    return CLASS_INIT == this;
+  }
 
-    @Override
-    public boolean isClassInitializer() {
-      return CLASS_INIT == this;
-    }
+  public boolean isInstanceInitializer() {
+    return INSTANCE_INIT == this;
+  }
 
-    @Override
-    public boolean isInstanceInitializer() {
-      return INSTANCE_INIT == this;
-    }
+  public boolean isExact() {
+    return pattern.isExact();
+  }
 
-    @Override
-    public boolean isExact() {
-      return pattern.isExact();
-    }
-
-    @Override
-    public String asExactString() {
-      return pattern.asExactString();
-    }
+  public String asExactString() {
+    return pattern.asExactString();
   }
 }
diff --git a/src/keepanno/java/com/android/tools/r8/keepanno/keeprules/RulePrintingUtils.java b/src/keepanno/java/com/android/tools/r8/keepanno/keeprules/RulePrintingUtils.java
index f16d9dd..7737744 100644
--- a/src/keepanno/java/com/android/tools/r8/keepanno/keeprules/RulePrintingUtils.java
+++ b/src/keepanno/java/com/android/tools/r8/keepanno/keeprules/RulePrintingUtils.java
@@ -176,9 +176,7 @@
   }
 
   private static RulePrinter printFieldName(RulePrinter builder, KeepFieldNamePattern namePattern) {
-    return namePattern.isAny()
-        ? builder.appendStar()
-        : builder.append(namePattern.asExact().getName());
+    return printStringPattern(builder, namePattern.asStringPattern());
   }
 
   private static RulePrinter printMethodName(
diff --git a/src/test/java/com/android/tools/r8/keepanno/FieldNameStringPatternsTest.java b/src/test/java/com/android/tools/r8/keepanno/FieldNameStringPatternsTest.java
new file mode 100644
index 0000000..520c65c
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/keepanno/FieldNameStringPatternsTest.java
@@ -0,0 +1,117 @@
+// Copyright (c) 2024, 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.keepanno;
+
+import static com.android.tools.r8.utils.codeinspector.Matchers.isAbsent;
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresentAndRenamed;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.keepanno.annotations.KeepItemKind;
+import com.android.tools.r8.keepanno.annotations.KeepTarget;
+import com.android.tools.r8.keepanno.annotations.StringPattern;
+import com.android.tools.r8.keepanno.annotations.UsedByReflection;
+import com.android.tools.r8.keepanno.annotations.UsesReflection;
+import com.android.tools.r8.utils.AndroidApiLevel;
+import com.android.tools.r8.utils.StringUtils;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.google.common.collect.ImmutableList;
+import java.lang.reflect.Field;
+import java.util.List;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class FieldNameStringPatternsTest extends TestBase {
+
+  static final String EXPECTED = StringUtils.lines("1", "2");
+
+  private final TestParameters parameters;
+
+  @Parameterized.Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withDefaultRuntimes().withApiLevel(AndroidApiLevel.B).build();
+  }
+
+  public FieldNameStringPatternsTest(TestParameters parameters) {
+    this.parameters = parameters;
+  }
+
+  @Test
+  public void testReference() throws Exception {
+    testForRuntime(parameters)
+        .addProgramClasses(getInputClasses())
+        .run(parameters.getRuntime(), TestClass.class)
+        .assertSuccessWithOutput(EXPECTED);
+  }
+
+  @Test
+  public void testR8() throws Exception {
+    testForR8(parameters.getBackend())
+        .enableExperimentalKeepAnnotations()
+        .addProgramClasses(getInputClasses())
+        .setMinApi(parameters)
+        .run(parameters.getRuntime(), TestClass.class)
+        .assertSuccessWithOutput(EXPECTED)
+        .inspect(this::checkOutput);
+  }
+
+  public List<Class<?>> getInputClasses() {
+    return ImmutableList.of(TestClass.class, A.class, B.class);
+  }
+
+  private void checkOutput(CodeInspector inspector) {
+    assertThat(inspector.clazz(B.class), isPresentAndRenamed());
+    assertThat(inspector.clazz(B.class).field("int", "bar"), isAbsent());
+    assertThat(inspector.clazz(B.class).field("int", "hiddenOneI"), isPresent());
+    assertThat(inspector.clazz(B.class).field("int", "hiddenTwoI"), isPresent());
+    assertThat(inspector.clazz(B.class).field("java.lang.String", "hiddenSomeS"), isAbsent());
+  }
+
+  static class A {
+
+    @UsesReflection(
+        @KeepTarget(
+            classConstant = B.class,
+            fieldNamePattern = @StringPattern(startsWith = "hidden", endsWith = "I")))
+    public void foo() throws Exception {
+      int counter = 1;
+      for (Field field : B.class.getDeclaredFields()) {
+        String name = field.getName();
+        if (name.startsWith("hidden")) {
+          if (name.endsWith("I")) {
+            field.set(null, counter++);
+          }
+        }
+      }
+      for (Field field : B.class.getDeclaredFields()) {
+        String name = field.getName();
+        if (name.startsWith("hidden")) {
+          if (name.endsWith("I")) {
+            System.out.println(field.get(null));
+          }
+        }
+      }
+    }
+  }
+
+  static class B {
+    static int hiddenOneI = 9;
+    static int hiddenTwoI = 18;
+    static String hiddenSomeS = "foo";
+    static int bar = 7;
+  }
+
+  static class TestClass {
+
+    @UsedByReflection(kind = KeepItemKind.CLASS_AND_METHODS)
+    public static void main(String[] args) throws Exception {
+      new A().foo();
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/keepanno/ast/KeepEdgeAstTest.java b/src/test/java/com/android/tools/r8/keepanno/ast/KeepEdgeAstTest.java
index 8a83cf9..dae2c6b 100644
--- a/src/test/java/com/android/tools/r8/keepanno/ast/KeepEdgeAstTest.java
+++ b/src/test/java/com/android/tools/r8/keepanno/ast/KeepEdgeAstTest.java
@@ -250,7 +250,7 @@
 
   private KeepMemberPattern defaultInitializerPattern() {
     return KeepMethodPattern.builder()
-        .setNamePattern(KeepMethodNamePattern.initializer())
+        .setNamePattern(KeepMethodNamePattern.instanceInitializer())
         .setParametersPattern(KeepMethodParametersPattern.none())
         .setReturnTypeVoid()
         .build();
diff --git a/src/test/java/com/android/tools/r8/keepanno/utils/KeepItemAnnotationGenerator.java b/src/test/java/com/android/tools/r8/keepanno/utils/KeepItemAnnotationGenerator.java
index 0ab89fa..61265cb 100644
--- a/src/test/java/com/android/tools/r8/keepanno/utils/KeepItemAnnotationGenerator.java
+++ b/src/test/java/com/android/tools/r8/keepanno/utils/KeepItemAnnotationGenerator.java
@@ -821,7 +821,14 @@
                   .addParagraph(getMutuallyExclusiveForFieldProperties())
                   .addParagraph(getFieldDefaultDoc("any field name"))
                   .setDocReturn("The exact field name of the field.")
-                  .defaultEmptyString());
+                  .defaultEmptyString())
+          .addMember(
+              new GroupMember("fieldNamePattern")
+                  .setDocTitle("Define the field-name pattern by a string pattern.")
+                  .addParagraph(getMutuallyExclusiveForFieldProperties())
+                  .addParagraph(getFieldDefaultDoc("any field name"))
+                  .setDocReturn("The string pattern of the field name.")
+                  .defaultValue(StringPattern.class, DEFAULT_INVALID_STRING_PATTERN));
     }
 
     private Group createFieldTypeGroup() {