Skip to content
Merged
Show file tree
Hide file tree
Changes from 9 commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -92,14 +92,16 @@ recipeDependencies {
parserClasspath("io.micrometer:micrometer-commons:1.11.+")
parserClasspath("io.micrometer:micrometer-core:1.11.+")
parserClasspath("io.micrometer:micrometer-observation:1.11.+")
parserClasspath("io.springfox:springfox-swagger2:3.+")
parserClasspath("io.swagger.core.v3:swagger-models:2.+")

testParserClasspath("com.nimbusds:nimbus-jose-jwt:9.13")
testParserClasspath("io.projectreactor:reactor-core:3.6.3")
testParserClasspath("io.springfox:springfox-core:3.+")
testParserClasspath("io.springfox:springfox-spring-web:3.+")
testParserClasspath("io.springfox:springfox-spi:3.+")
testParserClasspath("io.springfox:springfox-bean-validators:3.+")
testParserClasspath("io.swagger.core.v3:swagger-models:2.+")
testParserClasspath("io.swagger.core.v3:swagger-core:2.+")
testParserClasspath("jakarta.persistence:jakarta.persistence-api:2.2.3")
testParserClasspath("jakarta.validation:jakarta.validation-api:2.0.2")
testParserClasspath("jakarta.validation:jakarta.validation-api:3.0.+")
Expand Down
54 changes: 54 additions & 0 deletions src/main/java/org/openrewrite/java/spring/swagger/RemoveBuild.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
/*
* Copyright 2025 the original author or authors.
* <p>
* Licensed under the Moderne Source Available License (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://docs.moderne.io/licensing/moderne-source-available-license
* <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.spring.swagger;

import org.openrewrite.ExecutionContext;
import org.openrewrite.Preconditions;
import org.openrewrite.Recipe;
import org.openrewrite.TreeVisitor;
import org.openrewrite.java.JavaParser;
import org.openrewrite.java.JavaTemplate;
import org.openrewrite.java.JavaVisitor;
import org.openrewrite.java.MethodMatcher;
import org.openrewrite.java.search.UsesMethod;
import org.openrewrite.java.tree.J;

public class RemoveBuild extends Recipe {
private static final MethodMatcher BUILD_MATCHER = new MethodMatcher("springfox.documentation.builders.ApiInfoBuilder build()");

@Override
public String getDisplayName() {
return "Replace SpringDoc `Contact` with Swagger `Contact`";
}

@Override
public String getDescription() {
return "Replace three argument constructor with immutable builder.";
}

@Override
public TreeVisitor<?, ExecutionContext> getVisitor() {
return Preconditions.check(new UsesMethod<>(BUILD_MATCHER), new JavaVisitor<ExecutionContext>() {
@Override
public J visitMethodInvocation(J.MethodInvocation method, ExecutionContext ctx) {
if (BUILD_MATCHER.matches(method)) {
return method.getSelect().withPrefix(method.getPrefix());
}
return super.visitMethodInvocation(method, ctx);
}
});
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
/*
* Copyright 2025 the original author or authors.
* <p>
* Licensed under the Moderne Source Available License (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://docs.moderne.io/licensing/moderne-source-available-license
* <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.spring.swagger;

import org.jspecify.annotations.Nullable;
import org.openrewrite.ExecutionContext;
import org.openrewrite.Preconditions;
import org.openrewrite.Recipe;
import org.openrewrite.TreeVisitor;
import org.openrewrite.java.JavaParser;
import org.openrewrite.java.JavaTemplate;
import org.openrewrite.java.JavaVisitor;
import org.openrewrite.java.MethodMatcher;
import org.openrewrite.java.search.UsesMethod;
import org.openrewrite.java.tree.Expression;
import org.openrewrite.java.tree.J;
import org.openrewrite.java.tree.JRightPadded;
import org.openrewrite.java.tree.Space;

public class ReplaceContact extends Recipe {
private static final MethodMatcher CONTACT_MATCHER = new MethodMatcher("springfox.documentation.service.Contact <constructor>(String, String, String)");

@Override
public String getDisplayName() {
return "Replace SpringDoc `Contact` with Swagger `Contact`";
}

@Override
public String getDescription() {
return "Replace three argument constructor with immutable builder.";
}

@Override
public TreeVisitor<?, ExecutionContext> getVisitor() {
return Preconditions.check(new UsesMethod<>(CONTACT_MATCHER), new JavaVisitor<ExecutionContext>() {
// Replace `Contact` constructor
@Override
public J visitNewClass(J.NewClass newClass, ExecutionContext ctx) {
if (CONTACT_MATCHER.matches(newClass)) {
maybeRemoveImport("springfox.documentation.service.Contact");
maybeAddImport("io.swagger.v3.oas.models.info.Contact");
return JavaTemplate.builder("new Contact().name(#{any(String)}).url(#{any(String)}).email(#{any(String)})")
.imports("io.swagger.v3.oas.models.info.Contact")
.javaParser(JavaParser.fromJavaVersion().classpathFromResources(ctx, "swagger-models"))
.build()
.apply(getCursor(), newClass.getCoordinates().replace(), newClass.getArguments().toArray());
}
return super.visitNewClass(newClass, ctx);
}
});
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
/*
* Copyright 2025 the original author or authors.
* <p>
* Licensed under the Moderne Source Available License (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://docs.moderne.io/licensing/moderne-source-available-license
* <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.spring.swagger;

import org.jspecify.annotations.Nullable;
import org.openrewrite.ExecutionContext;
import org.openrewrite.Preconditions;
import org.openrewrite.Recipe;
import org.openrewrite.TreeVisitor;
import org.openrewrite.java.JavaParser;
import org.openrewrite.java.JavaTemplate;
import org.openrewrite.java.JavaVisitor;
import org.openrewrite.java.MethodMatcher;
import org.openrewrite.java.search.UsesMethod;
import org.openrewrite.java.tree.Expression;
import org.openrewrite.java.tree.J;
import org.openrewrite.java.tree.JRightPadded;
import org.openrewrite.java.tree.Space;

public class ReplaceLicenseUrl extends Recipe {
private static final MethodMatcher LICENSE_MATCHER = new MethodMatcher("springfox.documentation.builders.ApiInfoBuilder license(String)");
private static final MethodMatcher LICENSEURL_MATCHER = new MethodMatcher("springfox.documentation.builders.ApiInfoBuilder licenseUrl(String)");

@Override
public String getDisplayName() {
return "Transform SpringFox `ApiInfo` to Swagger v3 `Info`";
}

@Override
public String getDescription() {
return "Transforms SpringFox `ApiInfoBuilder` to Swagger v3 `Info` fluent API pattern.";
}

@Override
public TreeVisitor<?, ExecutionContext> getVisitor() {
TreeVisitor<?, ExecutionContext> preconditions = Preconditions.or(new UsesMethod<>(LICENSE_MATCHER), new UsesMethod<>(LICENSEURL_MATCHER));
return Preconditions.check(preconditions, new JavaVisitor<ExecutionContext>() {
@Nullable Expression license;
@Nullable Expression licenseUrl;

@Override
public J visitMethodInvocation(J.MethodInvocation method, ExecutionContext ctx) {
J.MethodInvocation mi = (J.MethodInvocation) super.visitMethodInvocation(method, ctx);
if (LICENSE_MATCHER.matches(mi)) {
license = mi.getArguments().get(0);
} else if (LICENSEURL_MATCHER.matches(mi)) {
licenseUrl = mi.getArguments().get(0);
} else {
return mi;
}

// Combine `license` & `licenseUrl`
if (license != null && licenseUrl != null) {
maybeAddImport("io.swagger.v3.oas.models.info.License");
return JavaTemplate.builder("#{any(io.swagger.v3.oas.models.info.Info)}\n.license(new License().name(#{any(String)}).url(#{any(String)}))")
.imports("io.swagger.v3.oas.models.info.License")
.javaParser(JavaParser.fromJavaVersion().classpathFromResources(ctx, "swagger-models"))
.build()
.apply(getCursor(), mi.getCoordinates().replace(), mi.getSelect(), license, licenseUrl);
}

// Remove the method itself already
return mi.getSelect();
}
});
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,154 @@
/*
* Copyright 2025 the original author or authors.
* <p>
* Licensed under the Moderne Source Available License (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://docs.moderne.io/licensing/moderne-source-available-license
* <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.spring.swagger;

import org.openrewrite.ExecutionContext;
import org.openrewrite.Preconditions;
import org.openrewrite.Recipe;
import org.openrewrite.TreeVisitor;
import org.openrewrite.java.ChangeType;
import org.openrewrite.java.JavaIsoVisitor;
import org.openrewrite.java.JavaTemplate;
import org.openrewrite.java.MethodMatcher;
import org.openrewrite.java.search.UsesType;
import org.openrewrite.java.tree.J;
import org.openrewrite.java.tree.TypeUtils;

import java.util.HashMap;
import java.util.Map;
import java.util.function.BiFunction;
import java.util.function.Function;

import static java.util.stream.Collectors.joining;

public class TransformApiInfo extends Recipe {

@Override
public String getDisplayName() {
return "Transform SpringFox `ApiInfo` to Swagger v3 `Info`";
}

@Override
public String getDescription() {
return "Transforms SpringFox `ApiInfoBuilder` to Swagger v3 `Info` fluent API pattern.";
}

@Override
public TreeVisitor<?, ExecutionContext> getVisitor() {
return Preconditions.check(
new UsesType<>("springfox.documentation.builders.ApiInfoBuilder", true),
new TransformApiInfoVisitor()
);
}

private static class TransformApiInfoVisitor extends JavaIsoVisitor<ExecutionContext> {
private final MethodMatcher apiInfoBuilderConstructor = new MethodMatcher("springfox.documentation.builders.ApiInfoBuilder <constructor>()");

// accumulator for AppInfoBuilder methods
private static class Accumulator {
private final Map<String, Function<J.MethodInvocation, String>> transformations = new HashMap<>();
private final Map<String, J.MethodInvocation> methodInvocations = new HashMap<>();
private final BiFunction<J.MethodInvocation, J.MethodInvocation, String> licenseTransformer =
(name, url) -> {
if (name == null && url == null) {
return "";
}
StringBuilder sb = new StringBuilder(".license(new License()");
if (name != null) {
sb.append(".name(\"").append(name.getArguments().get(0)).append("\")");
}
if (url != null) {
sb.append(".url(\"").append(url.getArguments().get(0)).append("\")");
}
sb.append(')');
return sb.toString();
};

private Accumulator() {
transformations.put("termsOfServiceUrl", mi -> String.format("termsOfService(\"%s\")", mi.getArguments().get(0)));
transformations.put("license", mi -> String.format("name(\"%s\")", mi.getArguments().get(0)));
transformations.put("licenseUrl", mi -> String.format("url(\"%s\")", mi.getArguments().get(0)));
transformations.put("contact", mi -> {
J.NewClass c = (J.NewClass) mi.getArguments().get(0);
return String.format("contact(new Contact().name(\"%s\").url(\"%s\").email(\"%s\"))", c.getArguments().get(0), c.getArguments().get(1), c.getArguments().get(2));
});
}

public void add(J.MethodInvocation mi) {
if (!"build".equals(mi.getSimpleName())) {
methodInvocations.put(mi.getSimpleName(), mi);
}
}

public String toTemplate() {

// special case for license and licenseUrl
J.MethodInvocation licenseMi = methodInvocations.remove("license");
J.MethodInvocation licenseUrlMi = methodInvocations.remove("licenseUrl");
String licenseFormat = licenseTransformer.apply(licenseMi, licenseUrlMi);

String template = methodInvocations.entrySet().stream()
.map(e -> transformations.getOrDefault(e.getKey(),
m -> String.format("%s(\"%s\")",m.getSimpleName(),m.getArguments().get(0))).apply(e.getValue()))
.collect(joining("."));

return licenseFormat.isEmpty() ? template : template.concat(licenseFormat);
}
}
Accumulator accumulator = new Accumulator();

@Override
public J.MethodInvocation visitMethodInvocation(J.MethodInvocation method, ExecutionContext ctx) {
J.MethodInvocation m = super.visitMethodInvocation(method, ctx);
accumulator.add(m);
return m;
}

@Override
public J.NewClass visitNewClass(J.NewClass newClass, ExecutionContext ctx) {
J.NewClass n = super.visitNewClass(newClass, ctx);

if (apiInfoBuilderConstructor.matches(n)) {
maybeAddImport("io.swagger.v3.oas.models.info.Info");
maybeAddImport("io.swagger.v3.oas.models.info.License");
maybeAddImport("io.swagger.v3.oas.models.info.Contact");
maybeRemoveImport("springfox.documentation.builders.ApiInfoBuilder");
return JavaTemplate.builder("new Info()")
.imports("io.swagger.v3.oas.models.info.Info")
.imports("io.swagger.v3.oas.models.info.License")
.imports("io.swagger.v3.oas.models.info.Contact")
.build()
.apply(getCursor(), n.getCoordinates().replace());
}

return n;
}

@Override
public J.MethodDeclaration visitMethodDeclaration(J.MethodDeclaration method, ExecutionContext ctx) {
J.MethodDeclaration md = super.visitMethodDeclaration(method, ctx);

// Transform return type from ApiInfo to Info
if (TypeUtils.isOfClassType(md.getType(), "springfox.documentation.service.ApiInfo")) {
// doAfterVisit here...
ChangeType changeType = new ChangeType("springfox.documentation.service.ApiInfo", "io.swagger.v3.oas.models.info.Info", true);
return (J.MethodDeclaration) changeType.getVisitor().visitNonNull(md, ctx);
}

return md;
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
/*
* Copyright 2025 the original author or authors.
* <p>
* Licensed under the Moderne Source Available License (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://docs.moderne.io/licensing/moderne-source-available-license
* <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.
*/
@NullMarked @NonNullFields
package org.openrewrite.java.spring.swagger;

import org.jspecify.annotations.NullMarked;
import org.openrewrite.internal.lang.NonNullFields;
Binary file modified src/main/resources/META-INF/rewrite/classpath.tsv.gz
Binary file not shown.
Loading
Loading