Skip to content
Merged
Show file tree
Hide file tree
Changes from 23 commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
7bb9db8
Add WIP
Fossur Mar 31, 2025
73e9efc
Add licences
Fossur Mar 31, 2025
a950992
Clean up recipe naming, Update inherited recipes to visitor style inh…
Fossur Mar 31, 2025
dd8f3ac
Add usages to yaml
Fossur Mar 31, 2025
d946fc0
Fix tests to not use full runtime classpath
Fossur Apr 1, 2025
c85975e
Fix examples and descriptions
Fossur Apr 1, 2025
40eb375
Apply suggestions from code review
timtebeek Apr 1, 2025
255b8c7
Apply formatter on tests
timtebeek Apr 7, 2025
a46e244
Apply formatter on tests
timtebeek Apr 7, 2025
b30d0ab
Restore package declarations
timtebeek Apr 7, 2025
eeb4d31
Fix extended visitors to inline
Fossur Apr 8, 2025
0a790c1
Merge branch 'superclass-modifying-recipes' of https://github.com/Fos…
Fossur Apr 8, 2025
a46101e
Remove method stub creator
Fossur Apr 14, 2025
4de524e
Update tests
Fossur Apr 14, 2025
9e21ec8
Merge branch 'main' into superclass-modifying-recipes
timtebeek Apr 18, 2025
6f2b944
Apply suggestions from code review
timtebeek Apr 18, 2025
92ae561
Merge branch 'main' into superclass-modifying-recipes
timtebeek Apr 28, 2025
08fb545
Polish `RemoveUnnecessarySuperCalls`
timtebeek Apr 28, 2025
b4fa74e
Polish `RemoveSuperTypeVisitor`
timtebeek Apr 28, 2025
294e6ad
Place the documentation examples first
timtebeek Apr 28, 2025
b139441
Remove `RemoveSuperTypeByPackage` as it's likely to make unchecked ch…
timtebeek Apr 28, 2025
fb67197
No need to handle interfaces just yet
timtebeek Apr 28, 2025
8f8e060
Trim down `RemoveUnnecessaryOverride`
timtebeek Apr 28, 2025
9ae951b
Use `RemoveAnnotationVisitor` to not change formatting
timtebeek Apr 28, 2025
128758b
Comment out `ee.taltech.example.AbstractJpaDAO` reference for now
timtebeek Apr 28, 2025
dac7cbf
Polish `ChangeSuperType`
timtebeek Apr 28, 2025
86502ca
Update examples.yml
timtebeek Apr 28, 2025
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
10 changes: 7 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
<p align="center">
<p align="center">

<a href="https://docs.openrewrite.org">
<picture>
<source media="(prefers-color-scheme: dark)" srcset="https://github.com/openrewrite/rewrite/raw/main/doc/logo-oss-dark.svg">
Expand All @@ -23,10 +24,13 @@

### What is this?

This project implements a [Rewrite module](https://github.com/openrewrite/rewrite) that applies best practices and migrations for those projects using the [Dropwizard framework](https://dropwizard.io/).
This project implements a [Rewrite module](https://github.com/openrewrite/rewrite) that applies best practices and
migrations for those projects using the [Dropwizard framework](https://dropwizard.io/).

Browse [a selection of recipes available through this module in the recipe catalog](https://docs.openrewrite.org/recipes/java/dropwizard).

## Contributing

We appreciate all types of contributions. See the [contributing guide](https://github.com/openrewrite/.github/blob/main/CONTRIBUTING.md) for detailed instructions on how to get started.
We appreciate all types of contributions. See
the [contributing guide](https://github.com/openrewrite/.github/blob/main/CONTRIBUTING.md) for detailed instructions on
how to get started.
2 changes: 2 additions & 0 deletions build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -26,9 +26,11 @@ dependencies {
testImplementation("org.openrewrite:rewrite-test")

testRuntimeOnly("io.dropwizard.metrics:metrics-annotation:4.1.+")
testRuntimeOnly("io.dropwizard.metrics:metrics-healthchecks:4.1.+")
testRuntimeOnly("org.springframework.boot:spring-boot-starter-actuator:2.5.+")
testRuntimeOnly("javax.persistence:javax.persistence-api:2.2")
testRuntimeOnly("org.projectlombok:lombok:1.18.+")
testRuntimeOnly("net.sourceforge.argparse4j:argparse4j:0.9.0")
}

recipeDependencies {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,164 @@
/*
* Copyright 2025 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.java.dropwizard.method;


import lombok.EqualsAndHashCode;
import lombok.Value;
import org.openrewrite.ExecutionContext;
import org.openrewrite.Option;
import org.openrewrite.Recipe;
import org.openrewrite.TreeVisitor;
import org.openrewrite.java.JavaIsoVisitor;
import org.openrewrite.java.JavaParser;
import org.openrewrite.java.JavaTemplate;
import org.openrewrite.java.service.ImportService;
import org.openrewrite.java.tree.Expression;
import org.openrewrite.java.tree.J;
import org.openrewrite.java.tree.JavaType;
import org.openrewrite.java.tree.TypeTree;

import static java.lang.Boolean.TRUE;
import static org.openrewrite.java.tree.TypeUtils.asFullyQualified;
import static org.openrewrite.java.tree.TypeUtils.isOfClassType;

@Value
@EqualsAndHashCode(callSuper = false)
public class ChangeSuperType extends Recipe {

@Option(displayName = "Target class",
description = "The fully qualified name of the class whose superclass should be changed.",
example = "com.myorg.MyClass")
String targetClass;

@Option(displayName = "New superclass",
description = "The fully qualified name of the new superclass to extend or interface to implement.",
example = "com.myorg.NewSuperclass")
String newSuperclass;

@Option(displayName = "Keep type parameters",
description = "Whether to keep existing type parameters on the target class declaration.",
required = false)
Boolean keepTypeParameters;

@Option(displayName = "Convert to interface",
description = "If the new supertype is an interface, setting this to true converts 'extends' to 'implements'.",
required = false)
Boolean convertToInterface;

@Option(displayName = "Remove unnecessary overrides",
description = "Remove method Override annotations that override methods from the *old* superclass but are no longer necessary with the new superclass.",
required = false)
Boolean removeUnnecessaryOverrides;

@Override
public String getDisplayName() {
return "Change superclass";
}

@Override
public String getDescription() {
return "Changes the superclass of a specified class to a new superclass.";
}

@Override
public TreeVisitor<?, ExecutionContext> getVisitor() {
return new JavaIsoVisitor<ExecutionContext>() {
@Override
public J.ClassDeclaration visitClassDeclaration(
J.ClassDeclaration classDecl, ExecutionContext ctx) {
J.ClassDeclaration cd = super.visitClassDeclaration(classDecl, ctx);

if (cd.getExtends() == null || !isOfClassType(cd.getExtends().getType(), targetClass)) {
return cd;
}

String typeParams = getTypeParams(cd.getExtends());

maybeAddImport(newSuperclass);
maybeRemoveImport(targetClass);

JavaTemplate extendsTemplate =
JavaTemplate.builder("#{}" + typeParams)
.javaParser(JavaParser.fromJavaVersion().classpath(JavaParser.runtimeClasspath()))
.imports(newSuperclass)
.contextSensitive()
.build();

JavaType.FullyQualified newSuperType;

if (TRUE.equals(convertToInterface)) {
cd =
extendsTemplate.apply(
updateCursor(cd), cd.getCoordinates().addImplementsClause(), newSuperclass);
cd = cd.withExtends(null);
TypeTree lastInterface = cd.getImplements().get(cd.getImplements().size() - 1);
newSuperType = asFullyQualified(lastInterface.getType());
} else {
cd =
extendsTemplate.apply(
updateCursor(cd), cd.getCoordinates().replaceExtendsClause(), newSuperclass);
newSuperType = asFullyQualified(cd.getExtends().getType());
}

// If changing to a class that was not compiled initially (eg. from a new library)
if (newSuperType == null) {
newSuperType = asFullyQualified(JavaType.buildType(newSuperclass));
}

cd = cd.withType(((JavaType.Class) cd.getType()).withSupertype(newSuperType));

doAfterVisit(new UpdateMethodTypesVisitor(cd.getType()));

if (TRUE.equals(removeUnnecessaryOverrides)) {
doAfterVisit(new RemoveUnnecessaryOverride(false).getVisitor());
}

doAfterVisit(new RemoveUnnecessarySuperCalls.RemoveUnnecessarySuperCallsVisitor());
doAfterVisit(service(ImportService.class).shortenFullyQualifiedTypeReferencesIn(cd));

return cd;
}

private String getTypeParams(TypeTree extendsType) {
StringBuilder typeParams = new StringBuilder();

if (TRUE.equals(keepTypeParameters) && extendsType instanceof J.ParameterizedType) {
J.ParameterizedType parameterizedType = (J.ParameterizedType) extendsType;

boolean hasParameters = false;
typeParams.append('<');

for (Expression typeParameter : parameterizedType.getTypeParameters()) {
if (hasParameters) {
typeParams.append(", ");
}
typeParams.append(typeParameter.toString());
hasParameters = true;
}

if (hasParameters) {
typeParams.append('>');
} else {
typeParams.setLength(0);
}
}

return typeParams.toString();
}
};
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
/*
* Copyright 2025 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.java.dropwizard.method;

import lombok.EqualsAndHashCode;
import lombok.Value;
import org.openrewrite.*;
import org.openrewrite.java.JavaIsoVisitor;
import org.openrewrite.java.search.UsesType;
import org.openrewrite.java.tree.J;
import org.openrewrite.java.tree.JavaType;

import static org.openrewrite.java.tree.TypeUtils.isOfClassType;

@Value
@EqualsAndHashCode(callSuper = false)
public class RemoveSuperTypeByType extends Recipe {

@Option(displayName = "Fully qualified name of the superclass to remove",
description = "Supertypes that match this name are to be removed",
example = "io.dropwizard.Configuration")
String typeToRemove;

@Override
public String getDisplayName() {
return "Remove supertype by fully qualified name matches";
}

@Override
public String getDescription() {
return "Removes a specified type from class extends or implements clauses.";
}

@Override
public TreeVisitor<?, ExecutionContext> getVisitor() {
return Preconditions.check(
new UsesType<>(typeToRemove, false),
new JavaIsoVisitor<ExecutionContext>() {
@Override
public J.ClassDeclaration visitClassDeclaration(J.ClassDeclaration classDecl, ExecutionContext ctx) {
J.ClassDeclaration cd = super.visitClassDeclaration(classDecl, ctx);

if (cd.getExtends() != null && isOfClassType(cd.getExtends().getType(), typeToRemove)) {
cd = cd.withExtends(null);
JavaType.ShallowClass type = (JavaType.ShallowClass) JavaType.buildType("java.lang.Object");
doAfterVisit(new UpdateMethodTypesVisitor(type));
doAfterVisit(new RemoveUnnecessarySuperCalls.RemoveUnnecessarySuperCallsVisitor());
}

return cd;
}

}
);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
/*
* Copyright 2025 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.java.dropwizard.method;

import lombok.EqualsAndHashCode;
import lombok.Value;
import org.jspecify.annotations.Nullable;
import org.openrewrite.*;
import org.openrewrite.java.AnnotationMatcher;
import org.openrewrite.java.JavaIsoVisitor;
import org.openrewrite.java.service.AnnotationService;
import org.openrewrite.java.tree.J;
import org.openrewrite.java.tree.TypeUtils;

import java.util.ArrayList;
import java.util.List;

@Value
@EqualsAndHashCode(callSuper = false)
public class RemoveUnnecessaryOverride extends Recipe {

private static final AnnotationMatcher OVERRIDE_ANNOTATION = new AnnotationMatcher("@java.lang.Override");

@Option(
displayName = "Ignore methods in anonymous classes",
description = "When enabled, ignore @Override annotations on methods in anonymous classes.",
required = false)
@Nullable
Boolean ignoreAnonymousClassMethods;

@Override
public String getDisplayName() {
return "Remove unnecessary `@Override` annotations";
}

@Override
public String getDescription() {
return "Removes `@Override` annotations from methods that don't actually override or implement any method. " +
"This helps maintain clean code by removing incorrect annotations that could be misleading.";
}

@Override
public TreeVisitor<?, ExecutionContext> getVisitor() {
return new JavaIsoVisitor<ExecutionContext>() {
private Cursor getCursorToParentScope(Cursor cursor) {
return cursor.dropParentUntil(is -> is instanceof J.NewClass || is instanceof J.ClassDeclaration);
}

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

if (!m.isConstructor() &&
service(AnnotationService.class).matches(getCursor(), OVERRIDE_ANNOTATION) &&
!TypeUtils.isOverride(m.getMethodType()) &&
!(Boolean.TRUE.equals(ignoreAnonymousClassMethods) &&
getCursorToParentScope(getCursor()).getValue() instanceof J.NewClass)) {

// Find and remove the @Override annotation
List<J.Annotation> annotations = new ArrayList<>(m.getLeadingAnnotations());
annotations.removeIf(OVERRIDE_ANNOTATION::matches);

return maybeAutoFormat(method, m.withLeadingAnnotations(annotations), ctx);
}

return m;
}
};
}
}
Loading
Loading