From 1f39323a97835c248168894465088430b0f21fef Mon Sep 17 00:00:00 2001 From: Evie Lau Date: Mon, 19 Feb 2024 16:02:08 -0600 Subject: [PATCH 1/7] Initial implementation with tests --- .../javax/RemoveTemporalAnnotation.java | 104 ++++++++++++++++++ .../rewrite/openjpa-to-eclipselink.yml | 3 +- .../javax/RemoveTemporalAnnotationTest.java | 101 +++++++++++++++++ 3 files changed, 207 insertions(+), 1 deletion(-) create mode 100644 src/main/java/org/openrewrite/java/migrate/javax/RemoveTemporalAnnotation.java create mode 100644 src/test/java/org/openrewrite/java/migrate/javax/RemoveTemporalAnnotationTest.java diff --git a/src/main/java/org/openrewrite/java/migrate/javax/RemoveTemporalAnnotation.java b/src/main/java/org/openrewrite/java/migrate/javax/RemoveTemporalAnnotation.java new file mode 100644 index 0000000000..61a6344eb0 --- /dev/null +++ b/src/main/java/org/openrewrite/java/migrate/javax/RemoveTemporalAnnotation.java @@ -0,0 +1,104 @@ +package org.openrewrite.java.migrate.javax; + +import lombok.EqualsAndHashCode; +import lombok.Value; +import org.openrewrite.ExecutionContext; +import org.openrewrite.Preconditions; +import org.openrewrite.Recipe; +import org.openrewrite.TreeVisitor; +import org.openrewrite.java.JavaIsoVisitor; +import org.openrewrite.java.RemoveAnnotation; +import org.openrewrite.java.search.FindAnnotations; +import org.openrewrite.java.search.UsesType; +import org.openrewrite.java.tree.J; + +import java.util.Set; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +@Value +@EqualsAndHashCode(callSuper=false) +public class RemoveTemporalAnnotation extends Recipe { + /* + * This rule scans for the following annotation-attribute combinations where data does not need to be converted and the Temporal annotation must be removed to avoid an EclipseLink error: + * + * A javax.persistence.Temporal(TemporalType.DATE) annotation on a java.sql.Date attribute + * A the javax.persistence.Temporal(TemporalType.TIME) annotation on a java.sql.Date attribute + * A the javax.persistence.Temporal(TemporalType.DATE) annotation on a java.sql.Time attribute + * A the javax.persistence.Temporal(TemporalType.TIME) annotation on a java.sql.Time attribute + * A the javax.persistence.Temporal(TemporalType.TIMESTAMP) annotation on a java.sql.Time + * A the javax.persistence.Temporal(TemporalType.TIMESTAMP) annotation on a java.sql.Timestamp attribute + * + */ + + @Override + public String getDisplayName() { + return "Remove the `@Temporal` annotation for some `java.sql` attributes"; + } + + @Override + public String getDescription() { + return "OpenJPA persists the fields of attributes of type `java.sql.Date`, `java.sql.Time`, or `java.sql.Timestamp` that have a `javax.persistence.Temporal` annotation, whereas EclipseLink throws an exception."; + } + + @Override + public TreeVisitor getVisitor() { + Pattern temporalPattern = Pattern.compile(".*TemporalType\\.(TIMESTAMP|DATE|TIME)"); + return Preconditions.check( + Preconditions.and( + new UsesType<>("javax.persistence.Temporal", true), + Preconditions.or( + new UsesType<>("java.sql.Date", true), + new UsesType<>("java.sql.Time", true), + new UsesType<>("java.sql.Timestamp", true) + ) + ), + new JavaIsoVisitor() { + @Override + public J.VariableDeclarations visitVariableDeclarations(J.VariableDeclarations multiVariable, ExecutionContext ctx) { + // T && (A || B || C + // !(T && (A || B || C) + // !T || !(A || B || C) + // !T || !A && !B && !C + // Exit if no @Temporal annotation, or var is not java.sql.Date/Time/Timestamp + String varClass = multiVariable.getType().toString(); + Set temporalAnnos = FindAnnotations.find(multiVariable, "javax.persistence.Temporal"); + if (temporalAnnos.isEmpty() + || !varClass.equals("java.sql.Date") + && !varClass.equals("java.sql.Time") + && !varClass.equals("java.sql.Timestamp")) { + return multiVariable; + } + + // Get TemporalType + J.Annotation temporal = temporalAnnos.iterator().next(); + String temporalDef = temporal.getArguments().iterator().next().toString(); + Matcher temporalMatch = temporalPattern.matcher(temporalDef); + if (!temporalMatch.find()) { + return multiVariable; + } + String temporalType = temporalMatch.group(1); + + // Check combination of attribute and var's class + switch (varClass) { + case "java.sql.Date": + if (!temporalType.equals("DATE") && !temporalType.equals("TIME")) { + return multiVariable; + } + break; + case "java.sql.Timestamp": + if (!temporalType.equals("TIMESTAMP")) { + return multiVariable; + } + break; + default: // TIME does not work with any + break; + } + + // Remove @Temporal annotation on this var + return (J.VariableDeclarations) new RemoveAnnotation("javax.persistence.Temporal").getVisitor().visit(multiVariable, ctx); + } + } + ); + } +} \ No newline at end of file diff --git a/src/main/resources/META-INF/rewrite/openjpa-to-eclipselink.yml b/src/main/resources/META-INF/rewrite/openjpa-to-eclipselink.yml index 7260e8fd78..a1ab8fa46e 100644 --- a/src/main/resources/META-INF/rewrite/openjpa-to-eclipselink.yml +++ b/src/main/resources/META-INF/rewrite/openjpa-to-eclipselink.yml @@ -22,4 +22,5 @@ tags: - javaee7 - deprecated recipeList: - - org.openrewrite.java.migrate.javax.AddTableGenerator \ No newline at end of file + - org.openrewrite.java.migrate.javax.AddTableGenerator + - org.openrewrite.java.migrate.javax.RemoveTemporalAnnotation \ No newline at end of file diff --git a/src/test/java/org/openrewrite/java/migrate/javax/RemoveTemporalAnnotationTest.java b/src/test/java/org/openrewrite/java/migrate/javax/RemoveTemporalAnnotationTest.java new file mode 100644 index 0000000000..f20cc46e28 --- /dev/null +++ b/src/test/java/org/openrewrite/java/migrate/javax/RemoveTemporalAnnotationTest.java @@ -0,0 +1,101 @@ +package org.openrewrite.java.migrate.javax; + +import org.junit.jupiter.api.Test; +import org.openrewrite.DocumentExample; +import org.openrewrite.InMemoryExecutionContext; +import org.openrewrite.java.JavaParser; +import org.openrewrite.test.RecipeSpec; +import org.openrewrite.test.RewriteTest; + +import static org.openrewrite.java.Assertions.java; + +class RemoveTemporalAnnotationTest implements RewriteTest { + @Override + public void defaults(RecipeSpec spec) { + spec.parser(JavaParser.fromJavaVersion().classpathFromResources(new InMemoryExecutionContext(), "javax.persistence-api-2.2")) + .recipe(new RemoveTemporalAnnotation()); + } + + @Test + @DocumentExample + void removeTemporalAnnotation() { + rewriteRun( + java( + """ + import javax.persistence.Temporal; + import javax.persistence.TemporalType; + import java.sql.Date; + import java.sql.Time; + import java.sql.Timestamp; + + public class TemporalDates { + @Temporal(TemporalType.DATE) + private Date dateDate; + + @Temporal(TemporalType.TIME) + private Date dateTime; + + @Temporal(TemporalType.DATE) + private Time timeDate; + + @Temporal(TemporalType.TIME) + private java.sql.Time timeTime; + + @Temporal(TemporalType.TIMESTAMP) + private java.sql.Time timeTimestamp; + + @Temporal(TemporalType.TIMESTAMP) + private java.sql.Timestamp timestampTimestamp; + } + """, + """ + import javax.persistence.Temporal; + import javax.persistence.TemporalType; + import java.sql.Date; + import java.sql.Time; + import java.sql.Timestamp; + + public class TemporalDates { + private Date dateDate; + + private Date dateTime; + + private Time timeDate; + + private java.sql.Time timeTime; + + private java.sql.Time timeTimestamp; + + private java.sql.Timestamp timestampTimestamp; + } + """ + ) + ); + } + + @Test + void dontChangeOtherCombinations() { + rewriteRun( + java( + """ + import javax.persistence.Temporal; + import javax.persistence.TemporalType; + import java.sql.Date; + import java.sql.Time; + import java.sql.Timestamp; + + public class TemporalDates { + @Temporal(TemporalType.TIMESTAMP) + private Date dateDate; + + @Temporal(TemporalType.DATE) + private java.sql.Timestamp timestampTimestamp; + + @Temporal(TemporalType.TIME) + private java.sql.Timestamp timestampTimestamp; + } + """ + ) + ); + } +} \ No newline at end of file From bb012d01f3c74b27af36c80831d9de2a93414882 Mon Sep 17 00:00:00 2001 From: Evie Lau Date: Mon, 19 Feb 2024 16:36:12 -0600 Subject: [PATCH 2/7] Refactor to simplify conditions --- .../javax/RemoveTemporalAnnotation.java | 56 ++++++++++--------- 1 file changed, 30 insertions(+), 26 deletions(-) diff --git a/src/main/java/org/openrewrite/java/migrate/javax/RemoveTemporalAnnotation.java b/src/main/java/org/openrewrite/java/migrate/javax/RemoveTemporalAnnotation.java index 61a6344eb0..45c1b9860e 100644 --- a/src/main/java/org/openrewrite/java/migrate/javax/RemoveTemporalAnnotation.java +++ b/src/main/java/org/openrewrite/java/migrate/javax/RemoveTemporalAnnotation.java @@ -12,9 +12,13 @@ import org.openrewrite.java.search.UsesType; import org.openrewrite.java.tree.J; +import java.util.HashMap; +import java.util.Map; import java.util.Set; import java.util.regex.Matcher; import java.util.regex.Pattern; +import java.util.stream.Collectors; +import java.util.stream.Stream; @Value @EqualsAndHashCode(callSuper=false) @@ -44,55 +48,55 @@ public String getDescription() { @Override public TreeVisitor getVisitor() { Pattern temporalPattern = Pattern.compile(".*TemporalType\\.(TIMESTAMP|DATE|TIME)"); + final String JAVA_SQL_TIMESTAMP = "java.sql.Timestamp"; + final String JAVA_SQL_TIME = "java.sql.Time"; + final String JAVA_SQL_DATE = "java.sql.Date"; + + Set javaSqlDateTimeTypes = Stream.of( + JAVA_SQL_TIMESTAMP, + JAVA_SQL_TIME, + JAVA_SQL_DATE + ).collect(Collectors.toSet()); + // Combinations of TemporalType and java.sql classes that do not need removal + Map doNotRemove = Stream.of(new String[][] { + {"DATE", JAVA_SQL_TIMESTAMP}, + {"TIME", JAVA_SQL_TIMESTAMP}, + {"TIMESTAMP", JAVA_SQL_DATE} + }).collect(Collectors.toMap(data -> data[0], data -> data[1])); + // TODO: maybe future recipe to handle these by creating a converter class + // https://wiki.eclipse.org/EclipseLink/Examples/JPA/Migration/OpenJPA/Mappings#.40Temporal_on_java.sql.Date.2FTime.2FTimestamp_fields + return Preconditions.check( Preconditions.and( new UsesType<>("javax.persistence.Temporal", true), Preconditions.or( - new UsesType<>("java.sql.Date", true), - new UsesType<>("java.sql.Time", true), - new UsesType<>("java.sql.Timestamp", true) + new UsesType<>(JAVA_SQL_DATE, true), + new UsesType<>(JAVA_SQL_TIME, true), + new UsesType<>(JAVA_SQL_TIMESTAMP, true) ) ), new JavaIsoVisitor() { @Override public J.VariableDeclarations visitVariableDeclarations(J.VariableDeclarations multiVariable, ExecutionContext ctx) { - // T && (A || B || C - // !(T && (A || B || C) - // !T || !(A || B || C) - // !T || !A && !B && !C // Exit if no @Temporal annotation, or var is not java.sql.Date/Time/Timestamp String varClass = multiVariable.getType().toString(); Set temporalAnnos = FindAnnotations.find(multiVariable, "javax.persistence.Temporal"); - if (temporalAnnos.isEmpty() - || !varClass.equals("java.sql.Date") - && !varClass.equals("java.sql.Time") - && !varClass.equals("java.sql.Timestamp")) { + if (temporalAnnos.isEmpty() || !javaSqlDateTimeTypes.contains(varClass)) { return multiVariable; } // Get TemporalType J.Annotation temporal = temporalAnnos.iterator().next(); - String temporalDef = temporal.getArguments().iterator().next().toString(); - Matcher temporalMatch = temporalPattern.matcher(temporalDef); + String temporalArg = temporal.getArguments().iterator().next().toString(); + Matcher temporalMatch = temporalPattern.matcher(temporalArg); if (!temporalMatch.find()) { return multiVariable; } String temporalType = temporalMatch.group(1); // Check combination of attribute and var's class - switch (varClass) { - case "java.sql.Date": - if (!temporalType.equals("DATE") && !temporalType.equals("TIME")) { - return multiVariable; - } - break; - case "java.sql.Timestamp": - if (!temporalType.equals("TIMESTAMP")) { - return multiVariable; - } - break; - default: // TIME does not work with any - break; + if (doNotRemove.get(temporalType).equals(varClass)) { + return multiVariable; } // Remove @Temporal annotation on this var From b7f38dcc98be654e3a950188d7e6854ae9dd4243 Mon Sep 17 00:00:00 2001 From: Evie Lau Date: Tue, 20 Feb 2024 09:53:29 -0600 Subject: [PATCH 3/7] Cleanup --- .../migrate/javax/RemoveTemporalAnnotation.java | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/src/main/java/org/openrewrite/java/migrate/javax/RemoveTemporalAnnotation.java b/src/main/java/org/openrewrite/java/migrate/javax/RemoveTemporalAnnotation.java index 45c1b9860e..5c6ce59a14 100644 --- a/src/main/java/org/openrewrite/java/migrate/javax/RemoveTemporalAnnotation.java +++ b/src/main/java/org/openrewrite/java/migrate/javax/RemoveTemporalAnnotation.java @@ -12,7 +12,6 @@ import org.openrewrite.java.search.UsesType; import org.openrewrite.java.tree.J; -import java.util.HashMap; import java.util.Map; import java.util.Set; import java.util.regex.Matcher; @@ -21,10 +20,11 @@ import java.util.stream.Stream; @Value -@EqualsAndHashCode(callSuper=false) +@EqualsAndHashCode(callSuper = false) public class RemoveTemporalAnnotation extends Recipe { /* - * This rule scans for the following annotation-attribute combinations where data does not need to be converted and the Temporal annotation must be removed to avoid an EclipseLink error: + * This rule scans for the following annotation-attribute combinations where data does not need to be converted + * and the Temporal annotation must be removed to avoid an EclipseLink error: * * A javax.persistence.Temporal(TemporalType.DATE) annotation on a java.sql.Date attribute * A the javax.persistence.Temporal(TemporalType.TIME) annotation on a java.sql.Date attribute @@ -33,6 +33,7 @@ public class RemoveTemporalAnnotation extends Recipe { * A the javax.persistence.Temporal(TemporalType.TIMESTAMP) annotation on a java.sql.Time * A the javax.persistence.Temporal(TemporalType.TIMESTAMP) annotation on a java.sql.Timestamp attribute * + * NOTES: @Temporal has a required argument, which can only be TemporalType.DATE/TIME/TIMESTAMP */ @Override @@ -42,7 +43,8 @@ public String getDisplayName() { @Override public String getDescription() { - return "OpenJPA persists the fields of attributes of type `java.sql.Date`, `java.sql.Time`, or `java.sql.Timestamp` that have a `javax.persistence.Temporal` annotation, whereas EclipseLink throws an exception."; + return "OpenJPA persists the fields of attributes of type `java.sql.Date`, `java.sql.Time`, or `java.sql.Timestamp` " + + "that have a `javax.persistence.Temporal` annotation, whereas EclipseLink throws an exception."; } @Override @@ -56,9 +58,9 @@ public TreeVisitor getVisitor() { JAVA_SQL_TIMESTAMP, JAVA_SQL_TIME, JAVA_SQL_DATE - ).collect(Collectors.toSet()); + ).collect(Collectors.toSet()); // Combinations of TemporalType and java.sql classes that do not need removal - Map doNotRemove = Stream.of(new String[][] { + Map doNotRemove = Stream.of(new String[][]{ {"DATE", JAVA_SQL_TIMESTAMP}, {"TIME", JAVA_SQL_TIMESTAMP}, {"TIMESTAMP", JAVA_SQL_DATE} @@ -99,7 +101,7 @@ public J.VariableDeclarations visitVariableDeclarations(J.VariableDeclarations m return multiVariable; } - // Remove @Temporal annotation on this var + // Remove @Temporal annotation return (J.VariableDeclarations) new RemoveAnnotation("javax.persistence.Temporal").getVisitor().visit(multiVariable, ctx); } } From b527a3f8af4fa6b1c96f576712625b2405a54ad8 Mon Sep 17 00:00:00 2001 From: Evie Lau Date: Tue, 20 Feb 2024 10:40:02 -0600 Subject: [PATCH 4/7] Add copyright --- .../migrate/javax/RemoveTemporalAnnotation.java | 15 +++++++++++++++ .../javax/RemoveTemporalAnnotationTest.java | 15 +++++++++++++++ 2 files changed, 30 insertions(+) diff --git a/src/main/java/org/openrewrite/java/migrate/javax/RemoveTemporalAnnotation.java b/src/main/java/org/openrewrite/java/migrate/javax/RemoveTemporalAnnotation.java index 5c6ce59a14..7f46d2e3f4 100644 --- a/src/main/java/org/openrewrite/java/migrate/javax/RemoveTemporalAnnotation.java +++ b/src/main/java/org/openrewrite/java/migrate/javax/RemoveTemporalAnnotation.java @@ -1,3 +1,18 @@ +/* + * Copyright 2024 the original author or authors. + *

+ * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + *

+ * https://www.apache.org/licenses/LICENSE-2.0 + *

+ * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ package org.openrewrite.java.migrate.javax; import lombok.EqualsAndHashCode; diff --git a/src/test/java/org/openrewrite/java/migrate/javax/RemoveTemporalAnnotationTest.java b/src/test/java/org/openrewrite/java/migrate/javax/RemoveTemporalAnnotationTest.java index f20cc46e28..31cb463161 100644 --- a/src/test/java/org/openrewrite/java/migrate/javax/RemoveTemporalAnnotationTest.java +++ b/src/test/java/org/openrewrite/java/migrate/javax/RemoveTemporalAnnotationTest.java @@ -1,3 +1,18 @@ +/* + * Copyright 2024 the original author or authors. + *

+ * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + *

+ * https://www.apache.org/licenses/LICENSE-2.0 + *

+ * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ package org.openrewrite.java.migrate.javax; import org.junit.jupiter.api.Test; From 1cb6293274634c2f6e766a7fe89b3daf81811e97 Mon Sep 17 00:00:00 2001 From: Evie Lau Date: Tue, 20 Feb 2024 18:23:41 -0600 Subject: [PATCH 5/7] Add language comment to test --- .../java/migrate/javax/RemoveTemporalAnnotationTest.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/test/java/org/openrewrite/java/migrate/javax/RemoveTemporalAnnotationTest.java b/src/test/java/org/openrewrite/java/migrate/javax/RemoveTemporalAnnotationTest.java index 31cb463161..37f32c212b 100644 --- a/src/test/java/org/openrewrite/java/migrate/javax/RemoveTemporalAnnotationTest.java +++ b/src/test/java/org/openrewrite/java/migrate/javax/RemoveTemporalAnnotationTest.java @@ -34,6 +34,7 @@ public void defaults(RecipeSpec spec) { @Test @DocumentExample void removeTemporalAnnotation() { + //language=java rewriteRun( java( """ @@ -90,6 +91,7 @@ public class TemporalDates { @Test void dontChangeOtherCombinations() { + //language=java rewriteRun( java( """ From 6a6613827d7380b82b4aa4618862b6c9b5fc4244 Mon Sep 17 00:00:00 2001 From: Evie Lau Date: Wed, 28 Feb 2024 10:55:43 -0600 Subject: [PATCH 6/7] Update description with recipe action --- .../java/migrate/javax/RemoveTemporalAnnotation.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/main/java/org/openrewrite/java/migrate/javax/RemoveTemporalAnnotation.java b/src/main/java/org/openrewrite/java/migrate/javax/RemoveTemporalAnnotation.java index 7f46d2e3f4..4964189b9b 100644 --- a/src/main/java/org/openrewrite/java/migrate/javax/RemoveTemporalAnnotation.java +++ b/src/main/java/org/openrewrite/java/migrate/javax/RemoveTemporalAnnotation.java @@ -59,7 +59,8 @@ public String getDisplayName() { @Override public String getDescription() { return "OpenJPA persists the fields of attributes of type `java.sql.Date`, `java.sql.Time`, or `java.sql.Timestamp` " + - "that have a `javax.persistence.Temporal` annotation, whereas EclipseLink throws an exception."; + "that have a `javax.persistence.Temporal` annotation, whereas EclipseLink throws an exception. " + + "Remove the `@Temporal` annotation so the behavior in EclipseLink will match the behavior in OpenJPA."; } @Override From f90964e8fc9ffc875f667283a0e385d24ed8bf7c Mon Sep 17 00:00:00 2001 From: Evie Lau Date: Wed, 10 Apr 2024 11:39:19 -0500 Subject: [PATCH 7/7] Add test case with valid classes --- .../javax/RemoveTemporalAnnotationTest.java | 26 ++++++++++++++++++- 1 file changed, 25 insertions(+), 1 deletion(-) diff --git a/src/test/java/org/openrewrite/java/migrate/javax/RemoveTemporalAnnotationTest.java b/src/test/java/org/openrewrite/java/migrate/javax/RemoveTemporalAnnotationTest.java index 37f32c212b..bb0b22a7e2 100644 --- a/src/test/java/org/openrewrite/java/migrate/javax/RemoveTemporalAnnotationTest.java +++ b/src/test/java/org/openrewrite/java/migrate/javax/RemoveTemporalAnnotationTest.java @@ -91,6 +91,7 @@ public class TemporalDates { @Test void dontChangeOtherCombinations() { + // These combinations require a converter //language=java rewriteRun( java( @@ -100,7 +101,7 @@ void dontChangeOtherCombinations() { import java.sql.Date; import java.sql.Time; import java.sql.Timestamp; - + public class TemporalDates { @Temporal(TemporalType.TIMESTAMP) private Date dateDate; @@ -115,4 +116,27 @@ public class TemporalDates { ) ); } + + @Test + void allowTemporalOnValidClasses() { + //language=java + rewriteRun( + java( + """ + import javax.persistence.Temporal; + import javax.persistence.TemporalType; + import java.util.Date; + import java.util.Calendar; + + public class TemporalDates { + @Temporal(TemporalType.TIMESTAMP) + private Date dateDate; + + @Temporal(TemporalType.DATE) + private Calendar timestampTimestamp; + } + """ + ) + ); + } } \ No newline at end of file