Skip to content

Commit bc8e2f7

Browse files
evie-lautimtebeekgithub-actions[bot]
authored
Add Transient annotation to private accessor methods (openrewrite#425)
* Initial implementation with test * Polishing and add another test * Check if the method return value is a field from the parent class * Prevent applying annotation twice; add unhandled case * Add test to show annotated field not changed again * Handle FieldAccess and Literal returns, add test for Literal * Cleanup * Small improvements and comments * Refactor, add test * Handle private accessor in inner class of an entity to match WAMT behavior * Fix test formatting Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> * Apply suggestions from code review Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> * Add protected getter to test * Check all returns in method for class field variables * Add complex logic test without field access * Refactor for efficiency * Add logic comments --------- Co-authored-by: Tim te Beek <[email protected]> Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
1 parent 3897b37 commit bc8e2f7

File tree

3 files changed

+589
-0
lines changed

3 files changed

+589
-0
lines changed
Lines changed: 126 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,126 @@
1+
/*
2+
* Copyright 2024 the original author or authors.
3+
* <p>
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
* <p>
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
* <p>
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package org.openrewrite.java.migrate.javax;
17+
18+
import lombok.EqualsAndHashCode;
19+
import lombok.Value;
20+
import org.openrewrite.ExecutionContext;
21+
import org.openrewrite.Preconditions;
22+
import org.openrewrite.Recipe;
23+
import org.openrewrite.TreeVisitor;
24+
import org.openrewrite.java.JavaIsoVisitor;
25+
import org.openrewrite.java.JavaParser;
26+
import org.openrewrite.java.JavaTemplate;
27+
import org.openrewrite.java.search.FindAnnotations;
28+
import org.openrewrite.java.search.UsesType;
29+
import org.openrewrite.java.tree.Expression;
30+
import org.openrewrite.java.tree.J;
31+
import org.openrewrite.java.tree.JavaType;
32+
33+
import java.util.*;
34+
import java.util.stream.Collectors;
35+
36+
@Value
37+
@EqualsAndHashCode(callSuper = false)
38+
public class AddTransientAnnotationToPrivateAccessor extends Recipe {
39+
40+
@Override
41+
public String getDisplayName() {
42+
return "Private accessor methods must have a `@Transient` annotation";
43+
}
44+
45+
@Override
46+
public String getDescription() {
47+
return "According to the JPA 2.1 specification, when property access is used, the property accessor methods " +
48+
"must be public or protected. OpenJPA ignores any private accessor methods, whereas EclipseLink persists " +
49+
"those attributes. To ignore private accessor methods in EclipseLink, the methods must have a " +
50+
"`@Transient` annotation.";
51+
}
52+
53+
54+
@Override
55+
public TreeVisitor<?, ExecutionContext> getVisitor() {
56+
return Preconditions.check(
57+
new UsesType<>("javax.persistence.Entity", true),
58+
new JavaIsoVisitor<ExecutionContext>() {
59+
List<JavaType.Variable> classVars = new ArrayList<>();
60+
@Override
61+
public J.ClassDeclaration visitClassDeclaration(J.ClassDeclaration classDecl, ExecutionContext ctx) {
62+
// Collect all class variables
63+
classVars = classDecl.getBody().getStatements().stream()
64+
.filter(J.VariableDeclarations.class::isInstance)
65+
.map(J.VariableDeclarations.class::cast)
66+
.map(J.VariableDeclarations::getVariables)
67+
.flatMap(Collection::stream)
68+
.map(var -> var.getName().getFieldType())
69+
.filter(Objects::nonNull)
70+
.collect(Collectors.toList());
71+
return super.visitClassDeclaration(classDecl, ctx);
72+
}
73+
74+
@Override
75+
public J.MethodDeclaration visitMethodDeclaration(J.MethodDeclaration md, ExecutionContext ctx) {
76+
if (isPrivateAccessorMethodWithoutTransientAnnotation(md)) {
77+
// Add @Transient annotation
78+
maybeAddImport("javax.persistence.Transient");
79+
return JavaTemplate.builder("@Transient")
80+
.contextSensitive()
81+
.javaParser(JavaParser.fromJavaVersion().classpathFromResources(ctx, "javax.persistence-api-2.2"))
82+
.imports("javax.persistence.Transient")
83+
.build()
84+
.apply(getCursor(), md.getCoordinates().addAnnotation(Comparator.comparing(J.Annotation::getSimpleName)));
85+
}
86+
return md;
87+
}
88+
89+
private boolean isPrivateAccessorMethodWithoutTransientAnnotation(J.MethodDeclaration method) {
90+
return method.hasModifier(J.Modifier.Type.Private)
91+
&& method.getParameters().get(0) instanceof J.Empty
92+
&& method.getReturnTypeExpression().getType() != JavaType.Primitive.Void
93+
&& FindAnnotations.find(method, "javax.persistence.Transient").isEmpty()
94+
&& methodReturnsFieldFromClass(method);
95+
}
96+
97+
/**
98+
* Check if the given method returns a field defined in the parent class
99+
*/
100+
private boolean methodReturnsFieldFromClass(J.MethodDeclaration method) {
101+
// Get all return values from method
102+
List<JavaType.Variable> returns = new ArrayList<>();
103+
JavaIsoVisitor<List<JavaType.Variable>> returnValueCollector = new JavaIsoVisitor<List<JavaType.Variable>>() {
104+
@Override
105+
public J.Return visitReturn(J.Return ret, List<JavaType.Variable> returnedVars) {
106+
Expression expression = ret.getExpression();
107+
JavaType.Variable returnedVar;
108+
if (expression instanceof J.FieldAccess) { // ie: return this.field;
109+
returnedVar = ((J.FieldAccess) expression).getName().getFieldType();
110+
returnedVars.add(returnedVar);
111+
} else if (expression instanceof J.Identifier) { // ie: return field;
112+
returnedVar = ((J.Identifier) expression).getFieldType();
113+
returnedVars.add(returnedVar);
114+
} // last case should be null: do nothing and continue
115+
return super.visitReturn(ret, returnedVars);
116+
}
117+
};
118+
returnValueCollector.visitBlock(method.getBody(), returns);
119+
120+
// Check if any return values are a class field
121+
return returns.stream().anyMatch(classVars::contains);
122+
}
123+
}
124+
);
125+
}
126+
}

src/main/resources/META-INF/rewrite/openjpa-to-eclipselink.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ recipeList:
2727
- org.openrewrite.java.migrate.javax.AddTableGenerator
2828
- org.openrewrite.java.migrate.javax.AddTransientAnnotationToCollections
2929
- org.openrewrite.java.migrate.javax.AddTransientAnnotationToEntity
30+
- org.openrewrite.java.migrate.javax.AddTransientAnnotationToPrivateAccessor
3031
- org.openrewrite.java.migrate.javax.RemoveEmbeddableId
3132
- org.openrewrite.java.migrate.javax.RemoveTemporalAnnotation
3233
- org.openrewrite.java.migrate.javax.UseJoinColumnForMapping

0 commit comments

Comments
 (0)