-
Notifications
You must be signed in to change notification settings - Fork 36
Java Pattern Refactor
Let's say you have a constructor and you want to replace it with a builder pattern.
Before | After |
---|---|
import org.alfasoftware.astra.core.refactoring.methods.constructortobuilder.builder.BuiltType; public class Example { BuiltType builtType = new BuiltType("", Integer.valueOf(1), Integer.valueOf(2)); } |
import org.alfasoftware.astra.core.refactoring.methods.constructortobuilder.builder.BuiltType; public class ExampleAfter { BuiltType builtType = BuiltType.builderForKey("").withValues(Integer.valueOf(1), Integer.valueOf(2)).build(); |
We can achieve this using a JavaPatternASTOperation refactor with a Pattern file that looks like this:
import org.alfasoftware.astra.core.refactoring.methods.constructortobuilder.builder.BuiltType;
import org.alfasoftware.astra.core.refactoring.operations.javapattern.JavaPattern;
import org.alfasoftware.astra.core.refactoring.operations.javapattern.JavaPatternReplacement;
class ConstructorToBuilderInnerClassBuilderExampleMatcherWithVarargs {
@JavaPattern
void pattern(String parameter1, Object[] parameterArray){
new BuiltType(parameter1, parameterArray);
}
@JavaPatternReplacement
void patternReplacement(String parameter1, Object[] parameterArray){
BuiltType.builderForKey(parameter1).withValues(parameterArray).build();
}
}
There's a few things going on here which need some explanation. At a high level, the JavaPatternASTOperation requires that there is at least one method annotated with @JavaPattern in the Pattern class and exactly one @JavaPatternReplacement annotated method. The ASTOperation will search the code base for code which matches any of the patterns defined by @JavaPattern method and will replace them with the code in the @JavaPatternReplacement method.
Let's take a closer look at the @JavaPattern annotated method.
@JavaPattern
void pattern(String parameter1, // A value to capture. Requires that a matched type is compatible with String
Object[] parameterArray // Capture parameters to a varargs method
){
new BuiltType(parameter1, parameterArray); // the pattern in Java we are trying to match
}
Parameters in a @JavaPattern annotated method define information which we are trying to capture. In this constructor example, we are looking for any invocations of the BuiltType constructor which takes a String and an Object array as parameters. We don't want to say what the parameters are, rather we want to capture the parameters used in each specific invocation.
For each match we find we will have captured information about the actual values for paramter1 and parameterArray.
Now let's take a look at how the replacement works.
@JavaPatternReplacement
void patternReplacement(String parameter1, // the parameter found by the JavaPattern
Object[] parameterArray // the parameters found by the JavaPattern
){
BuiltType.builderForKey(parameter1).withValues(parameterArray).build(); // The pattern to replace the found match with
}
Parameters in the JavaPatternReplacement refer back to parameters in the JavaPatterns. The JavaPatternASTOperation works by replacing the parameters in the JavaPatternReplacement with the captured values from the match, and then replacing the whole match with the replacement pattern.
The JavaPatternASTOperation supports substitute methods as well as the parameters seen in the example above. A Substitute method is used to capture a method invocation where we aren't interested in the specific method used.
An example use of a Substitute method is to capture methods invoked with the result passed as an argument to a method. For example something like this
stringListHashMap.put("a key", Collections.singletonList("some value"));
Let's say we want to swap the order of the key and parameter, the Pattern file for this would look like the following
/**
* Capture some method taking a String and returning a List<String>
*/
@Substitute
abstract List<String> someMethod(String string);
@JavaPattern
void patternWithParameters(String key, Map<String, List<String>> map, String parameter){
map.put(key, someMethod(parameter));
}
@JavaPatternReplacement
void patternReplacement(String key, Map<String, List<String>> map, String parameter){
map.put(parameter, someMethod(key));
}
Here, we capture the actual method invocation Collections.singletonList() and the parameter "some value". In our JavaPatternReplacement we can then easily change the argument to whatever we want, including other values capture by the JavaPattern, in this case the key "a key".