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

Feature/3156 lombok utility class #3306

Closed
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
f23f730
Intermediate commit
lukas-poos-openvalue Jun 9, 2023
ce62e60
#3156 = add some tests
coiouhkc Jun 9, 2023
40df947
#3156: Add JavaParser to JavaTemplate
lukas-poos-openvalue Jun 9, 2023
0f60f44
Merge remote-tracking branch 'alexei-fork/feature/3156_Lombok_Utility…
lukas-poos-openvalue Jun 9, 2023
09806c2
#3156: Remove dependency to rewrite-maven
lukas-poos-openvalue Jun 9, 2023
f063d99
#3156 = correct tests, fix logic
coiouhkc Jun 9, 2023
c73542c
#3156 = fix implementation of happy path, add failing tests
coiouhkc Jun 10, 2023
1e9539e
#3156 = add/refine tests related to non-public, inner and nested classes
coiouhkc Jun 10, 2023
9843177
#3156: Simplify check visitor calls
lukas-poos-openvalue Jun 11, 2023
3d9c9e6
#3156 = wip
coiouhkc Jun 11, 2023
e9e61a1
#3156 = add multi-variable test
coiouhkc Jun 11, 2023
10b5c96
#3156: Update static class members correctly
lukas-poos-openvalue Jun 11, 2023
bb366ab
#3156 = fix non-final fields, mark progress
coiouhkc Jun 11, 2023
25c9dda
#3156: Organize test cases
lukas-poos-openvalue Jun 11, 2023
6d87f63
#3156 = add additional suggestion for a test case
coiouhkc Jun 12, 2023
2a27d55
#3156 = add test and exclusion for main method
coiouhkc Jun 17, 2023
28581b0
#3156 = add license
coiouhkc Jun 17, 2023
acb5cf5
#3156 = do not transform abstract
coiouhkc Jun 17, 2023
722f905
#3156 = do not transform interfaces
coiouhkc Jun 17, 2023
270b43d
#3156 = add failing test
coiouhkc Jun 17, 2023
66e9dbf
#3156 = remove todo
coiouhkc Jun 17, 2023
15fe027
Merge branch 'main' into feature/3156_Lombok_UtilityClass
timtebeek Jun 30, 2023
f1fcd25
Merge branch 'main' into feature/3156_Lombok_UtilityClass
timtebeek Nov 22, 2023
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
Original file line number Diff line number Diff line change
@@ -0,0 +1,185 @@
/*
* 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.java;
Copy link
Collaborator

@JLLeitschuh JLLeitschuh Jan 25, 2024

Choose a reason for hiding this comment

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

Suggested change
package org.openrewrite.java;
package org.openrewrite.java.lombok;

Put this in a lombok sub package? Maybe? @timtebeek thoughts?

Copy link
Contributor

Choose a reason for hiding this comment

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

It might fit in best with https://github.com/openrewrite/rewrite-migrate-java/tree/main/src/main/java/org/openrewrite/java/migrate/lombok , since we've rearchitectured openrewrite/rewrite a bit to have less recipes in there, and more in specific modules


import org.openrewrite.ExecutionContext;
import org.openrewrite.Recipe;
import org.openrewrite.TreeVisitor;
import org.openrewrite.java.tree.Flag;
import org.openrewrite.java.tree.J;

import java.util.concurrent.atomic.AtomicBoolean;
import java.util.stream.Collectors;

import static java.util.Comparator.comparing;

/**
* TODO: Check the following criteria:
* - Lombok in dependencies +
* - All methods of class are static +
* - No instances of given class +
* - All static attributes are final +
* <p>
* TODO: Perform the transformation:
* - Add the annotation +
* - Remove static from all attributes and methods +
* <p>
* TODO: Features to consider:
* - Transformation: Add Lombok config if not present + supported configuration options for utility class
* - Transformation: Replace instantiation with static calls to methods --> na
* - Anonymous classes ???
* - Reflection ???
Comment on lines +30 to +44
Copy link
Contributor

Choose a reason for hiding this comment

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

These comments and the failing test give me pause in reviewing this further; could you tell us your plans on these?

*/
public class LombokUtilityClass extends Recipe {

@Override
public String getDisplayName() {
return "Lombok UtilityClass";
}

@Override
public String getDescription() {
return "This recipe will check if any class is transformable (only static methods in class)" +
" into the Lombok UtilityClass and will perform the change if applicable.";
}

@Override
public TreeVisitor<?, ExecutionContext> getVisitor() {
return new ChangeVisitor();
Copy link
Contributor

Choose a reason for hiding this comment

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

You could look at using Precondition.check here to first verify that a class is a utility class candidate, before applying the visitor that adds the annotation.

}


private static class ChangeVisitor extends JavaIsoVisitor<ExecutionContext> {

@Override
public J.ClassDeclaration visitClassDeclaration(
final J.ClassDeclaration classDecl,
final ExecutionContext executionContext
) {
if (!CheckVisitor.shouldPerformChanges(classDecl)) {
return super.visitClassDeclaration(classDecl, executionContext);
}

maybeAddImport("lombok.experimental.UtilityClass", false);
return super.visitClassDeclaration(
JavaTemplate
.builder("@UtilityClass")
.imports("lombok.experimental.UtilityClass")
.javaParser(JavaParser.fromJavaVersion().classpath("lombok"))
.build()
.apply(
getCursor(),
classDecl.getCoordinates().addAnnotation(comparing(J.Annotation::getSimpleName))
),
executionContext
);
}

@Override
public J.MethodDeclaration visitMethodDeclaration(
final J.MethodDeclaration method,
final ExecutionContext executionContext
) {
final J.ClassDeclaration classDecl = getCursor().firstEnclosingOrThrow(J.ClassDeclaration.class);
if (!CheckVisitor.shouldPerformChanges(classDecl)) {
return super.visitMethodDeclaration(method, executionContext);
}

return super.visitMethodDeclaration(
method.withModifiers(method.getModifiers().stream()
.filter(m -> m.getType() != J.Modifier.Type.Static)
.collect(Collectors.toList())),
executionContext
);
}

@Override
public J.VariableDeclarations visitVariableDeclarations(
final J.VariableDeclarations multiVariable,
final ExecutionContext executionContext
) {
final J.ClassDeclaration classDecl = getCursor().firstEnclosingOrThrow(J.ClassDeclaration.class);
if (!CheckVisitor.shouldPerformChanges(classDecl)) {
return super.visitVariableDeclarations(multiVariable, executionContext);
}

return super.visitVariableDeclarations(
multiVariable
.withModifiers(multiVariable.getModifiers().stream()
.filter(m -> m.getType() != J.Modifier.Type.Static)
.collect(Collectors.toList())
)
.withVariables(multiVariable.getVariables().stream()
.map(v -> v.withName(v.getName().withSimpleName(v.getName().getSimpleName().toLowerCase())))
.collect(Collectors.toList())
),
executionContext
);
}
}

private static class CheckVisitor extends JavaIsoVisitor<AtomicBoolean> {

@Override
public J.ClassDeclaration visitClassDeclaration(
final J.ClassDeclaration classDecl,
final AtomicBoolean shouldPerformChanges
) {
if (classDecl.getType().hasFlags(Flag.Interface)) {
shouldPerformChanges.set(false);
}
if (classDecl.hasModifier(J.Modifier.Type.Abstract)) {
shouldPerformChanges.set(false);
}
if (classDecl.getLeadingAnnotations().stream().anyMatch(a -> "UtilityClass".equals(a.getSimpleName()))) {
shouldPerformChanges.set(false);
}
return super.visitClassDeclaration(classDecl, shouldPerformChanges);
}

@Override
public J.MethodDeclaration visitMethodDeclaration(
final J.MethodDeclaration method,
final AtomicBoolean shouldPerformChanges
) {
if (!method.hasModifier(J.Modifier.Type.Static)) {
shouldPerformChanges.set(false);
}

if (method.getSimpleName().equalsIgnoreCase("main")) {
shouldPerformChanges.set(false);
}
return super.visitMethodDeclaration(method, shouldPerformChanges);
}

@Override
public J.VariableDeclarations.NamedVariable visitVariable(
final J.VariableDeclarations.NamedVariable variable,
final AtomicBoolean shouldPerformChanges
) {
if (variable.isField(getCursor())
&& variable.getVariableType() != null
&& !variable.getVariableType().hasFlags(Flag.Static, Flag.Final)) {
shouldPerformChanges.set(false);
}
return super.visitVariable(variable, shouldPerformChanges);
}

private static boolean shouldPerformChanges(final J.ClassDeclaration classDecl) {
return new CheckVisitor().reduce(classDecl, new AtomicBoolean(true)).get();
}
}
}
Loading
Loading