7.4 KiB
7.4 KiB
OpenRewrite Recipe Common Patterns
Quick reference for frequently used code patterns in recipe development.
Import Management
Adding Imports
maybeAddImport("java.util.List");
maybeAddImport("java.util.Collections", "emptyList"); // Static import
Removing Imports
maybeRemoveImport("old.package.Type");
Visitor Patterns
Chaining Visitors
doAfterVisit(new OtherRecipe().getVisitor());
Cursor Messaging (Intra-visitor State)
// Store state
getCursor().putMessage("key", value);
// Retrieve state
Object value = getCursor().getNearestMessage("key");
Type Checking
Check Class Type
if (methodInvocation.getType() != null &&
TypeUtils.isOfClassType(methodInvocation.getType(), "com.example.Type")) {
// Type matches
}
Check Method Type
if (TypeUtils.isOfType(method.getMethodType(), "com.example.Type", "methodName")) {
// Method matches
}
Check Assignability
if (TypeUtils.isAssignableTo("java.util.Collection", someType)) {
// Type is assignable to Collection
}
LST Manipulation
Modifying Lists (Never Mutate!)
// WRONG - Mutates the list
method.getArguments().remove(0);
// CORRECT - Creates new list with ListUtils
method.withArguments(ListUtils.map(method.getArguments(), (i, arg) ->
i == 0 ? null : arg // null removes the element
));
Adding to Lists
// Add at end
classDecl.withModifiers(
ListUtils.concat(classDecl.getModifiers(), newModifier)
);
// Add at beginning
classDecl.withModifiers(
ListUtils.concat(newModifier, classDecl.getModifiers())
);
Replacing in Lists
classDecl.withModifiers(
ListUtils.map(classDecl.getModifiers(), mod ->
shouldReplace(mod) ? newModifier : mod
)
);
JavaTemplate Patterns
Simple Template
JavaTemplate template = JavaTemplate
.builder("new Expression()")
.build();
// Apply
expression = template.apply(getCursor(), expression.getCoordinates().replace());
Template with Imports
JavaTemplate template = JavaTemplate
.builder("Collections.emptyList()")
.imports("java.util.Collections")
.build();
Template with Parameters
JavaTemplate template = JavaTemplate
.builder("new #{any(String)}(#{})")
.build();
// Apply with parameters
expression = template.apply(
getCursor(),
expression.getCoordinates().replace(),
typeName, // #{any(String)}
constructorArg // #{}
);
Template with External Classpath
JavaTemplate template = JavaTemplate
.builder("new CustomType()")
.imports("com.external.CustomType")
.javaParser(JavaParser.fromJavaVersion()
.classpath("external-library"))
.build();
Context-Sensitive Template
// Use ONLY when referencing local variables/methods
JavaTemplate template = JavaTemplate
.builder("localVariable.toString()")
.contextSensitive()
.build();
Preconditions
Single Precondition
@Override
public TreeVisitor<?, ExecutionContext> getVisitor() {
return Preconditions.check(
new UsesType<>("com.example.Type", true),
new YourVisitor()
);
}
Multiple Preconditions (AND)
return Preconditions.check(
Preconditions.and(
new UsesType<>("com.example.Type", true),
new UsesMethod<>("com.example.Type methodName(..)")
),
new YourVisitor()
);
Multiple Preconditions (OR)
return Preconditions.check(
Preconditions.or(
new UsesType<>("com.example.TypeA", true),
new UsesType<>("com.example.TypeB", true)
),
new YourVisitor()
);
Java Version Check
return Preconditions.check(
new UsesJavaVersion<>(17),
new YourVisitor()
);
ScanningRecipe Patterns
Basic Accumulator Structure
public static class Accumulator {
// Use per-project tracking for multi-module support
Map<JavaProject, Set<String>> projectData = new HashMap<>();
}
Scanner (First Pass - Collect Only)
@Override
public TreeVisitor<?, ExecutionContext> getScanner(Accumulator acc) {
return new JavaIsoVisitor<>() {
@Override
public J.CompilationUnit visitCompilationUnit(J.CompilationUnit cu, ExecutionContext ctx) {
// Collect data - DO NOT MODIFY LST
JavaProject project = cu.getMarkers()
.findFirst(JavaProject.class)
.orElse(null);
if (project != null) {
acc.projectData
.computeIfAbsent(project, k -> new HashSet<>())
.add(someData);
}
return cu; // Return unchanged
}
};
}
Visitor (Second Pass - Modify Using Accumulator)
@Override
public TreeVisitor<?, ExecutionContext> getVisitor(Accumulator acc) {
return new JavaIsoVisitor<>() {
@Override
public J.CompilationUnit visitCompilationUnit(J.CompilationUnit cu, ExecutionContext ctx) {
JavaProject project = cu.getMarkers()
.findFirst(JavaProject.class)
.orElse(null);
if (project == null) {
return cu;
}
// Use accumulator data to make decisions
Set<String> projectData = acc.projectData.get(project);
if (projectData != null && projectData.contains(someCondition)) {
// Make changes
}
return cu;
}
};
}
Formatting Preservation
Use Space.format()
// Preserve formatting when adding elements
Space.format("\n" + indent)
Copy Formatting from Existing Elements
newElement = newElement.withPrefix(existingElement.getPrefix());
Testing Patterns
Multi-file Tests
rewriteRun(
java(
"""
package com.example;
class First { }
""",
"""
package com.example;
class First { /* modified */ }
"""
),
java(
"""
package com.example;
class Second { }
"""
// No second arg = no change expected
)
);
Tests with Parser Configuration
@Override
public void defaults(RecipeSpec spec) {
spec.recipe(new YourRecipe())
.parser(JavaParser.fromJavaVersion()
.classpath("external-library"));
}
Tests with Different Java Versions
rewriteRun(
spec -> spec.parser(JavaParser.fromJavaVersion().version("11")),
java(
// Java 11 specific code
)
);
Error Handling
Safe Type Access
// Always check for null
if (element.getType() != null) {
// Safe to use type
}
Safe Method Type Access
JavaType.Method methodType = method.getMethodType();
if (methodType != null && methodType.getDeclaringType() != null) {
// Safe to use
}
Performance Optimization
Referential Equality Check
J.ClassDeclaration cd = super.visitClassDeclaration(classDecl, ctx);
if (!shouldChange(cd)) {
return cd; // Same object = no work downstream
}
Early Return Pattern
// Check cheapest conditions first
if (element.getName() == null) {
return element;
}
if (element.getType() == null) {
return element;
}
// Then check more expensive conditions
if (!TypeUtils.isOfClassType(element.getType(), targetType)) {
return element;
}
// Finally, make changes
return makeChanges(element);