Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add transfers for using arguments #16

Open
wants to merge 11 commits into
base: main
Choose a base branch
from
Original file line number Diff line number Diff line change
Expand Up @@ -25,13 +25,13 @@
import org.openrewrite.java.service.AnnotationService;
import org.openrewrite.java.tree.J;

import java.util.HashSet;
import java.util.Set;
import java.util.HashMap;
import java.util.Map;

public class RemoveDoublyAnnotatedCodehausAnnotations extends Recipe {

private static final AnnotationMatcher MATCHER_FASTERXML = new AnnotationMatcher("@com.fasterxml.jackson.databind.annotation.JsonSerialize", true);
private static final AnnotationMatcher MATCHER_CODEHAUS = new AnnotationMatcher("@org.codehaus.jackson.map.annotate.JsonSerialize", true);
private static final AnnotationMatcher MATCHER_FASTERXML = new AnnotationMatcher("@com.fasterxml.jackson.databind.annotation.JsonSerialize", true);

@Override
public String getDisplayName() {
Expand All @@ -52,17 +52,18 @@ public TreeVisitor<?, ExecutionContext> getVisitor() {
public J preVisit(@NonNull J tree, ExecutionContext ctx) {
stopAfterPreVisit();

Set<J.Annotation> annotationsToRemove = new FindDoublyAnnotatedVisitor().reduce(tree, new HashSet<>());
AnnotationMatcher matcher = new AnnotationMatcher(
// Map from codehaus -> fasterxml annotation
Map<J.Annotation, J.Annotation> doubleAnnotated = new FindDoublyAnnotatedVisitor().reduce(tree, new HashMap<>());

AnnotationMatcher removeCodehausMatcher = new AnnotationMatcher(
// ignored in practice, as we only match annotations previously found just above
"@org.codehaus.jackson.map.annotate.JsonSerialize", true) {
@Override
public boolean matches(J.Annotation annotation) {
return annotationsToRemove.contains(annotation);
return doubleAnnotated.containsKey(annotation);
}
};

doAfterVisit(new RemoveAnnotationVisitor(matcher));
doAfterVisit(new RemoveAnnotationVisitor(removeCodehausMatcher));
maybeRemoveImport("org.codehaus.jackson.map.annotate.JsonSerialize.Inclusion.*");
maybeRemoveImport("org.codehaus.jackson.map.annotate.JsonSerialize.Typing.*");
doAfterVisit(new ShortenFullyQualifiedTypeReferences().getVisitor());
Expand All @@ -71,15 +72,21 @@ public boolean matches(J.Annotation annotation) {
});
}

private static class FindDoublyAnnotatedVisitor extends JavaIsoVisitor<Set<J.Annotation>> {
static class FindDoublyAnnotatedVisitor extends JavaIsoVisitor<Map<J.Annotation, J.Annotation>> {

@Override
public J.Annotation visitAnnotation(J.Annotation annotation, Set<J.Annotation> doublyAnnotated) {
public J.Annotation visitAnnotation(J.Annotation annotation, Map<J.Annotation, J.Annotation> doublyAnnotated) {
J.Annotation a = super.visitAnnotation(annotation, doublyAnnotated);
if (MATCHER_CODEHAUS.matches(annotation) && service(AnnotationService.class).matches(getCursor().getParentOrThrow(), MATCHER_FASTERXML)) {
doublyAnnotated.add(annotation);
if (MATCHER_CODEHAUS.matches(annotation)) {
// Find sibling fasterXMl annotation
service(AnnotationService.class)
.getAllAnnotations(getCursor().getParentOrThrow())
.stream()
.filter(MATCHER_FASTERXML::matches)
.findFirst()
.ifPresent(fasterxml -> doublyAnnotated.put(annotation, fasterxml));
}
return a;
}
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
/*
* Copyright 2024 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.jackson.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.java.jackson.codehaus;

import lombok.RequiredArgsConstructor;
import org.jspecify.annotations.NonNull;
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.jackson.codehaus.RemoveDoublyAnnotatedCodehausAnnotations.FindDoublyAnnotatedVisitor;
import org.openrewrite.java.search.UsesType;
import org.openrewrite.java.tree.Expression;
import org.openrewrite.java.tree.J;
import org.openrewrite.java.tree.Space;

import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

public class TransferUsingArgumentFromCodehausToFasterXML extends Recipe {
timtebeek marked this conversation as resolved.
Show resolved Hide resolved

@Override
public String getDisplayName() {
return "Transfer using argument from Codehaus to FasterXML";
}

@Override
public String getDescription() {
return "Transfer the using argument from Codehaus to FasterXML if it was not set before. " +
"If the `using` argument was set already, it will not be transferred.";
}


@Override
public TreeVisitor<?, ExecutionContext> getVisitor() {
return Preconditions.check(Preconditions.and(
new UsesType<>("org.codehaus.jackson.map.annotate.JsonSerialize", false),
new UsesType<>("com.fasterxml.jackson.databind.annotation.JsonSerialize", false)),
new JavaIsoVisitor<ExecutionContext>() {
@Override
public J preVisit(@NonNull J tree, ExecutionContext ctx) {
stopAfterPreVisit();

// Map from codehaus -> fasterxml annotation
Map<J.Annotation, J.Annotation> doubleAnnotated = new FindDoublyAnnotatedVisitor().reduce(tree, new HashMap<>());
Map<J.Annotation, Expression> fasterXmlToUsingExpression = mapToArgumentExpression(doubleAnnotated);
doAfterVisit(new TransferUsingVisitor(fasterXmlToUsingExpression));
return tree;
}
});
}

private static Map<J.Annotation, Expression> mapToArgumentExpression(Map<J.Annotation, J.Annotation> doubleAnnotated) {
// Map from fasterxml -> value of "using=..." in codehaus annotation
Map<J.Annotation, Expression> mapToArgument = new HashMap<>();
doubleAnnotated.forEach((key, value) -> {
if (key.getArguments() != null || key.getArguments().isEmpty()) {
key.getArguments().forEach(arg -> {
if (arg instanceof J.Assignment) {
J.Assignment assign = (J.Assignment) arg;
J.Identifier varId = (J.Identifier) assign.getVariable();
if ("using".equals(varId.getSimpleName())) {
mapToArgument.put(value, arg);
}
}
});
}
});
return mapToArgument;
}

@RequiredArgsConstructor
private static class TransferUsingVisitor extends JavaIsoVisitor<ExecutionContext> {

private final Map<J.Annotation, Expression> fasterXmlToUsingExpression;

@Override
public J.Annotation visitAnnotation(J.Annotation annotation, ExecutionContext ctx) {
Expression e = fasterXmlToUsingExpression.get(annotation);
if (e != null) {
List<Expression> arguments = annotation.getArguments();
if (arguments == null || arguments.isEmpty() || arguments.get(0) instanceof J.Empty) {
return annotation.withArguments(Collections.singletonList(e.withPrefix(Space.EMPTY)));
}

boolean notAlreadyUsing = arguments.stream().noneMatch(arg -> {
if (arg instanceof J.Assignment) {
J.Assignment assign = (J.Assignment) arg;
J.Identifier varId = (J.Identifier) assign.getVariable();
return "using".equals(varId.getSimpleName());
}
return false;
});
if (notAlreadyUsing) {
arguments.add(e);
return annotation.withArguments(arguments);
}
}
return annotation;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ description: >-
In Jackson 2, the package and dependency coordinates moved from Codehaus to FasterXML.
recipeList:
- org.openrewrite.java.jackson.codehaus.RemoveDoublyAnnotatedCodehausAnnotations
- org.openrewrite.java.jackson.codehaus.TransferUsingArgumentFromCodehausToFasterXMLTest
- org.openrewrite.java.jackson.CodehausClassesToFasterXML
- org.openrewrite.java.jackson.codehaus.CodehausDependencyToFasterXML:
version: 2.x
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
/*
* Copyright 2024 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.jackson.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.java.jackson.codehaus;

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;

import static org.openrewrite.java.Assertions.java;

@SuppressWarnings("DefaultAnnotationParam")
class TransferUsingArgumentFromCodehausToFasterXMLTest implements RewriteTest {

@Override
public void defaults(RecipeSpec spec) {
spec
.recipe(new TransferUsingArgumentFromCodehausToFasterXML())
.parser(JavaParser.fromJavaVersion().classpath(JavaParser.runtimeClasspath()));
}

@DocumentExample
@Test
void shouldTransferArgument() {
rewriteRun(
//language=java
java(
"""
import org.codehaus.jackson.map.JsonSerializer.None;
import org.codehaus.jackson.map.annotate.JsonSerialize;
import static org.codehaus.jackson.map.annotate.JsonSerialize.Inclusion.NON_NULL;

@JsonSerialize(include = NON_NULL, using = None.class)
@com.fasterxml.jackson.databind.annotation.JsonSerialize
class Test {
@JsonSerialize(include = NON_NULL, using = None.class)
@com.fasterxml.jackson.databind.annotation.JsonSerialize
private String first;
}
""",
"""
import org.codehaus.jackson.map.JsonSerializer.None;
import org.codehaus.jackson.map.annotate.JsonSerialize;
import static org.codehaus.jackson.map.annotate.JsonSerialize.Inclusion.NON_NULL;

@JsonSerialize(include = NON_NULL, using = None.class)
@com.fasterxml.jackson.databind.annotation.JsonSerialize(using = None.class)
class Test {
@JsonSerialize(include = NON_NULL, using = None.class)
@com.fasterxml.jackson.databind.annotation.JsonSerialize(using = None.class)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What I found slightly odd about the "after" usage example here is that we're one-to-one moving the argument over, whereas the using argument here, as well as the contentUsing, keyUsing and nullsUsing sibling only accept a FasterXml JsonSerializer, whereas the moved argument is a Codehaus JsonSerialzer, That means after this recipe the code would not yet compile. Compare the before and after packages:

image image

That means we likely need to expand the converted packages (ideally) and types (for exceptional cases) seen here:

name: org.openrewrite.java.jackson.CodehausClassesToFasterXML
displayName: Migrate classes from Jackson Codehaus (legacy) to Jackson FasterXML
description: >-
In Jackson 2, the package and dependency coordinates moved from Codehaus to FasterXML.
recipeList:
- org.openrewrite.java.jackson.codehaus.JsonIncludeAnnotation
- org.openrewrite.java.ChangeType:
oldFullyQualifiedTypeName: org.codehaus.jackson.map.JsonSerializer
newFullyQualifiedTypeName: com.fasterxml.jackson.databind.JsonSerializer
- org.openrewrite.java.ChangeType:
oldFullyQualifiedTypeName: org.codehaus.jackson.map.annotate.JsonSerialize$Inclusion
newFullyQualifiedTypeName: com.fasterxml.jackson.annotation.JsonInclude$Include
- org.openrewrite.java.ChangeType:
oldFullyQualifiedTypeName: org.codehaus.jackson.map.annotate.JsonSerialize
newFullyQualifiedTypeName: com.fasterxml.jackson.databind.annotation.JsonSerialize
- org.openrewrite.java.ChangeType:
oldFullyQualifiedTypeName: org.codehaus.jackson.map.ObjectMapper
newFullyQualifiedTypeName: com.fasterxml.jackson.databind.ObjectMapper
- org.openrewrite.java.ChangeType:
oldFullyQualifiedTypeName: org.codehaus.jackson.map.SerializationConfig$Feature
newFullyQualifiedTypeName: com.fasterxml.jackson.databind.SerializationFeature
- org.openrewrite.java.ChangeType:
oldFullyQualifiedTypeName: org.codehaus.jackson.map.DeserializationConfig$Feature
newFullyQualifiedTypeName: com.fasterxml.jackson.databind.DeserializationFeature
- org.openrewrite.java.ChangePackage:
oldPackageName: org.codehaus.jackson.annotate
newPackageName: com.fasterxml.jackson.annotation
recursive: true

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pushed up in 736fce0.

private String first;
}
"""
)
);
}

@Test
void doNotOverwriteExistingUsing() {
rewriteRun(
//language=java
java(
"""
import org.codehaus.jackson.map.JsonSerializer.None;
import org.codehaus.jackson.map.annotate.JsonSerialize;
import static org.codehaus.jackson.map.annotate.JsonSerialize.Inclusion.NON_NULL;

@JsonSerialize(include = NON_NULL, using = None.class)
@com.fasterxml.jackson.databind.annotation.JsonSerialize(using = com.fasterxml.jackson.databind.JsonSerializer.None.class)
class Test {
@JsonSerialize(include = NON_NULL, using = None.class)
@com.fasterxml.jackson.databind.annotation.JsonSerialize(using = com.fasterxml.jackson.databind.JsonSerializer.None.class)
private String first;
}
"""
)
);
}
}
Loading