11 KiB
11 KiB
Java LST Structure Reference
Complete reference for OpenRewrite's Java Lossless Semantic Tree (LST) structure.
Overview
The Java LST represents Java code as a tree structure that preserves all formatting, comments, and whitespace. This allows transformations that maintain the original file's appearance.
Type Hierarchy
org.openrewrite.java.tree.J
├── J.CompilationUnit (root of Java file)
├── J.ClassDeclaration (class definitions)
├── J.MethodDeclaration (method definitions)
├── J.MethodInvocation (method calls)
├── J.VariableDeclarations (variable declarations)
├── J.Block (code blocks)
├── J.If (if statements)
├── J.ForLoop (for loops)
├── J.WhileLoop (while loops)
├── J.Try (try-catch blocks)
├── J.Import (import statements)
├── J.Annotation (annotations)
├── J.Binary (binary operations: +, -, *, /, &&, ||, etc.)
├── J.Literal (primitive literals)
├── J.Identifier (variable/type names)
├── J.NewClass (object instantiation)
├── J.Return (return statements)
├── J.Assignment (assignments)
└── ... (many more)
Core Types
J.CompilationUnit
The root element of a Java source file.
public interface CompilationUnit extends JavaSourceFile, J {
List<Import> getImports();
List<ClassDeclaration> getClasses();
Space getEof();
// ... other methods
}
Usage:
@Override
public J.CompilationUnit visitCompilationUnit(J.CompilationUnit cu, ExecutionContext ctx) {
// Visit entire file
cu = super.visitCompilationUnit(cu, ctx);
// Access package declaration
String packageName = cu.getPackageDeclaration() != null ?
cu.getPackageDeclaration().getExpression().printTrimmed() : null;
// Access imports
List<J.Import> imports = cu.getImports();
// Access classes
List<J.ClassDeclaration> classes = cu.getClasses();
return cu;
}
J.ClassDeclaration
Represents class, interface, enum, or record declarations.
public interface ClassDeclaration extends Statement, TypedTree {
List<Annotation> getLeadingAnnotations();
List<Modifier> getModifiers();
Kind getKind(); // Class, Interface, Enum, Record, Annotation
Identifier getName();
@Nullable TypeParameters getTypeParameters();
@Nullable TypeTree getExtends();
@Nullable Container<TypeTree> getImplements();
Block getBody();
JavaType.FullyQualified getType();
}
Usage:
@Override
public J.ClassDeclaration visitClassDeclaration(J.ClassDeclaration classDecl, ExecutionContext ctx) {
classDecl = super.visitClassDeclaration(classDecl, ctx);
// Get class name
String className = classDecl.getSimpleName();
// Get fully qualified name
if (classDecl.getType() != null) {
String fqn = classDecl.getType().getFullyQualifiedName();
}
// Check if interface
if (classDecl.getKind() == J.ClassDeclaration.Kind.Type.Interface) {
// ...
}
// Access methods
List<Statement> statements = classDecl.getBody().getStatements();
for (Statement statement : statements) {
if (statement instanceof J.MethodDeclaration) {
J.MethodDeclaration method = (J.MethodDeclaration) statement;
// Process method
}
}
return classDecl;
}
J.MethodDeclaration
Represents method declarations.
public interface MethodDeclaration extends Statement, TypedTree {
List<Annotation> getLeadingAnnotations();
List<Modifier> getModifiers();
@Nullable TypeParameters getTypeParameters();
@Nullable TypeTree getReturnTypeExpression();
Identifier getName();
List<Statement> getParameters();
@Nullable Container<NameTree> getThrows();
@Nullable Block getBody();
JavaType.Method getMethodType();
}
Usage:
@Override
public J.MethodDeclaration visitMethodDeclaration(J.MethodDeclaration method, ExecutionContext ctx) {
method = super.visitMethodDeclaration(method, ctx);
// Get method name
String methodName = method.getSimpleName();
// Get parameters
List<Statement> params = method.getParameters();
// Get return type
TypeTree returnType = method.getReturnTypeExpression();
// Get method body
J.Block body = method.getBody();
// Check if method matches signature
if (method.getMethodType() != null &&
TypeUtils.isOfType(method.getMethodType(), "com.example.Type", "methodName")) {
// Method matches
}
return method;
}
J.MethodInvocation
Represents method calls.
public interface MethodInvocation extends Expression, TypedTree, MethodCall {
@Nullable Expression getSelect(); // Object being called on
Identifier getName();
List<Expression> getArguments();
JavaType.Method getMethodType();
}
Usage:
@Override
public J.MethodInvocation visitMethodInvocation(J.MethodInvocation method, ExecutionContext ctx) {
method = super.visitMethodInvocation(method, ctx);
// Get method name
String methodName = method.getSimpleName();
// Get arguments
List<Expression> args = method.getArguments();
// Check if calling specific method
if (method.getMethodType() != null &&
TypeUtils.isOfType(method.getMethodType(), "java.lang.String", "equals")) {
// This is a String.equals() call
}
// Get select (object being called on)
Expression select = method.getSelect();
return method;
}
J.VariableDeclarations
Represents variable declarations.
public interface VariableDeclarations extends Statement, TypedTree {
List<Annotation> getLeadingAnnotations();
List<Modifier> getModifiers();
@Nullable TypeTree getTypeExpression();
List<NamedVariable> getVariables();
}
Usage:
@Override
public J.VariableDeclarations visitVariableDeclarations(J.VariableDeclarations multiVariable, ExecutionContext ctx) {
multiVariable = super.visitVariableDeclarations(multiVariable, ctx);
// Get type
TypeTree type = multiVariable.getTypeExpression();
// Get all variables declared
for (J.VariableDeclarations.NamedVariable var : multiVariable.getVariables()) {
String varName = var.getSimpleName();
Expression initializer = var.getInitializer();
// Process variable
}
return multiVariable;
}
J.Import
Represents import statements.
public interface Import extends Statement {
boolean isStatic();
FieldAccess getQualid();
}
Usage:
@Override
public J.Import visitImport(J.Import _import, ExecutionContext ctx) {
_import = super.visitImport(_import, ctx);
// Get fully qualified name
String fqn = _import.getQualid().printTrimmed();
// Check if static import
if (_import.isStatic()) {
// Static import
}
return _import;
}
J.Annotation
Represents annotations.
public interface Annotation extends Expression {
NameTree getAnnotationType();
@Nullable Container<Expression> getArguments();
}
Usage:
@Override
public J.Annotation visitAnnotation(J.Annotation annotation, ExecutionContext ctx) {
annotation = super.visitAnnotation(annotation, ctx);
// Get annotation type
String annotationType = annotation.getAnnotationType().printTrimmed();
// Check specific annotation
if ("org.junit.Test".equals(annotationType)) {
// This is a @Test annotation
}
// Get arguments
if (annotation.getArguments() != null) {
List<Expression> args = annotation.getArguments().getElements();
}
return annotation;
}
Common Patterns
Type Checking
// Check if method invocation is of specific type
if (method.getMethodType() != null &&
TypeUtils.isOfClassType(method.getMethodType().getDeclaringType(), "com.example.Class")) {
// Method is declared in com.example.Class
}
// Check method signature
if (TypeUtils.isOfType(method.getMethodType(), "com.example.Type", "methodName")) {
// Method matches
}
Safe Value Access
// Always check for null before accessing type information
if (classDecl.getType() != null) {
String fqn = classDecl.getType().getFullyQualifiedName();
}
// Check for null on optional elements
if (method.getBody() != null) {
List<Statement> statements = method.getBody().getStatements();
}
Modifying LST Elements
// Always use .withX() methods - never mutate
classDecl = classDecl.withName(classDecl.getName().withSimpleName("NewName"));
// Use ListUtils for list operations
classDecl = classDecl.withModifiers(
ListUtils.concat(classDecl.getModifiers(), newModifier)
);
// Remove from list
method = method.withArguments(
ListUtils.map(method.getArguments(), (i, arg) ->
i == indexToRemove ? null : arg
)
);
Import Management
// Add import if not present
maybeAddImport("java.util.List");
// Add static import
maybeAddImport("java.util.Collections", "emptyList");
// Remove import
maybeRemoveImport("old.package.Type");
Visitor Chaining
// Chain another visitor after this one
doAfterVisit(new SomeOtherRecipe().getVisitor());
Visit Method Reference
Common visit methods to override:
| LST Element | Visit Method | Common Use |
|---|---|---|
J.CompilationUnit |
visitCompilationUnit() |
Entire file operations |
J.ClassDeclaration |
visitClassDeclaration() |
Class modifications |
J.MethodDeclaration |
visitMethodDeclaration() |
Method modifications |
J.MethodInvocation |
visitMethodInvocation() |
Method call changes |
J.VariableDeclarations |
visitVariableDeclarations() |
Variable operations |
J.Block |
visitBlock() |
Code block operations |
J.If |
visitIf() |
Conditional logic |
J.ForLoop |
visitForLoop() |
Loop transformations |
J.Import |
visitImport() |
Import management |
J.Annotation |
visitAnnotation() |
Annotation operations |
J.Binary |
visitBinary() |
Binary operations |
J.Literal |
visitLiteral() |
Literal values |
J.Assignment |
visitAssignment() |
Assignment operations |
J.Return |
visitReturn() |
Return statements |
J.NewClass |
visitNewClass() |
Object instantiation |
Best Practices
- Always call super -
super.visitX()traverses the subtree - Return modified copies - Never mutate LST elements directly
- Use
.withX()methods - For all modifications - Handle null cases - Check for null before accessing type information
- Preserve formatting - LST methods maintain formatting automatically
- Use ListUtils - For all list operations (never mutate directly)
- Check types safely - Use TypeUtils methods with null checks
See Also
references/common-patterns.md- Code patterns for common operationsreferences/troubleshooting-guide.md- Solutions to common issuestemplates/template-imperative-recipe.java- Complete recipe template