From 6724d68f7891749d1af442e9fac55737ad9ccf68 Mon Sep 17 00:00:00 2001 From: Iuliia Sobolevska Date: Thu, 22 Jun 2023 11:36:23 -0700 Subject: [PATCH 1/8] chore: [issue#4] add recipe for updating JSON type mapping with 'io.hypersistence:hypersistence-utils-hibernate' --- .../MigrateHypersistenceUtils61Types.java | 133 ++++++++++++ .../META-INF/rewrite/hibernate-6.yml | 1 + .../hibernate/MigrateToHibernate61Test.java | 200 ++++++++++++++++++ 3 files changed, 334 insertions(+) create mode 100644 src/main/java/org/openrewrite/hibernate/hibernate60/MigrateHypersistenceUtils61Types.java diff --git a/src/main/java/org/openrewrite/hibernate/hibernate60/MigrateHypersistenceUtils61Types.java b/src/main/java/org/openrewrite/hibernate/hibernate60/MigrateHypersistenceUtils61Types.java new file mode 100644 index 0000000..ae1b260 --- /dev/null +++ b/src/main/java/org/openrewrite/hibernate/hibernate60/MigrateHypersistenceUtils61Types.java @@ -0,0 +1,133 @@ +/* + * Copyright 2023 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.hibernate.hibernate60; + +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.search.FindImports; +import org.openrewrite.java.tree.Expression; +import org.openrewrite.java.tree.J; +import org.openrewrite.java.tree.JavaType; + +import java.time.Duration; +import java.util.List; +import java.util.stream.Collectors; + +@Value +@EqualsAndHashCode(callSuper = true) +public class MigrateHypersistenceUtils61Types extends Recipe { + + @Override + public String getDisplayName() { + return "Migrate io.hypersistence:hypersistence-utils-hibernate Json type"; + } + + @Override + public String getDescription() { + return "When io.hypersistence.utils are being used, " + + "removes @org.hibernate.annotations.TypeDefs annotation as it doesn't exist in Hibernate 6 and updates generic JSON type mapping."; + } + + @Override + public Duration getEstimatedEffortPerOccurrence() { + return Duration.ofMinutes(5); + } + + @Override + public TreeVisitor getVisitor() { + return Preconditions.check(Preconditions.and( + new FindImports("org.hibernate.annotations.Type").getVisitor(), + Preconditions.or( + new FindImports("com.vladmihalcea..*").getVisitor(), + new FindImports("io.hypersistence.utils..*").getVisitor() + )), new MigrateHibernateAnnotationType()); + } + + private static class MigrateHibernateAnnotationType extends JavaIsoVisitor { + private static final String HIBERNATE_ANNOTATIONS_TYPE_DEF_FULLNAME = "org.hibernate.annotations.TypeDef"; + private static final String HIBERNATE_ANNOTATIONS_TYPE_DEFS_FULLNAME = "org.hibernate.annotations.TypeDefs"; + private static final String TYPE_DEFS_ANNOTATION = "TypeDefs"; + private static final String HYPERSISTENCE_UTILS_JSON_TYPE_FULLNAME = "io.hypersistence.utils.hibernate.type.json.JsonType"; + private static final String HYPERSISTENCE_UTILS_JSON_BINARY_TYPE_FULLNAME = "io.hypersistence.utils.hibernate.type.json.JsonBinaryType"; + private static final String HYPERSISTENCE_UTILS_JSON_STRING_TYPE_FULLNAME = "io.hypersistence.utils.hibernate.type.json.JsonStringType"; + private static final String JSON_TYPE_CLASS = "JsonType.class"; + + private static boolean isHibernateTypeAnnotation(J.Annotation annotation) { + return annotation.getAnnotationType() instanceof J.Identifier + && "Type".equals(((J.Identifier) annotation.getAnnotationType()).getSimpleName()) + && annotation.getArguments() != null; + } + + private static boolean isTypeJsonArgument(Expression arg) { + return arg instanceof J.Assignment + && ((J.Assignment) arg).getVariable() instanceof J.Identifier + && ((J.Assignment) arg).getAssignment() instanceof J.Literal + && "type".equals(((J.Identifier) ((J.Assignment) arg).getVariable()).getSimpleName()) + && ((J.Literal) ((J.Assignment) arg).getAssignment()).getValue() != null + && ((J.Literal) ((J.Assignment) arg).getAssignment()).getValue().toString().contains("json"); + } + + @Override + public J.ClassDeclaration visitClassDeclaration(J.ClassDeclaration classDecl, ExecutionContext ctx) { + J.ClassDeclaration c = super.visitClassDeclaration(classDecl, ctx); + + maybeRemoveImport(HIBERNATE_ANNOTATIONS_TYPE_DEF_FULLNAME); + maybeRemoveImport(HIBERNATE_ANNOTATIONS_TYPE_DEFS_FULLNAME); + + List newLeadingAnnotations = classDecl.getLeadingAnnotations().stream() + .filter(annotation -> !((J.Identifier) annotation.getAnnotationType()).getSimpleName().equals(TYPE_DEFS_ANNOTATION)) + .collect(Collectors.toList()); + + return c.withLeadingAnnotations(newLeadingAnnotations); + } + + @Override + public J.Annotation visitAnnotation(J.Annotation annotation, ExecutionContext ctx) { + // check if annotation is @org.hibernate.annotations.Type + if (isHibernateTypeAnnotation(annotation)) { + List arguments = annotation.getArguments(); + J.Annotation newAnnotation = annotation.withArguments(arguments.stream() + .map(arg -> { + if (isTypeJsonArgument(arg)) { + // import is not added when onlyIfReferenced is true because it's only used in annotation and there is no such field + maybeAddImport(HYPERSISTENCE_UTILS_JSON_TYPE_FULLNAME, false); + + return ((J.Assignment) arg) + .withVariable(((J.Identifier) ((J.Assignment) arg).getVariable()).withSimpleName("value")) + .withAssignment(((J.Literal) ((J.Assignment) arg).getAssignment()) + .withType(JavaType.buildType(HYPERSISTENCE_UTILS_JSON_TYPE_FULLNAME)) + .withValue(JSON_TYPE_CLASS) + .withValueSource(JSON_TYPE_CLASS)); + } else { + return arg; + } + }) + .collect(Collectors.toList())); + + maybeRemoveImport(HYPERSISTENCE_UTILS_JSON_BINARY_TYPE_FULLNAME); + maybeRemoveImport(HYPERSISTENCE_UTILS_JSON_STRING_TYPE_FULLNAME); + return newAnnotation; + } + + return super.visitAnnotation(annotation, ctx); + } + } +} diff --git a/src/main/resources/META-INF/rewrite/hibernate-6.yml b/src/main/resources/META-INF/rewrite/hibernate-6.yml index cdd8f92..acd5bbf 100644 --- a/src/main/resources/META-INF/rewrite/hibernate-6.yml +++ b/src/main/resources/META-INF/rewrite/hibernate-6.yml @@ -447,3 +447,4 @@ recipeList: oldPackageName: com.vladmihalcea newPackageName: io.hypersistence.utils recursive: true + - org.openrewrite.hibernate.hibernate60.MigrateHypersistenceUtils61Types diff --git a/src/test/java/org/openrewrite/hibernate/MigrateToHibernate61Test.java b/src/test/java/org/openrewrite/hibernate/MigrateToHibernate61Test.java index 248774f..2aa747e 100644 --- a/src/test/java/org/openrewrite/hibernate/MigrateToHibernate61Test.java +++ b/src/test/java/org/openrewrite/hibernate/MigrateToHibernate61Test.java @@ -19,6 +19,7 @@ import org.openrewrite.config.Environment; import org.openrewrite.test.RecipeSpec; import org.openrewrite.test.RewriteTest; +import org.openrewrite.test.TypeValidation; import java.util.regex.Matcher; import java.util.regex.Pattern; @@ -155,5 +156,204 @@ public class TestApplication { ); } + @Test + void hypersistenceUtilsTypesMigrated() { + rewriteRun( + spec -> spec.typeValidationOptions(TypeValidation.none()), + mavenProject( + "Sample", + //language=xml + pomXml(""" + + + 4.0.0 + com.example + demo + 0.0.1-SNAPSHOT + + + org.hibernate + hibernate-core + 5.6.15.Final + + + com.vladmihalcea + hibernate-types-52 + 2.17.1 + + + + """, spec -> spec.after(actual -> { + Matcher hibernateMatcher = Pattern.compile("(6\\.1\\.\\d+\\.Final)").matcher(actual); + assertTrue(hibernateMatcher.find()); + Matcher hypersistenceMatcher = Pattern.compile("(3\\.5\\.\\d+)").matcher(actual); + assertTrue(hypersistenceMatcher.find()); + return """ + + + 4.0.0 + com.example + demo + 0.0.1-SNAPSHOT + + + org.hibernate.orm + hibernate-core + %s + + + io.hypersistence + hypersistence-utils-hibernate-60 + %s + + + + """.formatted(hibernateMatcher.group(1), hypersistenceMatcher.group(1)); + }) + ), + //language=java + srcMainJava( + java(""" + import com.vladmihalcea.hibernate.type.json.JsonStringType; + import org.hibernate.annotations.Type; + import org.hibernate.annotations.TypeDef; + import org.hibernate.annotations.TypeDefs; + + import javax.persistence.Basic; + import javax.persistence.Column; + import javax.persistence.Entity; + import javax.persistence.Id; + import javax.persistence.Table; + import java.io.Serializable; + import java.util.Map; + + @Entity + @Table(name = "schema_revisions") + @TypeDefs({@TypeDef(name = "json", typeClass = JsonStringType.class)}) + public class SchemaRevisionEntity implements Serializable { + @Id + @Basic(optional = false) + @Column(name = "name", nullable = false, updatable = false) + private String name; + + @Type(type = "json") + @Column(name = "schema_metadata", nullable = false) + private SchemaRevisionMetadataEntity metadata; + + public static class SchemaRevisionMetadataEntity implements Serializable { + private Map userMetadata; + } + } + """, + """ + import io.hypersistence.utils.hibernate.type.json.JsonType; + import org.hibernate.annotations.Type; + + import jakarta.persistence.Basic; + import jakarta.persistence.Column; + import jakarta.persistence.Entity; + import jakarta.persistence.Id; + import jakarta.persistence.Table; + import java.io.Serializable; + import java.util.Map; + + @Entity + @Table(name = "schema_revisions") + public class SchemaRevisionEntity implements Serializable { + @Id + @Basic(optional = false) + @Column(name = "name", nullable = false, updatable = false) + private String name; + + @Type(value = JsonType.class) + @Column(name = "schema_metadata", nullable = false) + private SchemaRevisionMetadataEntity metadata; + + public static class SchemaRevisionMetadataEntity implements Serializable { + private Map userMetadata; + } + } + """ + ) + ) + ) + ); + } + + @Test + void typeDefsAnnotationIsNotRemovedWhenHypersistenceUtilsAreNotBeingUsed() { + rewriteRun( + spec -> spec.typeValidationOptions(TypeValidation.none()), + mavenProject( + "Sample", + //language=java + srcMainJava( + java(""" + import org.hibernate.annotations.Type; + import org.hibernate.annotations.TypeDef; + import org.hibernate.annotations.TypeDefs; + + import javax.persistence.Basic; + import javax.persistence.Column; + import javax.persistence.Entity; + import javax.persistence.Id; + import javax.persistence.Table; + import java.io.Serializable; + import java.util.Map; + + @Entity + @Table(name = "schema_revisions") + @TypeDefs({@TypeDef(name = "json", typeClass = SomeCustomClass.class)}) + public class SchemaRevisionEntity implements Serializable { + @Id + @Basic(optional = false) + @Column(name = "name", nullable = false, updatable = false) + private String name; + @Column(name = "schema_metadata", nullable = false) + private SchemaRevisionMetadataEntity metadata; + + public static class SchemaRevisionMetadataEntity implements Serializable { + private Map userMetadata; + } + } + """, + """ + import org.hibernate.annotations.Type; + import org.hibernate.annotations.TypeDef; + import org.hibernate.annotations.TypeDefs; + + import jakarta.persistence.Basic; + import jakarta.persistence.Column; + import jakarta.persistence.Entity; + import jakarta.persistence.Id; + import jakarta.persistence.Table; + import java.io.Serializable; + import java.util.Map; + + @Entity + @Table(name = "schema_revisions") + @TypeDefs({@TypeDef(name = "json", typeClass = SomeCustomClass.class)}) + public class SchemaRevisionEntity implements Serializable { + @Id + @Basic(optional = false) + @Column(name = "name", nullable = false, updatable = false) + private String name; + + @Column(name = "schema_metadata", nullable = false) + private SchemaRevisionMetadataEntity metadata; + + public static class SchemaRevisionMetadataEntity implements Serializable { + private Map userMetadata; + } + } + """ + ) + ) + ) + ); + } } + From 9703ad0c0c7219f6f1f5baf82b0f1f0c275568a2 Mon Sep 17 00:00:00 2001 From: Tim te Beek Date: Thu, 17 Aug 2023 11:59:02 +0200 Subject: [PATCH 2/8] Switch to UsesType<> and add parser with classpath entry --- .../MigrateHypersistenceUtils61Types.java | 20 ++++++++----------- .../hibernate/MigrateToHibernate61Test.java | 4 +++- 2 files changed, 11 insertions(+), 13 deletions(-) diff --git a/src/main/java/org/openrewrite/hibernate/hibernate60/MigrateHypersistenceUtils61Types.java b/src/main/java/org/openrewrite/hibernate/hibernate60/MigrateHypersistenceUtils61Types.java index ae1b260..cd72135 100644 --- a/src/main/java/org/openrewrite/hibernate/hibernate60/MigrateHypersistenceUtils61Types.java +++ b/src/main/java/org/openrewrite/hibernate/hibernate60/MigrateHypersistenceUtils61Types.java @@ -22,7 +22,7 @@ import org.openrewrite.Recipe; import org.openrewrite.TreeVisitor; import org.openrewrite.java.JavaIsoVisitor; -import org.openrewrite.java.search.FindImports; +import org.openrewrite.java.search.UsesType; import org.openrewrite.java.tree.Expression; import org.openrewrite.java.tree.J; import org.openrewrite.java.tree.JavaType; @@ -46,19 +46,15 @@ public String getDescription() { "removes @org.hibernate.annotations.TypeDefs annotation as it doesn't exist in Hibernate 6 and updates generic JSON type mapping."; } - @Override - public Duration getEstimatedEffortPerOccurrence() { - return Duration.ofMinutes(5); - } - @Override public TreeVisitor getVisitor() { - return Preconditions.check(Preconditions.and( - new FindImports("org.hibernate.annotations.Type").getVisitor(), - Preconditions.or( - new FindImports("com.vladmihalcea..*").getVisitor(), - new FindImports("io.hypersistence.utils..*").getVisitor() - )), new MigrateHibernateAnnotationType()); + return Preconditions.check( + Preconditions.and( + new UsesType<>("org.hibernate.annotations.Type", true), + Preconditions.or( + new UsesType<>("com.vladmihalcea..*", true), + new UsesType<>("io.hypersistence.utils..*", true))), + new MigrateHibernateAnnotationType()); } private static class MigrateHibernateAnnotationType extends JavaIsoVisitor { diff --git a/src/test/java/org/openrewrite/hibernate/MigrateToHibernate61Test.java b/src/test/java/org/openrewrite/hibernate/MigrateToHibernate61Test.java index 2aa747e..094898d 100644 --- a/src/test/java/org/openrewrite/hibernate/MigrateToHibernate61Test.java +++ b/src/test/java/org/openrewrite/hibernate/MigrateToHibernate61Test.java @@ -17,6 +17,7 @@ import org.junit.jupiter.api.Test; import org.openrewrite.config.Environment; +import org.openrewrite.java.JavaParser; import org.openrewrite.test.RecipeSpec; import org.openrewrite.test.RewriteTest; import org.openrewrite.test.TypeValidation; @@ -34,7 +35,8 @@ public void defaults(RecipeSpec spec) { spec.recipe(Environment.builder() .scanRuntimeClasspath("org.openrewrite.hibernate", "org.openrewrite.java.migrate.jakarta") .build() - .activateRecipes("org.openrewrite.hibernate.MigrateToHibernate61")); + .activateRecipes("org.openrewrite.hibernate.MigrateToHibernate61")) + .parser(JavaParser.fromJavaVersion().classpath("hibernate-core")); } @Test From 6f3e07515ab7d520c9f8c35bb0f5d88b7a53b90e Mon Sep 17 00:00:00 2001 From: Tim te Beek Date: Thu, 17 Aug 2023 12:02:14 +0200 Subject: [PATCH 3/8] Add javax persistence dependency; drop TypeValidation.none() --- .../org/openrewrite/hibernate/MigrateToHibernate61Test.java | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/test/java/org/openrewrite/hibernate/MigrateToHibernate61Test.java b/src/test/java/org/openrewrite/hibernate/MigrateToHibernate61Test.java index 094898d..16486da 100644 --- a/src/test/java/org/openrewrite/hibernate/MigrateToHibernate61Test.java +++ b/src/test/java/org/openrewrite/hibernate/MigrateToHibernate61Test.java @@ -36,7 +36,7 @@ public void defaults(RecipeSpec spec) { .scanRuntimeClasspath("org.openrewrite.hibernate", "org.openrewrite.java.migrate.jakarta") .build() .activateRecipes("org.openrewrite.hibernate.MigrateToHibernate61")) - .parser(JavaParser.fromJavaVersion().classpath("hibernate-core")); + .parser(JavaParser.fromJavaVersion().classpath("hibernate-core", "javax.persistence-api")); } @Test @@ -161,7 +161,6 @@ public class TestApplication { @Test void hypersistenceUtilsTypesMigrated() { rewriteRun( - spec -> spec.typeValidationOptions(TypeValidation.none()), mavenProject( "Sample", //language=xml @@ -287,11 +286,11 @@ public static class SchemaRevisionMetadataEntity implements Serializable { @Test void typeDefsAnnotationIsNotRemovedWhenHypersistenceUtilsAreNotBeingUsed() { rewriteRun( - spec -> spec.typeValidationOptions(TypeValidation.none()), mavenProject( "Sample", //language=java srcMainJava( + java("class SomeCustomClass {}"), java(""" import org.hibernate.annotations.Type; import org.hibernate.annotations.TypeDef; From e7c261c673aeed6c8d5ba66e5978e9a558ca10fd Mon Sep 17 00:00:00 2001 From: Tim te Beek Date: Thu, 17 Aug 2023 12:04:22 +0200 Subject: [PATCH 4/8] Add package-info.java --- .../hibernate/hibernate60/package-info.java | 21 +++++++++++++++++++ 1 file changed, 21 insertions(+) create mode 100644 src/main/java/org/openrewrite/hibernate/hibernate60/package-info.java diff --git a/src/main/java/org/openrewrite/hibernate/hibernate60/package-info.java b/src/main/java/org/openrewrite/hibernate/hibernate60/package-info.java new file mode 100644 index 0000000..2434fa4 --- /dev/null +++ b/src/main/java/org/openrewrite/hibernate/hibernate60/package-info.java @@ -0,0 +1,21 @@ +/* + * Copyright 2023 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. + */ +@NonNullApi +@NonNullFields +package org.openrewrite.hibernate.hibernate60; + +import org.openrewrite.internal.lang.NonNullApi; +import org.openrewrite.internal.lang.NonNullFields; \ No newline at end of file From 61294e4fa5ea89187df31e78ac6bae670e7eb1f2 Mon Sep 17 00:00:00 2001 From: Tim te Beek Date: Thu, 17 Aug 2023 12:13:37 +0200 Subject: [PATCH 5/8] Add markdown bacticks to display name and description --- .../hibernate60/MigrateHypersistenceUtils61Types.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/main/java/org/openrewrite/hibernate/hibernate60/MigrateHypersistenceUtils61Types.java b/src/main/java/org/openrewrite/hibernate/hibernate60/MigrateHypersistenceUtils61Types.java index cd72135..a38f533 100644 --- a/src/main/java/org/openrewrite/hibernate/hibernate60/MigrateHypersistenceUtils61Types.java +++ b/src/main/java/org/openrewrite/hibernate/hibernate60/MigrateHypersistenceUtils61Types.java @@ -37,13 +37,13 @@ public class MigrateHypersistenceUtils61Types extends Recipe { @Override public String getDisplayName() { - return "Migrate io.hypersistence:hypersistence-utils-hibernate Json type"; + return "Migrate `io.hypersistence:hypersistence-utils-hibernate` Json type"; } @Override public String getDescription() { - return "When io.hypersistence.utils are being used, " + - "removes @org.hibernate.annotations.TypeDefs annotation as it doesn't exist in Hibernate 6 and updates generic JSON type mapping."; + return "When `io.hypersistence.utils` are being used, " + + "removes `@org.hibernate.annotations.TypeDefs` annotation as it doesn't exist in Hibernate 6 and updates generic JSON type mapping."; } @Override From ee7c2ced2b79bb611a7901a7eedd6a46dbd60f87 Mon Sep 17 00:00:00 2001 From: AlekSimpson Date: Tue, 12 Sep 2023 22:32:00 -0700 Subject: [PATCH 6/8] started on fixes, needs more testing --- .../hibernate/TypeAnnotationParameter.java | 115 ++++++++++++------ 1 file changed, 78 insertions(+), 37 deletions(-) diff --git a/src/main/java/org/openrewrite/hibernate/TypeAnnotationParameter.java b/src/main/java/org/openrewrite/hibernate/TypeAnnotationParameter.java index ce1e546..d31d4b2 100644 --- a/src/main/java/org/openrewrite/hibernate/TypeAnnotationParameter.java +++ b/src/main/java/org/openrewrite/hibernate/TypeAnnotationParameter.java @@ -15,13 +15,15 @@ */ package org.openrewrite.hibernate; +import org.jetbrains.annotations.NotNull; import org.openrewrite.ExecutionContext; import org.openrewrite.Recipe; import org.openrewrite.Tree; import org.openrewrite.TreeVisitor; import org.openrewrite.internal.ListUtils; import org.openrewrite.internal.lang.Nullable; -import org.openrewrite.java.JavaIsoVisitor; +import org.openrewrite.java.AnnotationMatcher; +import org.openrewrite.java.JavaVisitor; import org.openrewrite.java.tree.*; import org.openrewrite.marker.Markers; @@ -30,15 +32,18 @@ public class TypeAnnotationParameter extends Recipe { - private static final String FQN_TYPE_ANNOTATION = "org.hibernate.annotations.Type"; + //private static final String FQN_TYPE_ANNOTATION = "org.hibernate.annotations.Type"; + private static final AnnotationMatcher FQN_TYPE_ANNOTATION = new AnnotationMatcher("@org.hibernate.annotations Type(..)"); + private static final AnnotationMatcher TYPE_DEF_MATCHER = new AnnotationMatcher("@org.hibernate.annotations TypeDef(..)"); + private static final AnnotationMatcher TYPE_DEFS_MATCHER = new AnnotationMatcher("@org.hibernate.annotations TypeDefs(..)"); @Override - public String getDisplayName() { + public @NotNull String getDisplayName() { return "@Type annotation type parameter migration"; } @Override - public String getDescription() { + public @NotNull String getDescription() { return "Hibernate 6.x has 'type' parameter of type String replaced with 'value' of type class."; } @@ -46,46 +51,82 @@ public String getDescription() { public @Nullable Duration getEstimatedEffortPerOccurrence() { return Duration.ofMinutes(1); } + //FQN_TYPE_ANNOTATION.equals(type.getFullyQualifiedName()) @Override - public TreeVisitor getVisitor() { - return new JavaIsoVisitor() { + public @NotNull TreeVisitor getVisitor() { + return new JavaVisitor() { @Override - public J.Annotation visitAnnotation(J.Annotation annotation, ExecutionContext executionContext) { - J.Annotation a = super.visitAnnotation(annotation, executionContext); + public @NotNull J visitAnnotation(J.Annotation annotation, @NotNull ExecutionContext executionContext) { + J.Annotation a = (J.Annotation)super.visitAnnotation(annotation, executionContext); JavaType.FullyQualified type = TypeUtils.asFullyQualified(a.getType()); - if (type != null && FQN_TYPE_ANNOTATION.equals(type.getFullyQualifiedName())) { - final boolean isOnlyParameter = a.getArguments().size() == 1; - a = a.withArguments(ListUtils.map(a.getArguments(), arg -> { - if (arg instanceof J.Assignment) { - J.Assignment assignment = (J.Assignment) arg; - if (assignment.getVariable() instanceof J.Identifier - && "type".equals(((J.Identifier) assignment.getVariable()).getSimpleName()) - && assignment.getAssignment() instanceof J.Literal) { - J.Identifier paramName = (J.Identifier) assignment.getVariable(); - String fqTypeName = (String) ((J.Literal) assignment.getAssignment()).getValue(); - String simpleTypeName = getSimpleName(fqTypeName); - JavaType typeOfNewValue = JavaType.buildType(fqTypeName); - J.FieldAccess fa = new J.FieldAccess( - Tree.randomId(), - isOnlyParameter ? Space.EMPTY : assignment.getAssignment().getPrefix(), - assignment.getAssignment().getMarkers(), - new J.Identifier(Tree.randomId(), Space.EMPTY, Markers.EMPTY, Collections.emptyList(), simpleTypeName, typeOfNewValue, null), - JLeftPadded.build(new J.Identifier(Tree.randomId(), Space.EMPTY, Markers.EMPTY, Collections.emptyList(), "class", null, null)), - JavaType.buildType("java.lang.Class") - ); - maybeAddImport(fqTypeName); - if (isOnlyParameter) { - return fa; - } - return assignment.withVariable(paramName.withSimpleName("value")).withAssignment(fa); - } - } - return arg; - })); + if (type != null) { + if (FQN_TYPE_ANNOTATION.matches(a)) { + System.out.println("found @Type"); + return visitTypeAnnotation(a, executionContext); + }else if (TYPE_DEF_MATCHER.matches(a) || TYPE_DEFS_MATCHER.matches(a)) { + System.out.println("found @TypeDef or @TypeDefs"); + return visitTypeDefAnnotation(a, executionContext); + } } return a; } + + private J visitTypeDefAnnotation(J.Annotation a, ExecutionContext ctx) { + maybeRemoveImport("org.hibernate.annotations.TypeDef"); + maybeRemoveImport("org.hibernate.annotations.TypeDefs"); + return null; + } + + private J visitTypeAnnotation(J.Annotation annotation, ExecutionContext ctx) { + final boolean isOnlyParameter = annotation.getArguments().size() == 1; + J.Annotation a = annotation; + a = a.withArguments(ListUtils.map(a.getArguments(), arg -> { + // check if arg is an assignment + if (!(arg instanceof J.Assignment)) { + return arg; + } + // check if arg meets assignment conditions + J.Assignment assignment = (J.Assignment) arg; + if (checkArgumentAssignmentConditions(assignment)) { + return annotation; + } + + J.Identifier paramName = (J.Identifier) assignment.getVariable(); + String fqTypeName = (String) ((J.Literal) assignment.getAssignment()).getValue(); // json + boolean fqTypeNameIsJson = fqTypeName.equals("json"); + fqTypeName = fqTypeNameIsJson ? "JsonType" : fqTypeName; + String simpleTypeName = getSimpleName(fqTypeName); + JavaType typeOfNewValue = JavaType.buildType(fqTypeName); + + J.FieldAccess fa = buildFieldAccess(isOnlyParameter, assignment, simpleTypeName, typeOfNewValue); + + maybeAddImport(fqTypeName); + if (isOnlyParameter) { + return fa; + } + return assignment.withVariable(paramName.withSimpleName("value")).withAssignment(fa); + })); + + return a; + } + + private J.FieldAccess buildFieldAccess(boolean isOnlyParameter, J.Assignment assignment, String simpleTypeName, JavaType typeOfNewValue) { + return new J.FieldAccess( + Tree.randomId(), + isOnlyParameter ? Space.EMPTY : assignment.getAssignment().getPrefix(), + assignment.getAssignment().getMarkers(), + new J.Identifier(Tree.randomId(), Space.EMPTY, Markers.EMPTY, Collections.emptyList(), simpleTypeName, typeOfNewValue, null), + JLeftPadded.build(new J.Identifier(Tree.randomId(), Space.EMPTY, Markers.EMPTY, Collections.emptyList(), "class", null, null)), + JavaType.buildType("java.lang.Class") + ); + } + + private boolean checkArgumentAssignmentConditions(J.Assignment assignment) { + return !(assignment.getVariable() instanceof J.Identifier + && "type".equals(((J.Identifier) assignment.getVariable()).getSimpleName()) + && assignment.getAssignment() instanceof J.Literal); + } }; } From acbf1e9e91dfe8a68c8f4bf8f516c1369d38eab3 Mon Sep 17 00:00:00 2001 From: AlekSimpson Date: Wed, 13 Sep 2023 18:25:35 -0700 Subject: [PATCH 7/8] first draft --- .../hibernate/TypeAnnotationParameter.java | 174 +++++++++--------- 1 file changed, 86 insertions(+), 88 deletions(-) diff --git a/src/main/java/org/openrewrite/hibernate/TypeAnnotationParameter.java b/src/main/java/org/openrewrite/hibernate/TypeAnnotationParameter.java index d31d4b2..18230cd 100644 --- a/src/main/java/org/openrewrite/hibernate/TypeAnnotationParameter.java +++ b/src/main/java/org/openrewrite/hibernate/TypeAnnotationParameter.java @@ -15,35 +15,32 @@ */ package org.openrewrite.hibernate; -import org.jetbrains.annotations.NotNull; -import org.openrewrite.ExecutionContext; -import org.openrewrite.Recipe; -import org.openrewrite.Tree; -import org.openrewrite.TreeVisitor; +import org.openrewrite.*; import org.openrewrite.internal.ListUtils; import org.openrewrite.internal.lang.Nullable; import org.openrewrite.java.AnnotationMatcher; +import org.openrewrite.java.JavaTemplate; import org.openrewrite.java.JavaVisitor; +import org.openrewrite.java.search.UsesType; import org.openrewrite.java.tree.*; import org.openrewrite.marker.Markers; import java.time.Duration; import java.util.Collections; +@SuppressWarnings({"NullableProblems", "DataFlowIssue"}) public class TypeAnnotationParameter extends Recipe { - - //private static final String FQN_TYPE_ANNOTATION = "org.hibernate.annotations.Type"; - private static final AnnotationMatcher FQN_TYPE_ANNOTATION = new AnnotationMatcher("@org.hibernate.annotations Type(..)"); - private static final AnnotationMatcher TYPE_DEF_MATCHER = new AnnotationMatcher("@org.hibernate.annotations TypeDef(..)"); - private static final AnnotationMatcher TYPE_DEFS_MATCHER = new AnnotationMatcher("@org.hibernate.annotations TypeDefs(..)"); + private static final AnnotationMatcher FQN_TYPE_ANNOTATION = new AnnotationMatcher("@org.hibernate.annotations.Type"); + private static final AnnotationMatcher TYPE_DEF_MATCHER = new AnnotationMatcher("@org.hibernate.annotations.TypeDef"); + private static final AnnotationMatcher TYPE_DEFS_MATCHER = new AnnotationMatcher("@org.hibernate.annotations.TypeDefs"); @Override - public @NotNull String getDisplayName() { + public String getDisplayName() { return "@Type annotation type parameter migration"; } @Override - public @NotNull String getDescription() { + public String getDescription() { return "Hibernate 6.x has 'type' parameter of type String replaced with 'value' of type class."; } @@ -51,91 +48,92 @@ public class TypeAnnotationParameter extends Recipe { public @Nullable Duration getEstimatedEffortPerOccurrence() { return Duration.ofMinutes(1); } - //FQN_TYPE_ANNOTATION.equals(type.getFullyQualifiedName()) @Override - public @NotNull TreeVisitor getVisitor() { - return new JavaVisitor() { - @Override - public @NotNull J visitAnnotation(J.Annotation annotation, @NotNull ExecutionContext executionContext) { - J.Annotation a = (J.Annotation)super.visitAnnotation(annotation, executionContext); - JavaType.FullyQualified type = TypeUtils.asFullyQualified(a.getType()); - if (type != null) { - if (FQN_TYPE_ANNOTATION.matches(a)) { - System.out.println("found @Type"); - return visitTypeAnnotation(a, executionContext); - }else if (TYPE_DEF_MATCHER.matches(a) || TYPE_DEFS_MATCHER.matches(a)) { - System.out.println("found @TypeDef or @TypeDefs"); - return visitTypeDefAnnotation(a, executionContext); - } + public TreeVisitor getVisitor() { + return new TypeAnnotationParameterVisitor(); + } + + private static class TypeAnnotationParameterVisitor extends JavaVisitor { + @Override + public J visitAnnotation(J.Annotation annotation, ExecutionContext executionContext) { + System.out.println("getting here"); + J.Annotation a = (J.Annotation)super.visitAnnotation(annotation, executionContext); + JavaType.FullyQualified type = TypeUtils.asFullyQualified(a.getType()); + if (type != null) { + if (FQN_TYPE_ANNOTATION.matches(a)) { + return visitTypeAnnotation(a); + }else if (TYPE_DEF_MATCHER.matches(a) || TYPE_DEFS_MATCHER.matches(a)) { + // TODO: figure out way to check ahead of time if code contains @Type. If it does, this code shouldn't run + maybeRemoveImport("org.hibernate.annotations.TypeDef"); + maybeRemoveImport("org.hibernate.annotations.TypeDefs"); + maybeRemoveImport("com.vladmihalcea.hibernate.type.json.JsonStringType"); + return null; } - return a; } - private J visitTypeDefAnnotation(J.Annotation a, ExecutionContext ctx) { - maybeRemoveImport("org.hibernate.annotations.TypeDef"); - maybeRemoveImport("org.hibernate.annotations.TypeDefs"); - return null; - } + return a; + } - private J visitTypeAnnotation(J.Annotation annotation, ExecutionContext ctx) { - final boolean isOnlyParameter = annotation.getArguments().size() == 1; - J.Annotation a = annotation; - a = a.withArguments(ListUtils.map(a.getArguments(), arg -> { - // check if arg is an assignment - if (!(arg instanceof J.Assignment)) { - return arg; - } - // check if arg meets assignment conditions - J.Assignment assignment = (J.Assignment) arg; - if (checkArgumentAssignmentConditions(assignment)) { - return annotation; - } - - J.Identifier paramName = (J.Identifier) assignment.getVariable(); - String fqTypeName = (String) ((J.Literal) assignment.getAssignment()).getValue(); // json - boolean fqTypeNameIsJson = fqTypeName.equals("json"); - fqTypeName = fqTypeNameIsJson ? "JsonType" : fqTypeName; - String simpleTypeName = getSimpleName(fqTypeName); - JavaType typeOfNewValue = JavaType.buildType(fqTypeName); - - J.FieldAccess fa = buildFieldAccess(isOnlyParameter, assignment, simpleTypeName, typeOfNewValue); - - maybeAddImport(fqTypeName); - if (isOnlyParameter) { - return fa; - } - return assignment.withVariable(paramName.withSimpleName("value")).withAssignment(fa); - })); - - return a; - } + private J visitTypeAnnotation(J.Annotation a) { + final boolean isOnlyParameter = a.getArguments().size() == 1; + J.Annotation ann = a.withArguments(ListUtils.map(a.getArguments(), arg -> { + // check if arg is an assignment + if (!(arg instanceof J.Assignment)) { + return arg; + } + // check if arg meets assignment conditions + J.Assignment assignment = (J.Assignment) arg; + if (checkArgumentAssignmentConditions(assignment)) { + return arg; + } - private J.FieldAccess buildFieldAccess(boolean isOnlyParameter, J.Assignment assignment, String simpleTypeName, JavaType typeOfNewValue) { - return new J.FieldAccess( - Tree.randomId(), - isOnlyParameter ? Space.EMPTY : assignment.getAssignment().getPrefix(), - assignment.getAssignment().getMarkers(), - new J.Identifier(Tree.randomId(), Space.EMPTY, Markers.EMPTY, Collections.emptyList(), simpleTypeName, typeOfNewValue, null), - JLeftPadded.build(new J.Identifier(Tree.randomId(), Space.EMPTY, Markers.EMPTY, Collections.emptyList(), "class", null, null)), - JavaType.buildType("java.lang.Class") - ); - } + J.Identifier paramName = (J.Identifier) assignment.getVariable(); + String fqTypeName = (String) ((J.Literal) assignment.getAssignment()).getValue(); // json + boolean fqTypeNameIsJson = fqTypeName.equals("json"); + fqTypeName = fqTypeNameIsJson ? "JsonType" : fqTypeName; + String simpleTypeName = getSimpleName(fqTypeName); + JavaType typeOfNewValue = JavaType.buildType(fqTypeName); - private boolean checkArgumentAssignmentConditions(J.Assignment assignment) { - return !(assignment.getVariable() instanceof J.Identifier - && "type".equals(((J.Identifier) assignment.getVariable()).getSimpleName()) - && assignment.getAssignment() instanceof J.Literal); - } - }; - } + J.FieldAccess fa = buildFieldAccess(isOnlyParameter, assignment, simpleTypeName, typeOfNewValue); - private static String getSimpleName(String fqName) { - int idx = fqName.lastIndexOf('.'); - if (idx > 0 && idx < fqName.length() - 1) { - return fqName.substring(idx + 1); + maybeAddImport("io.hypersistence.utils.hibernate.type.json.JsonType"); + // NOTE: seems like we want to keep the 'value = ' before arguments even when its the only argument? Not sure why this conditional is here + //if (isOnlyParameter) { + // return fa; + //} + + return assignment + .withVariable(paramName.withSimpleName("value")) + .withAssignment(fa); + })); + + return ann; + } + + private J.FieldAccess buildFieldAccess(boolean isOnlyParameter, J.Assignment assignment, String simpleTypeName, JavaType typeOfNewValue) { + return new J.FieldAccess( + Tree.randomId(), + isOnlyParameter ? assignment.getAssignment().getPrefix() : Space.EMPTY, + assignment.getAssignment().getMarkers(), + new J.Identifier(Tree.randomId(), Space.EMPTY, Markers.EMPTY, Collections.emptyList(), simpleTypeName, typeOfNewValue, null), + JLeftPadded.build(new J.Identifier(Tree.randomId(), Space.EMPTY, Markers.EMPTY, Collections.emptyList(), "class", null, null)), + JavaType.buildType("java.lang.Class") + ); } - return fqName; - } + private boolean checkArgumentAssignmentConditions(J.Assignment assignment) { + return !(assignment.getVariable() instanceof J.Identifier + && "type".equals(((J.Identifier) assignment.getVariable()).getSimpleName()) + && assignment.getAssignment() instanceof J.Literal); + } + + private static String getSimpleName(String fqName) { + int idx = fqName.lastIndexOf('.'); + if (idx > 0 && idx < fqName.length() - 1) { + return fqName.substring(idx + 1); + } + return fqName; + } + } } From 6bf7c48faac942201a4de0d5ffa677de27aaabb7 Mon Sep 17 00:00:00 2001 From: Tim te Beek Date: Thu, 14 Sep 2023 11:36:45 +0200 Subject: [PATCH 8/8] Apply IntelliJ formatter --- .../hibernate/TypeAnnotationParameter.java | 12 ++++++------ .../MigrateHypersistenceUtils61Types.java | 17 ++++++++--------- .../hibernate/MigrateToHibernate61Test.java | 7 +++---- 3 files changed, 17 insertions(+), 19 deletions(-) diff --git a/src/main/java/org/openrewrite/hibernate/TypeAnnotationParameter.java b/src/main/java/org/openrewrite/hibernate/TypeAnnotationParameter.java index 18230cd..8b6c5b4 100644 --- a/src/main/java/org/openrewrite/hibernate/TypeAnnotationParameter.java +++ b/src/main/java/org/openrewrite/hibernate/TypeAnnotationParameter.java @@ -15,13 +15,14 @@ */ package org.openrewrite.hibernate; -import org.openrewrite.*; +import org.openrewrite.ExecutionContext; +import org.openrewrite.Recipe; +import org.openrewrite.Tree; +import org.openrewrite.TreeVisitor; import org.openrewrite.internal.ListUtils; import org.openrewrite.internal.lang.Nullable; import org.openrewrite.java.AnnotationMatcher; -import org.openrewrite.java.JavaTemplate; import org.openrewrite.java.JavaVisitor; -import org.openrewrite.java.search.UsesType; import org.openrewrite.java.tree.*; import org.openrewrite.marker.Markers; @@ -57,13 +58,12 @@ public TreeVisitor getVisitor() { private static class TypeAnnotationParameterVisitor extends JavaVisitor { @Override public J visitAnnotation(J.Annotation annotation, ExecutionContext executionContext) { - System.out.println("getting here"); - J.Annotation a = (J.Annotation)super.visitAnnotation(annotation, executionContext); + J.Annotation a = (J.Annotation) super.visitAnnotation(annotation, executionContext); JavaType.FullyQualified type = TypeUtils.asFullyQualified(a.getType()); if (type != null) { if (FQN_TYPE_ANNOTATION.matches(a)) { return visitTypeAnnotation(a); - }else if (TYPE_DEF_MATCHER.matches(a) || TYPE_DEFS_MATCHER.matches(a)) { + } else if (TYPE_DEF_MATCHER.matches(a) || TYPE_DEFS_MATCHER.matches(a)) { // TODO: figure out way to check ahead of time if code contains @Type. If it does, this code shouldn't run maybeRemoveImport("org.hibernate.annotations.TypeDef"); maybeRemoveImport("org.hibernate.annotations.TypeDefs"); diff --git a/src/main/java/org/openrewrite/hibernate/hibernate60/MigrateHypersistenceUtils61Types.java b/src/main/java/org/openrewrite/hibernate/hibernate60/MigrateHypersistenceUtils61Types.java index a38f533..6b23877 100644 --- a/src/main/java/org/openrewrite/hibernate/hibernate60/MigrateHypersistenceUtils61Types.java +++ b/src/main/java/org/openrewrite/hibernate/hibernate60/MigrateHypersistenceUtils61Types.java @@ -27,7 +27,6 @@ import org.openrewrite.java.tree.J; import org.openrewrite.java.tree.JavaType; -import java.time.Duration; import java.util.List; import java.util.stream.Collectors; @@ -43,7 +42,7 @@ public String getDisplayName() { @Override public String getDescription() { return "When `io.hypersistence.utils` are being used, " + - "removes `@org.hibernate.annotations.TypeDefs` annotation as it doesn't exist in Hibernate 6 and updates generic JSON type mapping."; + "removes `@org.hibernate.annotations.TypeDefs` annotation as it doesn't exist in Hibernate 6 and updates generic JSON type mapping."; } @Override @@ -68,17 +67,17 @@ private static class MigrateHibernateAnnotationType extends JavaIsoVisitor