Skip to content
131 changes: 85 additions & 46 deletions src/main/java/org/openrewrite/hibernate/TypeAnnotationParameter.java
Original file line number Diff line number Diff line change
Expand Up @@ -21,16 +21,19 @@
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;

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");

@Override
public String getDisplayName() {
Expand All @@ -49,52 +52,88 @@ public String getDescription() {

@Override
public TreeVisitor<?, ExecutionContext> getVisitor() {
return new JavaIsoVisitor<ExecutionContext>() {
@Override
public J.Annotation visitAnnotation(J.Annotation annotation, ExecutionContext executionContext) {
J.Annotation a = 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;
}));
return new TypeAnnotationParameterVisitor();
}

private static class TypeAnnotationParameterVisitor extends JavaVisitor<ExecutionContext> {
@Override
public J visitAnnotation(J.Annotation annotation, 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)) {
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 static String getSimpleName(String fqName) {
int idx = fqName.lastIndexOf('.');
if (idx > 0 && idx < fqName.length() - 1) {
return fqName.substring(idx + 1);
return a;
}
return fqName;
}

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;
}

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("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")
);
}

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;
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
/*
* Copyright 2023 the original author or authors.
* <p>
* 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
* <p>
* https://www.apache.org/licenses/LICENSE-2.0
* <p>
* 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.UsesType;
import org.openrewrite.java.tree.Expression;
import org.openrewrite.java.tree.J;
import org.openrewrite.java.tree.JavaType;

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 TreeVisitor<?, ExecutionContext> getVisitor() {
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<ExecutionContext> {
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<J.Annotation> 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<Expression> 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);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
/*
* Copyright 2023 the original author or authors.
* <p>
* 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
* <p>
* https://www.apache.org/licenses/LICENSE-2.0
* <p>
* 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;
1 change: 1 addition & 0 deletions src/main/resources/META-INF/rewrite/hibernate-6.yml
Original file line number Diff line number Diff line change
Expand Up @@ -448,3 +448,4 @@ recipeList:
oldPackageName: com.vladmihalcea
newPackageName: io.hypersistence.utils
recursive: true
- org.openrewrite.hibernate.hibernate60.MigrateHypersistenceUtils61Types
Loading