From 7ccc007ea3d29bd5f9d1f809e5fb50b691a17c4d Mon Sep 17 00:00:00 2001 From: Frederic Mencier Date: Wed, 10 Sep 2025 15:30:37 +0200 Subject: [PATCH 1/8] add RefactorTemporalAnnotation recipe --- build.gradle.kts | 1 + .../quarkus/RefactorTemporalAnnotation.java | 127 +++++++++ .../RefactorTemporalAnnotationTest.java | 256 ++++++++++++++++++ 3 files changed, 384 insertions(+) create mode 100644 src/main/java/org/openrewrite/quarkus/RefactorTemporalAnnotation.java create mode 100644 src/test/java/org/openrewrite/quarkus/RefactorTemporalAnnotationTest.java diff --git a/build.gradle.kts b/build.gradle.kts index b22e6f3..4d8b5bd 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -53,6 +53,7 @@ dependencies { testRuntimeOnly("jakarta.inject:jakarta.inject-api:2.0.1") testRuntimeOnly("jakarta.enterprise:jakarta.enterprise.cdi-api:4.1.0") testRuntimeOnly("org.projectlombok:lombok:latest.release") + testRuntimeOnly("jakarta.persistence:jakarta.persistence-api:3.2.0") } recipeDependencies { diff --git a/src/main/java/org/openrewrite/quarkus/RefactorTemporalAnnotation.java b/src/main/java/org/openrewrite/quarkus/RefactorTemporalAnnotation.java new file mode 100644 index 0000000..1715c62 --- /dev/null +++ b/src/main/java/org/openrewrite/quarkus/RefactorTemporalAnnotation.java @@ -0,0 +1,127 @@ +package org.openrewrite.quarkus; + +import lombok.EqualsAndHashCode; +import lombok.Value; +import org.jetbrains.annotations.NotNull; +import org.jspecify.annotations.Nullable; +import org.openrewrite.*; +import org.openrewrite.java.ChangeType; +import org.openrewrite.java.JavaIsoVisitor; +import org.openrewrite.java.RemoveAnnotation; +import org.openrewrite.java.TypeMatcher; +import org.openrewrite.java.search.FindAnnotations; +import org.openrewrite.java.search.UsesType; +import org.openrewrite.java.tree.J; +import org.openrewrite.java.tree.J.Annotation; +import org.openrewrite.java.tree.J.FieldAccess; +import org.openrewrite.java.tree.J.Identifier; + +import java.util.Objects; + +@Value +@EqualsAndHashCode(callSuper = false) +public class RefactorTemporalAnnotation extends Recipe { + + private static final String TEMPORAL_ANNOTATION = "jakarta.persistence.Temporal"; + private static final String ENTITY_ANNOTATION = "jakarta.persistence.Entity"; + private static final String DATE_TYPE = "java.util.Date"; + private static final String JAVA_TIME_LOCAL_DATE = "java.time.LocalDate"; + private static final String JAVA_TIME_LOCAL_TIME = "java.time.LocalTime"; + private static final String JAVA_TIME_OFFSETDATETIME = "java.time.OffsetDateTime"; + private static final String JAVA_TIME_LOCAL_DATE_TIME = "java.time.LocalDateTime"; + + @Override + public String getDisplayName() { + return "Refactor @Temporal annotation java.util.Date fields to java.time API"; + } + + @Override + public String getDescription() { + return "Replace java.util.Date fields annotated with @Temporal " + + " with java.time.LocalDate, java.time.LocalTime, java.time.LocalDateTime or java.time.OffsetDateTime."; + } + + @Option(displayName = "Use offsetDateTime", + description = "If `true` the recipe will use OffsetDateTime instead of LocalDateTime. Default `false`.", + required = false) + @Nullable + Boolean useOffsetDateTime; + + @Override + public TreeVisitor getVisitor() { + return Preconditions.check( + new UsesType<>(ENTITY_ANNOTATION, true), + new TemporalRefactorVisitor(useOffsetDateTime) + ); + } + + private static class TemporalRefactorVisitor extends JavaIsoVisitor { + + private static final TypeMatcher DATE_MATCHER = new TypeMatcher(DATE_TYPE); + + private final Boolean useOffsetDT; + + public TemporalRefactorVisitor(Boolean useOffsetDateTime) { + useOffsetDT = useOffsetDateTime; + } + + @Override + public J.@NotNull VariableDeclarations visitVariableDeclarations(J.@NotNull VariableDeclarations multiVariable, @NotNull ExecutionContext ctx) { + J.VariableDeclarations decls = super.visitVariableDeclarations(multiVariable, ctx); + + if (!DATE_MATCHER.matches(decls.getTypeExpression())) { + return decls; + } + + Annotation temporalAnnotation = FindAnnotations.find(decls, TEMPORAL_ANNOTATION).stream() + .findFirst() + .orElse(null); + + if (temporalAnnotation == null) { + return decls; + } + + // Extract the enum constant (DATE or TIMESTAMP) + String newTypeToUse = Objects.requireNonNull(temporalAnnotation.getArguments()).stream() + .findFirst() + .map(arg -> { + if (arg instanceof FieldAccess) { + return getNewType(((FieldAccess) arg).getSimpleName()); + } + if (arg instanceof Identifier) { + return getNewType(((Identifier) arg).getSimpleName()); + } + return null; + }) + .orElse(null); + + if (newTypeToUse == null) { + return decls; + } + + doAfterVisit(new RemoveAnnotation(TEMPORAL_ANNOTATION).getVisitor()); + decls = (J.VariableDeclarations) new ChangeType(DATE_TYPE, newTypeToUse, null).getVisitor().visitNonNull(decls, ctx); + + maybeRemoveImport(DATE_TYPE); + maybeAddImport(newTypeToUse); + return decls; + } + + private String getNewType(String temporalConstant) { + switch (temporalConstant) { + case "DATE": + return JAVA_TIME_LOCAL_DATE; + case "TIME": + return JAVA_TIME_LOCAL_TIME; + case "TIMESTAMP": + if (Boolean.TRUE.equals(useOffsetDT)) { + return JAVA_TIME_OFFSETDATETIME; + } else { + return JAVA_TIME_LOCAL_DATE_TIME; + } + default: + return null; + } + } + } +} diff --git a/src/test/java/org/openrewrite/quarkus/RefactorTemporalAnnotationTest.java b/src/test/java/org/openrewrite/quarkus/RefactorTemporalAnnotationTest.java new file mode 100644 index 0000000..0e167d1 --- /dev/null +++ b/src/test/java/org/openrewrite/quarkus/RefactorTemporalAnnotationTest.java @@ -0,0 +1,256 @@ +package org.openrewrite.quarkus; + +import org.junit.jupiter.api.Test; +import org.openrewrite.java.JavaParser; +import org.openrewrite.test.RecipeSpec; +import org.openrewrite.test.RewriteTest; + +import static org.openrewrite.java.Assertions.java; + +public class RefactorTemporalAnnotationTest implements RewriteTest { + + @Override + public void defaults(RecipeSpec spec) { + spec.recipes(new RefactorTemporalAnnotation(null)) + .parser(JavaParser.fromJavaVersion() + .classpath("jakarta.persistence-api")); + } + + @Test + void shouldRemoveTemporalAnnotationAndKeepOtherAnnotations() { + rewriteRun( + //language=java + java( + """ + package org.refactor.model; + + import jakarta.persistence.Column; + import jakarta.persistence.Entity; + import jakarta.persistence.Id; + import jakarta.persistence.Table; + import jakarta.persistence.Temporal; + + import java.util.Date; + + @Entity + @Table(name = "rent_house") + public class RentHouseEntity { + @Id + @Column(name = "rent_house_id") + private Long id; + + @Column(name = "status") + private String status; + + @Column(name = "start_date") + @Temporal(TemporalType.DATE) + private Date startDate; + + @Column(name = "end_date") + @Temporal(TemporalType.DATE) + private Date endDate; + + @Column(name = "creation_date") + @Temporal(TemporalType.TIMESTAMP) + private Date creationDate; + } + """, + """ + package org.refactor.model; + + import jakarta.persistence.Column; + import jakarta.persistence.Entity; + import jakarta.persistence.Id; + import jakarta.persistence.Table; + + import java.time.LocalDate; + import java.time.LocalDateTime; + + @Entity + @Table(name = "rent_house") + public class RentHouseEntity { + @Id + @Column(name = "rent_house_id") + private Long id; + + @Column(name = "status") + private String status; + + @Column(name = "start_date") + private LocalDate startDate; + + @Column(name = "end_date") + private LocalDate endDate; + + @Column(name = "creation_date") + private LocalDateTime creationDate; + } + """ + ) + ); + } + + @Test + void shouldChangeNothing() { + rewriteRun( + //language=java + java( + """ + package org.refactor.model; + + import jakarta.persistence.Entity; + import jakarta.persistence.Table; + import java.util.Date; + + @Entity + @Table(name = "some_entity") + public class SomeEntity { + private Date createdOn; + } + """ + ) + ); + } + + @Test + void shouldRemoveTemporalAnnotationAndUseGoodType() { + rewriteRun( + //language=java + java( + """ + package org.refactor.model; + + import java.util.Date; + import jakarta.persistence.Temporal; + import jakarta.persistence.TemporalType; + import jakarta.persistence.Entity; + import jakarta.persistence.Table; + + @Entity + @Table(name = "some_entity") + public class MultiTemporalEntity { + @Temporal(TemporalType.DATE) + private Date dateField; + + @Temporal(TemporalType.TIMESTAMP) + private Date timestampField; + + @Temporal(TemporalType.TIME) + private Date timeField; + } + """, + """ + package org.refactor.model; + + import jakarta.persistence.Entity; + import jakarta.persistence.Table; + + import java.time.LocalDate; + import java.time.LocalDateTime; + import java.time.LocalTime; + + @Entity + @Table(name = "some_entity") + public class MultiTemporalEntity { + private LocalDate dateField; + + private LocalDateTime timestampField; + + private LocalTime timeField; + } + """ + ) + ); + } + + @Test + void shouldRemoveTemporalAnnotationAndUseLocalDateTimeTypeWhenStaticImport() { + rewriteRun( + //language=java + java( + """ + package org.refactor.model; + + import java.util.Date; + import static jakarta.persistence.TemporalType.DATE; + import jakarta.persistence.Temporal; + import jakarta.persistence.Entity; + import jakarta.persistence.Table; + + @Entity + @Table(name = "some_entity") + public class SomeEntity { + @Temporal(DATE) + private Date dateField; + } + """, + """ + package org.refactor.model; + + import jakarta.persistence.Entity; + import jakarta.persistence.Table; + + import java.time.LocalDate; + + @Entity + @Table(name = "some_entity") + public class SomeEntity { + private LocalDate dateField; + } + """ + ) + ); + } + + @Test + void shouldRemoveTemporalAnnotationWithOffsetDateTime() { + rewriteRun( + spec -> spec.recipe(new RefactorTemporalAnnotation(true)), + //language=java + java( + """ + package org.refactor.model; + + import java.util.Date; + import jakarta.persistence.Temporal; + import jakarta.persistence.TemporalType; + import jakarta.persistence.Entity; + import jakarta.persistence.Table; + + @Entity + @Table(name = "some_entity") + public class MultiTemporalEntity { + @Temporal(TemporalType.DATE) + private Date dateField; + + @Temporal(TemporalType.TIMESTAMP) + private Date timestampField; + + @Temporal(TemporalType.TIME) + private Date timeField; + } + """, + """ + package org.refactor.model; + + import jakarta.persistence.Entity; + import jakarta.persistence.Table; + + import java.time.LocalDate; + import java.time.LocalTime; + import java.time.OffsetDateTime; + + @Entity + @Table(name = "some_entity") + public class MultiTemporalEntity { + private LocalDate dateField; + + private OffsetDateTime timestampField; + + private LocalTime timeField; + } + """ + ) + ); + } +} From 6306c25769a29fc18c5afa157513af51c6e01d53 Mon Sep 17 00:00:00 2001 From: Frederic Mencier Date: Wed, 10 Sep 2025 15:33:02 +0200 Subject: [PATCH 2/8] add License --- .../quarkus/RefactorTemporalAnnotation.java | 15 +++++++++++++++ .../quarkus/RefactorTemporalAnnotationTest.java | 15 +++++++++++++++ 2 files changed, 30 insertions(+) diff --git a/src/main/java/org/openrewrite/quarkus/RefactorTemporalAnnotation.java b/src/main/java/org/openrewrite/quarkus/RefactorTemporalAnnotation.java index 1715c62..a549951 100644 --- a/src/main/java/org/openrewrite/quarkus/RefactorTemporalAnnotation.java +++ b/src/main/java/org/openrewrite/quarkus/RefactorTemporalAnnotation.java @@ -1,3 +1,18 @@ +/* + * Copyright 2025 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.quarkus; import lombok.EqualsAndHashCode; diff --git a/src/test/java/org/openrewrite/quarkus/RefactorTemporalAnnotationTest.java b/src/test/java/org/openrewrite/quarkus/RefactorTemporalAnnotationTest.java index 0e167d1..c7722b2 100644 --- a/src/test/java/org/openrewrite/quarkus/RefactorTemporalAnnotationTest.java +++ b/src/test/java/org/openrewrite/quarkus/RefactorTemporalAnnotationTest.java @@ -1,3 +1,18 @@ +/* + * Copyright 2025 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.quarkus; import org.junit.jupiter.api.Test; From f917e730195ffd13db21dd970670d20ecd665cc5 Mon Sep 17 00:00:00 2001 From: Frederic Mencier Date: Wed, 10 Sep 2025 15:43:03 +0200 Subject: [PATCH 3/8] format source --- .../RefactorTemporalAnnotationTest.java | 420 +++++++++--------- 1 file changed, 210 insertions(+), 210 deletions(-) diff --git a/src/test/java/org/openrewrite/quarkus/RefactorTemporalAnnotationTest.java b/src/test/java/org/openrewrite/quarkus/RefactorTemporalAnnotationTest.java index c7722b2..5012c11 100644 --- a/src/test/java/org/openrewrite/quarkus/RefactorTemporalAnnotationTest.java +++ b/src/test/java/org/openrewrite/quarkus/RefactorTemporalAnnotationTest.java @@ -27,245 +27,245 @@ public class RefactorTemporalAnnotationTest implements RewriteTest { @Override public void defaults(RecipeSpec spec) { spec.recipes(new RefactorTemporalAnnotation(null)) - .parser(JavaParser.fromJavaVersion() - .classpath("jakarta.persistence-api")); + .parser(JavaParser.fromJavaVersion() + .classpath("jakarta.persistence-api")); } @Test void shouldRemoveTemporalAnnotationAndKeepOtherAnnotations() { rewriteRun( - //language=java - java( - """ - package org.refactor.model; - - import jakarta.persistence.Column; - import jakarta.persistence.Entity; - import jakarta.persistence.Id; - import jakarta.persistence.Table; - import jakarta.persistence.Temporal; - - import java.util.Date; - - @Entity - @Table(name = "rent_house") - public class RentHouseEntity { - @Id - @Column(name = "rent_house_id") - private Long id; - - @Column(name = "status") - private String status; - - @Column(name = "start_date") - @Temporal(TemporalType.DATE) - private Date startDate; - - @Column(name = "end_date") - @Temporal(TemporalType.DATE) - private Date endDate; - - @Column(name = "creation_date") - @Temporal(TemporalType.TIMESTAMP) - private Date creationDate; - } - """, - """ - package org.refactor.model; - - import jakarta.persistence.Column; - import jakarta.persistence.Entity; - import jakarta.persistence.Id; - import jakarta.persistence.Table; - - import java.time.LocalDate; - import java.time.LocalDateTime; - - @Entity - @Table(name = "rent_house") - public class RentHouseEntity { - @Id - @Column(name = "rent_house_id") - private Long id; - - @Column(name = "status") - private String status; - - @Column(name = "start_date") - private LocalDate startDate; - - @Column(name = "end_date") - private LocalDate endDate; - - @Column(name = "creation_date") - private LocalDateTime creationDate; - } - """ - ) + //language=java + java( + """ + package org.refactor.model; + + import jakarta.persistence.Column; + import jakarta.persistence.Entity; + import jakarta.persistence.Id; + import jakarta.persistence.Table; + import jakarta.persistence.Temporal; + + import java.util.Date; + + @Entity + @Table(name = "rent_house") + public class RentHouseEntity { + @Id + @Column(name = "rent_house_id") + private Long id; + + @Column(name = "status") + private String status; + + @Column(name = "start_date") + @Temporal(TemporalType.DATE) + private Date startDate; + + @Column(name = "end_date") + @Temporal(TemporalType.DATE) + private Date endDate; + + @Column(name = "creation_date") + @Temporal(TemporalType.TIMESTAMP) + private Date creationDate; + } + """, + """ + package org.refactor.model; + + import jakarta.persistence.Column; + import jakarta.persistence.Entity; + import jakarta.persistence.Id; + import jakarta.persistence.Table; + + import java.time.LocalDate; + import java.time.LocalDateTime; + + @Entity + @Table(name = "rent_house") + public class RentHouseEntity { + @Id + @Column(name = "rent_house_id") + private Long id; + + @Column(name = "status") + private String status; + + @Column(name = "start_date") + private LocalDate startDate; + + @Column(name = "end_date") + private LocalDate endDate; + + @Column(name = "creation_date") + private LocalDateTime creationDate; + } + """ + ) ); } @Test void shouldChangeNothing() { rewriteRun( - //language=java - java( - """ - package org.refactor.model; - - import jakarta.persistence.Entity; - import jakarta.persistence.Table; - import java.util.Date; - - @Entity - @Table(name = "some_entity") - public class SomeEntity { - private Date createdOn; - } - """ - ) + //language=java + java( + """ + package org.refactor.model; + + import jakarta.persistence.Entity; + import jakarta.persistence.Table; + import java.util.Date; + + @Entity + @Table(name = "some_entity") + public class SomeEntity { + private Date createdOn; + } + """ + ) ); } @Test void shouldRemoveTemporalAnnotationAndUseGoodType() { rewriteRun( - //language=java - java( - """ - package org.refactor.model; - - import java.util.Date; - import jakarta.persistence.Temporal; - import jakarta.persistence.TemporalType; - import jakarta.persistence.Entity; - import jakarta.persistence.Table; - - @Entity - @Table(name = "some_entity") - public class MultiTemporalEntity { - @Temporal(TemporalType.DATE) - private Date dateField; - - @Temporal(TemporalType.TIMESTAMP) - private Date timestampField; - - @Temporal(TemporalType.TIME) - private Date timeField; - } - """, - """ - package org.refactor.model; - - import jakarta.persistence.Entity; - import jakarta.persistence.Table; - - import java.time.LocalDate; - import java.time.LocalDateTime; - import java.time.LocalTime; - - @Entity - @Table(name = "some_entity") - public class MultiTemporalEntity { - private LocalDate dateField; - - private LocalDateTime timestampField; - - private LocalTime timeField; - } - """ - ) + //language=java + java( + """ + package org.refactor.model; + + import java.util.Date; + import jakarta.persistence.Temporal; + import jakarta.persistence.TemporalType; + import jakarta.persistence.Entity; + import jakarta.persistence.Table; + + @Entity + @Table(name = "some_entity") + public class MultiTemporalEntity { + @Temporal(TemporalType.DATE) + private Date dateField; + + @Temporal(TemporalType.TIMESTAMP) + private Date timestampField; + + @Temporal(TemporalType.TIME) + private Date timeField; + } + """, + """ + package org.refactor.model; + + import jakarta.persistence.Entity; + import jakarta.persistence.Table; + + import java.time.LocalDate; + import java.time.LocalDateTime; + import java.time.LocalTime; + + @Entity + @Table(name = "some_entity") + public class MultiTemporalEntity { + private LocalDate dateField; + + private LocalDateTime timestampField; + + private LocalTime timeField; + } + """ + ) ); } @Test void shouldRemoveTemporalAnnotationAndUseLocalDateTimeTypeWhenStaticImport() { rewriteRun( - //language=java - java( - """ - package org.refactor.model; - - import java.util.Date; - import static jakarta.persistence.TemporalType.DATE; - import jakarta.persistence.Temporal; - import jakarta.persistence.Entity; - import jakarta.persistence.Table; - - @Entity - @Table(name = "some_entity") - public class SomeEntity { - @Temporal(DATE) - private Date dateField; - } - """, - """ - package org.refactor.model; - - import jakarta.persistence.Entity; - import jakarta.persistence.Table; - - import java.time.LocalDate; - - @Entity - @Table(name = "some_entity") - public class SomeEntity { - private LocalDate dateField; - } - """ - ) + //language=java + java( + """ + package org.refactor.model; + + import java.util.Date; + import static jakarta.persistence.TemporalType.DATE; + import jakarta.persistence.Temporal; + import jakarta.persistence.Entity; + import jakarta.persistence.Table; + + @Entity + @Table(name = "some_entity") + public class SomeEntity { + @Temporal(DATE) + private Date dateField; + } + """, + """ + package org.refactor.model; + + import jakarta.persistence.Entity; + import jakarta.persistence.Table; + + import java.time.LocalDate; + + @Entity + @Table(name = "some_entity") + public class SomeEntity { + private LocalDate dateField; + } + """ + ) ); } @Test void shouldRemoveTemporalAnnotationWithOffsetDateTime() { rewriteRun( - spec -> spec.recipe(new RefactorTemporalAnnotation(true)), - //language=java - java( - """ - package org.refactor.model; - - import java.util.Date; - import jakarta.persistence.Temporal; - import jakarta.persistence.TemporalType; - import jakarta.persistence.Entity; - import jakarta.persistence.Table; - - @Entity - @Table(name = "some_entity") - public class MultiTemporalEntity { - @Temporal(TemporalType.DATE) - private Date dateField; - - @Temporal(TemporalType.TIMESTAMP) - private Date timestampField; - - @Temporal(TemporalType.TIME) - private Date timeField; - } - """, - """ - package org.refactor.model; - - import jakarta.persistence.Entity; - import jakarta.persistence.Table; - - import java.time.LocalDate; - import java.time.LocalTime; - import java.time.OffsetDateTime; - - @Entity - @Table(name = "some_entity") - public class MultiTemporalEntity { - private LocalDate dateField; - - private OffsetDateTime timestampField; - - private LocalTime timeField; - } - """ - ) + spec -> spec.recipe(new RefactorTemporalAnnotation(true)), + //language=java + java( + """ + package org.refactor.model; + + import java.util.Date; + import jakarta.persistence.Temporal; + import jakarta.persistence.TemporalType; + import jakarta.persistence.Entity; + import jakarta.persistence.Table; + + @Entity + @Table(name = "some_entity") + public class MultiTemporalEntity { + @Temporal(TemporalType.DATE) + private Date dateField; + + @Temporal(TemporalType.TIMESTAMP) + private Date timestampField; + + @Temporal(TemporalType.TIME) + private Date timeField; + } + """, + """ + package org.refactor.model; + + import jakarta.persistence.Entity; + import jakarta.persistence.Table; + + import java.time.LocalDate; + import java.time.LocalTime; + import java.time.OffsetDateTime; + + @Entity + @Table(name = "some_entity") + public class MultiTemporalEntity { + private LocalDate dateField; + + private OffsetDateTime timestampField; + + private LocalTime timeField; + } + """ + ) ); } } From d1a4d2db1554728d07abfde07dda4264ebb70869 Mon Sep 17 00:00:00 2001 From: Tim te Beek Date: Wed, 10 Sep 2025 16:12:13 +0200 Subject: [PATCH 4/8] Apply suggestions --- .../org/openrewrite/quarkus/RefactorTemporalAnnotation.java | 6 +++--- .../openrewrite/quarkus/RefactorTemporalAnnotationTest.java | 2 ++ 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/src/main/java/org/openrewrite/quarkus/RefactorTemporalAnnotation.java b/src/main/java/org/openrewrite/quarkus/RefactorTemporalAnnotation.java index a549951..3b0b93d 100644 --- a/src/main/java/org/openrewrite/quarkus/RefactorTemporalAnnotation.java +++ b/src/main/java/org/openrewrite/quarkus/RefactorTemporalAnnotation.java @@ -52,8 +52,8 @@ public String getDisplayName() { @Override public String getDescription() { - return "Replace java.util.Date fields annotated with @Temporal " - + " with java.time.LocalDate, java.time.LocalTime, java.time.LocalDateTime or java.time.OffsetDateTime."; + return "Replace java.util.Date fields annotated with @Temporal " + + "with java.time.LocalDate, java.time.LocalTime, java.time.LocalDateTime or java.time.OffsetDateTime."; } @Option(displayName = "Use offsetDateTime", @@ -81,7 +81,7 @@ public TemporalRefactorVisitor(Boolean useOffsetDateTime) { } @Override - public J.@NotNull VariableDeclarations visitVariableDeclarations(J.@NotNull VariableDeclarations multiVariable, @NotNull ExecutionContext ctx) { + public J. VariableDeclarations visitVariableDeclarations(J. VariableDeclarations multiVariable, ExecutionContext ctx) { J.VariableDeclarations decls = super.visitVariableDeclarations(multiVariable, ctx); if (!DATE_MATCHER.matches(decls.getTypeExpression())) { diff --git a/src/test/java/org/openrewrite/quarkus/RefactorTemporalAnnotationTest.java b/src/test/java/org/openrewrite/quarkus/RefactorTemporalAnnotationTest.java index 5012c11..a3b8d3c 100644 --- a/src/test/java/org/openrewrite/quarkus/RefactorTemporalAnnotationTest.java +++ b/src/test/java/org/openrewrite/quarkus/RefactorTemporalAnnotationTest.java @@ -16,6 +16,7 @@ package org.openrewrite.quarkus; import org.junit.jupiter.api.Test; +import org.openrewrite.DocumentExample; import org.openrewrite.java.JavaParser; import org.openrewrite.test.RecipeSpec; import org.openrewrite.test.RewriteTest; @@ -31,6 +32,7 @@ public void defaults(RecipeSpec spec) { .classpath("jakarta.persistence-api")); } + @DocumentExample @Test void shouldRemoveTemporalAnnotationAndKeepOtherAnnotations() { rewriteRun( From 1b9ef4c1297ed054af019aa1cf3e1128b90fd566 Mon Sep 17 00:00:00 2001 From: Tim te Beek Date: Wed, 10 Sep 2025 16:15:28 +0200 Subject: [PATCH 5/8] Polish --- .../quarkus/RefactorTemporalAnnotation.java | 20 ++++------ .../RefactorTemporalAnnotationTest.java | 38 +++++-------------- 2 files changed, 18 insertions(+), 40 deletions(-) diff --git a/src/main/java/org/openrewrite/quarkus/RefactorTemporalAnnotation.java b/src/main/java/org/openrewrite/quarkus/RefactorTemporalAnnotation.java index 3b0b93d..b0e9041 100644 --- a/src/main/java/org/openrewrite/quarkus/RefactorTemporalAnnotation.java +++ b/src/main/java/org/openrewrite/quarkus/RefactorTemporalAnnotation.java @@ -16,8 +16,8 @@ package org.openrewrite.quarkus; import lombok.EqualsAndHashCode; +import lombok.RequiredArgsConstructor; import lombok.Value; -import org.jetbrains.annotations.NotNull; import org.jspecify.annotations.Nullable; import org.openrewrite.*; import org.openrewrite.java.ChangeType; @@ -66,22 +66,19 @@ public String getDescription() { public TreeVisitor getVisitor() { return Preconditions.check( new UsesType<>(ENTITY_ANNOTATION, true), - new TemporalRefactorVisitor(useOffsetDateTime) + new TemporalRefactorVisitor(Boolean.TRUE.equals(useOffsetDateTime)) ); } + @RequiredArgsConstructor private static class TemporalRefactorVisitor extends JavaIsoVisitor { private static final TypeMatcher DATE_MATCHER = new TypeMatcher(DATE_TYPE); - private final Boolean useOffsetDT; - - public TemporalRefactorVisitor(Boolean useOffsetDateTime) { - useOffsetDT = useOffsetDateTime; - } + private final boolean useOffsetDT; @Override - public J. VariableDeclarations visitVariableDeclarations(J. VariableDeclarations multiVariable, ExecutionContext ctx) { + public J.VariableDeclarations visitVariableDeclarations(J.VariableDeclarations multiVariable, ExecutionContext ctx) { J.VariableDeclarations decls = super.visitVariableDeclarations(multiVariable, ctx); if (!DATE_MATCHER.matches(decls.getTypeExpression())) { @@ -115,21 +112,20 @@ public J. VariableDeclarations visitVariableDeclarations(J. VariableDeclarations } doAfterVisit(new RemoveAnnotation(TEMPORAL_ANNOTATION).getVisitor()); - decls = (J.VariableDeclarations) new ChangeType(DATE_TYPE, newTypeToUse, null).getVisitor().visitNonNull(decls, ctx); maybeRemoveImport(DATE_TYPE); maybeAddImport(newTypeToUse); - return decls; + return (J.VariableDeclarations) new ChangeType(DATE_TYPE, newTypeToUse, null).getVisitor().visitNonNull(decls, ctx); } - private String getNewType(String temporalConstant) { + private @Nullable String getNewType(String temporalConstant) { switch (temporalConstant) { case "DATE": return JAVA_TIME_LOCAL_DATE; case "TIME": return JAVA_TIME_LOCAL_TIME; case "TIMESTAMP": - if (Boolean.TRUE.equals(useOffsetDT)) { + if (useOffsetDT) { return JAVA_TIME_OFFSETDATETIME; } else { return JAVA_TIME_LOCAL_DATE_TIME; diff --git a/src/test/java/org/openrewrite/quarkus/RefactorTemporalAnnotationTest.java b/src/test/java/org/openrewrite/quarkus/RefactorTemporalAnnotationTest.java index a3b8d3c..07cbbc8 100644 --- a/src/test/java/org/openrewrite/quarkus/RefactorTemporalAnnotationTest.java +++ b/src/test/java/org/openrewrite/quarkus/RefactorTemporalAnnotationTest.java @@ -23,7 +23,7 @@ import static org.openrewrite.java.Assertions.java; -public class RefactorTemporalAnnotationTest implements RewriteTest { +class RefactorTemporalAnnotationTest implements RewriteTest { @Override public void defaults(RecipeSpec spec) { @@ -39,8 +39,6 @@ void shouldRemoveTemporalAnnotationAndKeepOtherAnnotations() { //language=java java( """ - package org.refactor.model; - import jakarta.persistence.Column; import jakarta.persistence.Entity; import jakarta.persistence.Id; @@ -51,7 +49,7 @@ void shouldRemoveTemporalAnnotationAndKeepOtherAnnotations() { @Entity @Table(name = "rent_house") - public class RentHouseEntity { + class RentHouseEntity { @Id @Column(name = "rent_house_id") private Long id; @@ -73,8 +71,6 @@ public class RentHouseEntity { } """, """ - package org.refactor.model; - import jakarta.persistence.Column; import jakarta.persistence.Entity; import jakarta.persistence.Id; @@ -85,7 +81,7 @@ public class RentHouseEntity { @Entity @Table(name = "rent_house") - public class RentHouseEntity { + class RentHouseEntity { @Id @Column(name = "rent_house_id") private Long id; @@ -113,15 +109,13 @@ void shouldChangeNothing() { //language=java java( """ - package org.refactor.model; - import jakarta.persistence.Entity; import jakarta.persistence.Table; import java.util.Date; @Entity @Table(name = "some_entity") - public class SomeEntity { + class SomeEntity { private Date createdOn; } """ @@ -135,8 +129,6 @@ void shouldRemoveTemporalAnnotationAndUseGoodType() { //language=java java( """ - package org.refactor.model; - import java.util.Date; import jakarta.persistence.Temporal; import jakarta.persistence.TemporalType; @@ -145,7 +137,7 @@ void shouldRemoveTemporalAnnotationAndUseGoodType() { @Entity @Table(name = "some_entity") - public class MultiTemporalEntity { + class MultiTemporalEntity { @Temporal(TemporalType.DATE) private Date dateField; @@ -157,8 +149,6 @@ public class MultiTemporalEntity { } """, """ - package org.refactor.model; - import jakarta.persistence.Entity; import jakarta.persistence.Table; @@ -168,7 +158,7 @@ public class MultiTemporalEntity { @Entity @Table(name = "some_entity") - public class MultiTemporalEntity { + class MultiTemporalEntity { private LocalDate dateField; private LocalDateTime timestampField; @@ -186,8 +176,6 @@ void shouldRemoveTemporalAnnotationAndUseLocalDateTimeTypeWhenStaticImport() { //language=java java( """ - package org.refactor.model; - import java.util.Date; import static jakarta.persistence.TemporalType.DATE; import jakarta.persistence.Temporal; @@ -196,14 +184,12 @@ void shouldRemoveTemporalAnnotationAndUseLocalDateTimeTypeWhenStaticImport() { @Entity @Table(name = "some_entity") - public class SomeEntity { + class SomeEntity { @Temporal(DATE) private Date dateField; } """, """ - package org.refactor.model; - import jakarta.persistence.Entity; import jakarta.persistence.Table; @@ -211,7 +197,7 @@ public class SomeEntity { @Entity @Table(name = "some_entity") - public class SomeEntity { + class SomeEntity { private LocalDate dateField; } """ @@ -226,8 +212,6 @@ void shouldRemoveTemporalAnnotationWithOffsetDateTime() { //language=java java( """ - package org.refactor.model; - import java.util.Date; import jakarta.persistence.Temporal; import jakarta.persistence.TemporalType; @@ -236,7 +220,7 @@ void shouldRemoveTemporalAnnotationWithOffsetDateTime() { @Entity @Table(name = "some_entity") - public class MultiTemporalEntity { + class MultiTemporalEntity { @Temporal(TemporalType.DATE) private Date dateField; @@ -248,8 +232,6 @@ public class MultiTemporalEntity { } """, """ - package org.refactor.model; - import jakarta.persistence.Entity; import jakarta.persistence.Table; @@ -259,7 +241,7 @@ public class MultiTemporalEntity { @Entity @Table(name = "some_entity") - public class MultiTemporalEntity { + class MultiTemporalEntity { private LocalDate dateField; private OffsetDateTime timestampField; From 9da4fda18f9811141cd6918b484f592bc19f33b7 Mon Sep 17 00:00:00 2001 From: Tim te Beek Date: Wed, 10 Sep 2025 16:21:29 +0200 Subject: [PATCH 6/8] Expand precondition to include all three types needed --- .../quarkus/RefactorTemporalAnnotation.java | 10 +++-- .../RefactorTemporalAnnotationTest.java | 42 +++++++++---------- 2 files changed, 28 insertions(+), 24 deletions(-) diff --git a/src/main/java/org/openrewrite/quarkus/RefactorTemporalAnnotation.java b/src/main/java/org/openrewrite/quarkus/RefactorTemporalAnnotation.java index b0e9041..1356714 100644 --- a/src/main/java/org/openrewrite/quarkus/RefactorTemporalAnnotation.java +++ b/src/main/java/org/openrewrite/quarkus/RefactorTemporalAnnotation.java @@ -45,6 +45,8 @@ public class RefactorTemporalAnnotation extends Recipe { private static final String JAVA_TIME_OFFSETDATETIME = "java.time.OffsetDateTime"; private static final String JAVA_TIME_LOCAL_DATE_TIME = "java.time.LocalDateTime"; + private static final TypeMatcher DATE_MATCHER = new TypeMatcher(DATE_TYPE); + @Override public String getDisplayName() { return "Refactor @Temporal annotation java.util.Date fields to java.time API"; @@ -65,7 +67,11 @@ public String getDescription() { @Override public TreeVisitor getVisitor() { return Preconditions.check( - new UsesType<>(ENTITY_ANNOTATION, true), + Preconditions.and( + new UsesType<>(ENTITY_ANNOTATION, true), + new UsesType<>(DATE_TYPE, true), + new UsesType<>(TEMPORAL_ANNOTATION, true) + ), new TemporalRefactorVisitor(Boolean.TRUE.equals(useOffsetDateTime)) ); } @@ -73,8 +79,6 @@ public TreeVisitor getVisitor() { @RequiredArgsConstructor private static class TemporalRefactorVisitor extends JavaIsoVisitor { - private static final TypeMatcher DATE_MATCHER = new TypeMatcher(DATE_TYPE); - private final boolean useOffsetDT; @Override diff --git a/src/test/java/org/openrewrite/quarkus/RefactorTemporalAnnotationTest.java b/src/test/java/org/openrewrite/quarkus/RefactorTemporalAnnotationTest.java index 07cbbc8..de8d940 100644 --- a/src/test/java/org/openrewrite/quarkus/RefactorTemporalAnnotationTest.java +++ b/src/test/java/org/openrewrite/quarkus/RefactorTemporalAnnotationTest.java @@ -104,27 +104,7 @@ class RentHouseEntity { } @Test - void shouldChangeNothing() { - rewriteRun( - //language=java - java( - """ - import jakarta.persistence.Entity; - import jakarta.persistence.Table; - import java.util.Date; - - @Entity - @Table(name = "some_entity") - class SomeEntity { - private Date createdOn; - } - """ - ) - ); - } - - @Test - void shouldRemoveTemporalAnnotationAndUseGoodType() { + void shouldRemoveTemporalAnnotationAndUseCorrespondingType() { rewriteRun( //language=java java( @@ -252,4 +232,24 @@ class MultiTemporalEntity { ) ); } + + @Test + void noAnnotationNoChange() { + rewriteRun( + //language=java + java( + """ + import jakarta.persistence.Entity; + import jakarta.persistence.Table; + import java.util.Date; + + @Entity + @Table(name = "some_entity") + class SomeEntity { + private Date createdOn; + } + """ + ) + ); + } } From 69a44ebd32852ce5e2c7636487971ac6913bf863 Mon Sep 17 00:00:00 2001 From: Tim te Beek Date: Wed, 10 Sep 2025 16:31:10 +0200 Subject: [PATCH 7/8] Collapse annotation to argument new type wrangling --- .../quarkus/RefactorTemporalAnnotation.java | 17 ++++------------- 1 file changed, 4 insertions(+), 13 deletions(-) diff --git a/src/main/java/org/openrewrite/quarkus/RefactorTemporalAnnotation.java b/src/main/java/org/openrewrite/quarkus/RefactorTemporalAnnotation.java index 1356714..90df342 100644 --- a/src/main/java/org/openrewrite/quarkus/RefactorTemporalAnnotation.java +++ b/src/main/java/org/openrewrite/quarkus/RefactorTemporalAnnotation.java @@ -31,8 +31,6 @@ import org.openrewrite.java.tree.J.FieldAccess; import org.openrewrite.java.tree.J.Identifier; -import java.util.Objects; - @Value @EqualsAndHashCode(callSuper = false) public class RefactorTemporalAnnotation extends Recipe { @@ -89,17 +87,11 @@ public J.VariableDeclarations visitVariableDeclarations(J.VariableDeclarations m return decls; } - Annotation temporalAnnotation = FindAnnotations.find(decls, TEMPORAL_ANNOTATION).stream() - .findFirst() - .orElse(null); - - if (temporalAnnotation == null) { - return decls; - } - - // Extract the enum constant (DATE or TIMESTAMP) - String newTypeToUse = Objects.requireNonNull(temporalAnnotation.getArguments()).stream() + String newTypeToUse = FindAnnotations.find(decls, TEMPORAL_ANNOTATION) + .stream() .findFirst() + .map(Annotation::getArguments) + .map(args -> args.get(0)) .map(arg -> { if (arg instanceof FieldAccess) { return getNewType(((FieldAccess) arg).getSimpleName()); @@ -110,7 +102,6 @@ public J.VariableDeclarations visitVariableDeclarations(J.VariableDeclarations m return null; }) .orElse(null); - if (newTypeToUse == null) { return decls; } From dadbf09e0835e5c29110bd3195d55535c327beb1 Mon Sep 17 00:00:00 2001 From: Tim te Beek Date: Wed, 10 Sep 2025 16:33:48 +0200 Subject: [PATCH 8/8] Polish markdown --- .../quarkus/RefactorTemporalAnnotation.java | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/main/java/org/openrewrite/quarkus/RefactorTemporalAnnotation.java b/src/main/java/org/openrewrite/quarkus/RefactorTemporalAnnotation.java index 90df342..fd8d21a 100644 --- a/src/main/java/org/openrewrite/quarkus/RefactorTemporalAnnotation.java +++ b/src/main/java/org/openrewrite/quarkus/RefactorTemporalAnnotation.java @@ -47,17 +47,17 @@ public class RefactorTemporalAnnotation extends Recipe { @Override public String getDisplayName() { - return "Refactor @Temporal annotation java.util.Date fields to java.time API"; + return "Refactor `@Temporal` annotation `java.util.Date` fields to `java.time` API"; } @Override public String getDescription() { - return "Replace java.util.Date fields annotated with @Temporal " + - "with java.time.LocalDate, java.time.LocalTime, java.time.LocalDateTime or java.time.OffsetDateTime."; + return "Replace `java.util.Date` fields annotated with `@Temporal` " + + "with `java.time.LocalDate`, `java.time.LocalTime`, `java.time.LocalDateTime` or `java.time.OffsetDateTime`."; } @Option(displayName = "Use offsetDateTime", - description = "If `true` the recipe will use OffsetDateTime instead of LocalDateTime. Default `false`.", + description = "If `true` the recipe will use `OffsetDateTime` instead of `LocalDateTime`. Default `false`.", required = false) @Nullable Boolean useOffsetDateTime; @@ -66,8 +66,8 @@ public String getDescription() { public TreeVisitor getVisitor() { return Preconditions.check( Preconditions.and( - new UsesType<>(ENTITY_ANNOTATION, true), new UsesType<>(DATE_TYPE, true), + new UsesType<>(ENTITY_ANNOTATION, true), new UsesType<>(TEMPORAL_ANNOTATION, true) ), new TemporalRefactorVisitor(Boolean.TRUE.equals(useOffsetDateTime))