Method Patterns
A simple and powerful way of identifying method definitions and invocations
Many recipes operate on method declarations or invocations. Method patterns are a mechanism for configuring and developing such recipes. OpenRewrite's pointcut method expressions are derived from AspectJ. An OpenRewrite "method pattern" is comparable to an AspectJ's "Pointcut method expression".

Anatomy of a Method Pattern

A pointcut method expression can identify one or more method definitions or invocations based on their:
    Fully qualified receiver type
    Method Name
    Method Argument types
1
com.yourorg.RecieverFullyQualifiedType methodName(argType1, argType2)
Copied!
There are two kinds of wildcard symbols:
    * - Matches any one thing. Applicable to receiver type, method name, and arguments.
    .. - Matches zero or more. Applicable to receiver type and arguments.
The return type of a method is not represented in method patterns. Methods in Java (and similar languages) can be uniquely identified by receiver type, name, and argument types alone.

Examples

So if you wanted to match invocations of String.toString(int beginIndex) then this expression would match only that method: java.lang.String toString(int). If you wanted to match the two-argument form of the same
Method Pattern
Matches
java.lang.String substring(int)
Exactly the single argument overload of String.substring()
java.lang.String substring(int, int)
Exactly the two argument overload of String.substring()
java.lang.String substring(..)
Any overload of String.substring()
java.lang.String *(int)
Any method on String that accepts a single argument of type int
com.yourorg.Foo bar(int, String, ..)
Any method on Foo named bar accepting an int, a String, and zero or more other arguments of any type
com.yourorg.Foo Foo(*, *, *)
Constructors of Foo accepting exactly three arguments of any type.
*..String *(..)
Any method accepting any arguments on classes named "String" in any package
*..* *(..)
Any method accepting any arguments on any class
org.example..* *(..)
Any method on any class in the "org.example" package, or any sub-package of "org.example"

Usage

Configuring Recipes

Recipes which take method patterns as arguments take them as strings. In yaml that looks like this:
1
---
2
type: specs.openrewrite.org/v1beta/recipe
3
name: com.yourorg.ChangeMethodNameExample
4
displayName: Change method name example
5
recipeList:
6
- org.openrewrite.java.ChangeMethodName:
7
methodPattern: org.mockito.Matchers anyVararg()
8
newMethodName: any
Copied!
Constructing a similarly configured instance of the same recipe in Java:
1
ChangeMethodName cmn = new ChangeMethodName("org.mockito.Matchers anyVararg()", "any");
Copied!

Authoring Recipes with MethodMatcher

org.openrewrite.java.MethodMatcher is the class that most recipes will use method patterns with. Instances are created by providing the method pattern to its constructor:
1
MethodMatcher mm = new MethodMatcher("org.mockito.Matchers anyVararg()");
Copied!
MethodMatcher.matches() has overloads that accept method declarations, method invocations, and constrcutor invocations. matches() returns true if the argument matches the method pattern the matcher was initialized with. They are frequently used by visitors to avoid making changes to AST elements other than those indicated by the method pattern.
1
// Adapted from org.openrewrite.java.ChangeMethodName for the sake of example
2
// This is lacks the full functionality of the complete Recipe
3
class ChangeMethodNameVisitor extends JavaIsoVisitor<ExecutionContext> {
4
private final MethodMatcher methodMatcher;
5
6
private ChangeMethodNameVisitor(String pointcutExpression) {
7
this.methodMatcher = new MethodMatcher(pointcutExpression);
8
}
9
10
@Override
11
public J.MethodDeclaration visitMethodDeclaration(J.MethodDeclaration method, ExecutionContext ctx) {
12
J.MethodDeclaration m = super.visitMethodDeclaration(method, ctx);
13
J.ClassDeclaration classDecl = getCursor().firstEnclosingOrThrow(J.ClassDeclaration.class);
14
// The enclosing class of a J.MethodDeclaration must be known for a MethodMatcher to match it
15
if (methodMatcher.matches(method, classDecl)) {
16
JavaType.Method type = m.getType();
17
// Note that both the name and the type information on the declaration are updated together
18
// Maintaining this consistency is important for maintaining the correct operation of other recipes
19
if(type != null) {
20
type = type.withName(newMethodName);
21
}
22
m = m.withName(m.getName().withName(newMethodName))
23
.withType(type);
24
}
25
return m;
26
}
27
28
@Override
29
public J.MethodInvocation visitMethodInvocation(J.MethodInvocation method, ExecutionContext ctx) {
30
J.MethodInvocation m = super.visitMethodInvocation(method, ctx);
31
// Type information stored in the J.MethodInvocation indicates the class so no second argument is necessary
32
if (methodMatcher.matches(method)) {
33
JavaType.Method type = m.getType();
34
// Note that both the name and the type information on the invocation are updated together
35
// Maintaining this consistency is important for maintaining the correct operation of other recipes
36
if(type != null) {
37
type = type.withName(newMethodName);
38
}
39
m = m.withName(m.getName().withName(newMethodName))
40
.withType(type);
41
}
42
return m;
43
}
44
45
// Other implementation follows
46
}
Copied!
Last modified 1mo ago