From 7a349335b2cea2bb12f51c08059a25604442e756 Mon Sep 17 00:00:00 2001 From: BoykoAlex Date: Mon, 27 Oct 2025 10:36:37 -0700 Subject: [PATCH 1/6] Remove rewrite recipes and upgrade Rewrite core Signed-off-by: BoykoAlex --- .../commons/commons-rewrite/pom.xml | 35 +- .../java/spring/AddSpringProperty.java | 197 +++++ ...dFieldIntoConstructorParameterVisitor.java | 247 ++++++ .../spring/ChangeSpringPropertyValue.java | 143 ++++ .../spring/ImplicitWebAnnotationNames.java | 135 ++++ .../java/spring/NoAutowiredOnConstructor.java | 89 +++ .../NoRepoAnnotationOnRepoInterface.java | 70 ++ .../spring/NoRequestMappingAnnotation.java | 186 +++++ .../spring/SpringExecutionContextView.java | 60 ++ ...ConfigurationAnnotationIfBeansPresent.java | 133 ++++ .../boot2/UnnecessarySpringExtension.java | 113 +++ .../java/spring/boot3/PreciseBeanType.java | 115 +++ .../framework/BeanMethodsNotPublic.java | 59 ++ .../security5/AuthorizeHttpRequests.java | 142 ++++ .../ConvertToSecurityDslVisitor.java | 306 ++++++++ .../security5/HttpSecurityLambdaDsl.java | 55 ++ .../ServerHttpSecurityLambdaDsl.java | 54 ++ .../WebSecurityConfigurerAdapter.java | 553 +++++++++++++ .../rewrite/java/AddAnnotationOverMethod.java | 2 +- .../java/spring/AddSpringPropertyTest.java | 199 +++++ .../spring/ChangeSpringPropertyValueTest.java | 59 ++ .../ImplicitWebAnnotationNamesTest.java | 183 +++++ .../org/openrewrite/java/spring/Jars.java | 98 +++ .../spring/NoAutowiredOnConstructorTest.java | 588 ++++++++++++++ .../NoRepoAnnotationOnRepoInterfaceTest.java | 255 ++++++ ...igurationAnnotationIfBeansPresentTest.java | 134 ++++ .../boot2/UnnecessarySpringExtensionTest.java | 116 +++ .../spring/boot3/PreciseBeanTypeTest.java | 149 ++++ .../framework/BeanMethodsNotPublicTest.java | 133 ++++ .../security5/AuthorizeHttpRequestsTest.java | 293 +++++++ .../security5/HttpSecurityLambdaDslTest.java | 341 ++++++++ .../ServerHttpSecurityLambdaDslTest.java | 134 ++++ .../WebSecurityConfigurerAdapterTest.java | 739 ++++++++++++++++++ .../vscode/commons/rewrite/LoadUtilsTest.java | 2 + .../java/AddAnnotationOverMethodTest.java | 8 +- .../maven/MavenIJavaProjectParserTest.java | 2 +- headless-services/commons/pom.xml | 9 +- .../HttpSecurityLambdaDslReconciler.java | 2 +- ...ServerHttpSecurityLambdaDslReconciler.java | 2 +- .../java/rewrite/RewriteRecipeRepository.java | 4 +- .../boot/app/RestTemplateFactoryTest.java | 4 +- .../index/test/SpringIndexStructureTest.java | 2 +- .../test/SpringIndexerBeanRegistrarTest.java | 5 +- .../test/BeanCompletionProviderTest.java | 6 +- .../test/DependsOnCompletionProviderTest.java | 2 +- .../test/DependsOnDefinitionProviderTest.java | 3 +- .../beans/test/FeignSymbolProviderTest.java | 2 +- .../test/NamedCompletionProviderTest.java | 4 +- .../test/NamedReferencesProviderTest.java | 4 +- .../test/ProfileCompletionProviderTest.java | 4 +- .../test/ProfileReferencesProviderTest.java | 4 +- .../test/QualifierCompletionProviderTest.java | 4 +- .../test/QualifierDefinitionProviderTest.java | 2 +- .../test/QualifierReferencesProviderTest.java | 4 +- .../test/ResourceCompletionProviderTest.java | 4 +- .../test/ResourceDefinitionProviderTest.java | 2 +- .../beans/test/SpringIndexerBeansTest.java | 4 +- ...ingIndexerJakartaJavaxAnnotationsTest.java | 2 +- .../test/ConditionalOnBeanCompletionTest.java | 2 +- ...nditionalOnBeanDefinitionProviderTest.java | 2 +- .../CronExpressionCompletionProviderTest.java | 12 +- .../boot/java/cron/CronReconcilerTest.java | 2 +- ...oryAotMetadataCodeLensProviderJpaTest.java | 2 +- ...otMetadataCodeLensProviderMongoDbTest.java | 2 +- .../DataRepositoryAotMetadataServiceTest.java | 4 +- .../test/DataRepositoryIndexElementsTest.java | 2 +- .../test/EventsReferencesProviderTest.java | 4 +- .../events/test/SpringIndexerEventsTest.java | 9 +- .../test/HttpExchangeIndexElementsTest.java | 2 +- .../test/WebConfigCodeLensProviderTest.java | 2 +- .../test/WebVersionSupportTest.java | 2 +- .../rewrite/RewriteRecipeRepositoryTest.java | 2 + .../java/spel/SpelDefinitionProviderTest.java | 2 +- .../ProjectBasedCatalogSourceTest.java | 4 +- .../stereotypes/StereotypesIndexerTest.java | 6 +- .../boot/java/utils/test/ASTUtilsTest.java | 8 +- .../boot/maven/PomInlayHintHandlerTest.java | 4 +- .../xml/test/XMLBeanRefContentAssistTest.java | 4 +- .../boot/xml/test/XMLContentAssistTest.java | 4 +- 79 files changed, 6180 insertions(+), 107 deletions(-) create mode 100644 headless-services/commons/commons-rewrite/src/main/java/org/openrewrite/java/spring/AddSpringProperty.java create mode 100644 headless-services/commons/commons-rewrite/src/main/java/org/openrewrite/java/spring/AutowiredFieldIntoConstructorParameterVisitor.java create mode 100644 headless-services/commons/commons-rewrite/src/main/java/org/openrewrite/java/spring/ChangeSpringPropertyValue.java create mode 100644 headless-services/commons/commons-rewrite/src/main/java/org/openrewrite/java/spring/ImplicitWebAnnotationNames.java create mode 100644 headless-services/commons/commons-rewrite/src/main/java/org/openrewrite/java/spring/NoAutowiredOnConstructor.java create mode 100644 headless-services/commons/commons-rewrite/src/main/java/org/openrewrite/java/spring/NoRepoAnnotationOnRepoInterface.java create mode 100644 headless-services/commons/commons-rewrite/src/main/java/org/openrewrite/java/spring/NoRequestMappingAnnotation.java create mode 100644 headless-services/commons/commons-rewrite/src/main/java/org/openrewrite/java/spring/SpringExecutionContextView.java create mode 100644 headless-services/commons/commons-rewrite/src/main/java/org/openrewrite/java/spring/boot2/AddConfigurationAnnotationIfBeansPresent.java create mode 100644 headless-services/commons/commons-rewrite/src/main/java/org/openrewrite/java/spring/boot2/UnnecessarySpringExtension.java create mode 100644 headless-services/commons/commons-rewrite/src/main/java/org/openrewrite/java/spring/boot3/PreciseBeanType.java create mode 100644 headless-services/commons/commons-rewrite/src/main/java/org/openrewrite/java/spring/framework/BeanMethodsNotPublic.java create mode 100644 headless-services/commons/commons-rewrite/src/main/java/org/openrewrite/java/spring/security5/AuthorizeHttpRequests.java create mode 100644 headless-services/commons/commons-rewrite/src/main/java/org/openrewrite/java/spring/security5/ConvertToSecurityDslVisitor.java create mode 100644 headless-services/commons/commons-rewrite/src/main/java/org/openrewrite/java/spring/security5/HttpSecurityLambdaDsl.java create mode 100644 headless-services/commons/commons-rewrite/src/main/java/org/openrewrite/java/spring/security5/ServerHttpSecurityLambdaDsl.java create mode 100644 headless-services/commons/commons-rewrite/src/main/java/org/openrewrite/java/spring/security5/WebSecurityConfigurerAdapter.java create mode 100644 headless-services/commons/commons-rewrite/src/test/java/org/openrewrite/java/spring/AddSpringPropertyTest.java create mode 100644 headless-services/commons/commons-rewrite/src/test/java/org/openrewrite/java/spring/ChangeSpringPropertyValueTest.java create mode 100644 headless-services/commons/commons-rewrite/src/test/java/org/openrewrite/java/spring/ImplicitWebAnnotationNamesTest.java create mode 100644 headless-services/commons/commons-rewrite/src/test/java/org/openrewrite/java/spring/Jars.java create mode 100644 headless-services/commons/commons-rewrite/src/test/java/org/openrewrite/java/spring/NoAutowiredOnConstructorTest.java create mode 100644 headless-services/commons/commons-rewrite/src/test/java/org/openrewrite/java/spring/NoRepoAnnotationOnRepoInterfaceTest.java create mode 100644 headless-services/commons/commons-rewrite/src/test/java/org/openrewrite/java/spring/boot2/AddConfigurationAnnotationIfBeansPresentTest.java create mode 100644 headless-services/commons/commons-rewrite/src/test/java/org/openrewrite/java/spring/boot2/UnnecessarySpringExtensionTest.java create mode 100644 headless-services/commons/commons-rewrite/src/test/java/org/openrewrite/java/spring/boot3/PreciseBeanTypeTest.java create mode 100644 headless-services/commons/commons-rewrite/src/test/java/org/openrewrite/java/spring/framework/BeanMethodsNotPublicTest.java create mode 100644 headless-services/commons/commons-rewrite/src/test/java/org/openrewrite/java/spring/security5/AuthorizeHttpRequestsTest.java create mode 100644 headless-services/commons/commons-rewrite/src/test/java/org/openrewrite/java/spring/security5/HttpSecurityLambdaDslTest.java create mode 100644 headless-services/commons/commons-rewrite/src/test/java/org/openrewrite/java/spring/security5/ServerHttpSecurityLambdaDslTest.java create mode 100644 headless-services/commons/commons-rewrite/src/test/java/org/openrewrite/java/spring/security5/WebSecurityConfigurerAdapterTest.java diff --git a/headless-services/commons/commons-rewrite/pom.xml b/headless-services/commons/commons-rewrite/pom.xml index b5ee41ef43..1f362f9d6a 100644 --- a/headless-services/commons/commons-rewrite/pom.xml +++ b/headless-services/commons/commons-rewrite/pom.xml @@ -77,6 +77,11 @@ rewrite-java-21 runtime + + org.openrewrite + rewrite-java-25 + runtime + org.openrewrite.gradle.tooling model @@ -101,17 +106,6 @@ 1.3.0 - - org.openrewrite.recipe - rewrite-spring - - - org.openrewrite.recipe - rewrite-static-analysis - - - - org.springframework.ide.vscode commons-maven @@ -139,7 +133,24 @@ org.springframework.boot spring-boot-starter-web test - + + + org.openrewrite.recipe + rewrite-java-dependencies + 1.43.0 + test + + + org.springframework.boot + spring-boot-starter-security + test + + + org.springframework.data + spring-data-commons + test + + \ No newline at end of file diff --git a/headless-services/commons/commons-rewrite/src/main/java/org/openrewrite/java/spring/AddSpringProperty.java b/headless-services/commons/commons-rewrite/src/main/java/org/openrewrite/java/spring/AddSpringProperty.java new file mode 100644 index 0000000000..8952490660 --- /dev/null +++ b/headless-services/commons/commons-rewrite/src/main/java/org/openrewrite/java/spring/AddSpringProperty.java @@ -0,0 +1,197 @@ +/* + * Copyright 2021 the original author or authors. + *

+ * 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 + *

+ * https://www.apache.org/licenses/LICENSE-2.0 + *

+ * 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; + +import org.jspecify.annotations.Nullable; +import org.openrewrite.*; +import org.openrewrite.internal.StringUtils; +import org.openrewrite.properties.AddProperty; +import org.openrewrite.properties.tree.Properties; +import org.openrewrite.yaml.MergeYaml; +import org.openrewrite.yaml.tree.Yaml; + +import java.nio.file.Path; +import java.util.List; +import java.util.Objects; +import java.util.regex.Pattern; + +/** + * A recipe to uniformly add a property to Spring configuration file. This recipe supports adding properties to + * ".properties" and YAML files. This recipe will only add the property if it does not already exist within the + * configuration file. + *

+ * NOTE: Because an application may have a large collection of yaml files (some of which may not even be related to + * Spring configuration), this recipe will only make changes to files that match one of the pathExpressions. If + * the recipe is configured without pathExpressions, it will query the execution context for reasonable defaults. + */ +public class AddSpringProperty extends Recipe { + + @Option(displayName = "Property key", + description = "The property key to add.", + example = "management.metrics.enable.process.files") + String property; + + @Option(displayName = "Property value", + description = "The value of the new property key.", + example = "true") + String value; + + @Option(displayName = "Optional comment to be prepended to the property", + description = "A comment that will be added to the new property.", + required = false, + example = "This is a comment") + @Nullable + String comment; + + @Option(displayName = "Optional list of file path matcher", + description = "Each value in this list represents a glob expression that is used to match which files will " + + "be modified. If this value is not present, this recipe will query the execution context for " + + "reasonable defaults. (\"**/application.yml\", \"**/application.yml\", and \"**/application.properties\".", + required = false, + example = "[\"**/application.yml\"]") + @Nullable + List pathExpressions; + + + public AddSpringProperty(String property, String value, @Nullable String comment, + @Nullable List pathExpressions) { + this.property = property; + this.value = value; + this.comment = comment; + this.pathExpressions = pathExpressions; + } + + @Override + public String getDisplayName() { + return "Add a spring configuration property"; + } + + @Override + public String getDescription() { + return "Add a spring configuration property to a configuration file if it does not already exist in that file."; + } + + @Override + public TreeVisitor getVisitor() { + return new TreeVisitor() { + @Override + public boolean isAcceptable(SourceFile sourceFile, ExecutionContext ctx) { + return sourceFile instanceof Yaml.Documents || sourceFile instanceof Properties.File; + } + + @Override + public @Nullable Tree visit(@Nullable Tree t, ExecutionContext ctx) { + if (t instanceof Yaml.Documents && sourcePathMatches(((SourceFile) t).getSourcePath(), ctx)) { + t = createMergeYamlVisitor().getVisitor().visit(t, ctx); + } else if (t instanceof Properties.File && sourcePathMatches(((SourceFile) t).getSourcePath(), ctx)) { + t = new AddProperty(property, value, comment, null, null).getVisitor().visit(t, ctx); + } + return t; + } + }; + } + + private boolean sourcePathMatches(Path sourcePath, ExecutionContext ctx) { + List expressions = pathExpressions; + if (expressions == null || pathExpressions.isEmpty()) { + //If not defined, get reasonable defaults from the execution context. + expressions = SpringExecutionContextView.view(ctx).getDefaultApplicationConfigurationPaths(); + } + if (expressions.isEmpty()) { + return true; + } + for (String filePattern : expressions) { + if (PathUtils.matchesGlob(sourcePath, filePattern)) { + return true; + } + } + + return false; + } + + private MergeYaml createMergeYamlVisitor() { + String[] propertyParts = property.split("\\."); + + StringBuilder yaml = new StringBuilder(); + + String indent = ""; + for (String part : propertyParts) { + if (yaml.length() > 0) { + yaml.append("\n"); + } + if (!StringUtils.isBlank(comment)) { + //noinspection StringEquality + if (part == propertyParts[propertyParts.length - 1]) { + yaml.append(indent).append("# ").append(comment).append("\n"); + } + } + yaml.append(indent).append(part).append(":"); + indent = indent + " "; + } + if (quoteValue(value)) { + yaml.append(" \"").append(value).append('"'); + } else { + yaml.append(" ").append(value); + } + return new MergeYaml("$", yaml.toString(), true, null, null, null, null, null); + } + + + + public String getProperty() { + return property; + } + + public String getValue() { + return value; + } + + public String getComment() { + return comment; + } + + public List getPathExpressions() { + return pathExpressions; + } + + + + @Override + public int hashCode() { + final int prime = 31; + int result = super.hashCode(); + result = prime * result + Objects.hash(comment, pathExpressions, property, value); + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (!super.equals(obj)) + return false; + if (getClass() != obj.getClass()) + return false; + AddSpringProperty other = (AddSpringProperty) obj; + return Objects.equals(comment, other.comment) && Objects.equals(pathExpressions, other.pathExpressions) + && Objects.equals(property, other.property) && Objects.equals(value, other.value); + } + + private static final Pattern scalarNeedsAQuote = Pattern.compile("[^a-zA-Z\\d\\s]*"); + private boolean quoteValue(String value) { + return scalarNeedsAQuote.matcher(value).matches(); + } +} diff --git a/headless-services/commons/commons-rewrite/src/main/java/org/openrewrite/java/spring/AutowiredFieldIntoConstructorParameterVisitor.java b/headless-services/commons/commons-rewrite/src/main/java/org/openrewrite/java/spring/AutowiredFieldIntoConstructorParameterVisitor.java new file mode 100644 index 0000000000..79137a7147 --- /dev/null +++ b/headless-services/commons/commons-rewrite/src/main/java/org/openrewrite/java/spring/AutowiredFieldIntoConstructorParameterVisitor.java @@ -0,0 +1,247 @@ +/* + * Copyright 2022 the original author or authors. + *

+ * 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 + *

+ * https://www.apache.org/licenses/LICENSE-2.0 + *

+ * 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; + +import org.openrewrite.Cursor; +import org.openrewrite.ExecutionContext; +import org.openrewrite.Tree; +import org.openrewrite.internal.ListUtils; +import org.openrewrite.java.*; +import org.openrewrite.java.tree.*; +import org.openrewrite.java.tree.J.Block; +import org.openrewrite.java.tree.J.ClassDeclaration; +import org.openrewrite.java.tree.J.MethodDeclaration; +import org.openrewrite.java.tree.J.VariableDeclarations; +import org.openrewrite.java.tree.JavaType.FullyQualified; +import org.openrewrite.marker.Markers; + +import java.util.Collections; +import java.util.List; +import java.util.Objects; +import java.util.Optional; +import java.util.stream.Collectors; +import java.util.stream.Stream; + + +public class AutowiredFieldIntoConstructorParameterVisitor extends JavaVisitor { + private static final String AUTOWIRED = "org.springframework.beans.factory.annotation.Autowired"; + + private final String classFqName; + private final String fieldName; + + + + public AutowiredFieldIntoConstructorParameterVisitor(String classFqName, String fieldName) { + this.classFqName = classFqName; + this.fieldName = fieldName; + } + + @Override + public J visitClassDeclaration(ClassDeclaration classDecl, ExecutionContext ctx) { + if (TypeUtils.isOfClassType(classDecl.getType(), classFqName)) { + List constructors = classDecl.getBody().getStatements().stream() + .filter(J.MethodDeclaration.class::isInstance) + .map(J.MethodDeclaration.class::cast) + .filter(MethodDeclaration::isConstructor) + .collect(Collectors.toList()); + boolean applicable = false; + if (constructors.isEmpty()) { + applicable = true; + } else if (constructors.size() == 1) { + MethodDeclaration c = constructors.get(0); + getCursor().putMessage("applicableConstructor", c); + applicable = isNotConstructorInitializingField(c, fieldName); + } else { + List autowiredConstructors = constructors.stream().filter(constr -> constr.getLeadingAnnotations().stream() + .map(a -> TypeUtils.asFullyQualified(a.getType())) + .filter(Objects::nonNull) + .map(FullyQualified::getFullyQualifiedName) + .anyMatch(AUTOWIRED::equals) + ) + .limit(2) + .collect(Collectors.toList()); + if (autowiredConstructors.size() == 1) { + MethodDeclaration c = autowiredConstructors.get(0); + getCursor().putMessage("applicableConstructor", autowiredConstructors.get(0)); + applicable = isNotConstructorInitializingField(c, fieldName); + } + } + if (applicable) { + // visit contents if there is applicable constructor + return super.visitClassDeclaration(classDecl, ctx); + } + } + return classDecl; + } + + public static boolean isNotConstructorInitializingField(MethodDeclaration c, String fieldName) { + return c.getBody() == null || c.getBody().getStatements().stream().filter(J.Assignment.class::isInstance).map(J.Assignment.class::cast).noneMatch(a -> { + Expression expr = a.getVariable(); + if (expr instanceof J.FieldAccess) { + J.FieldAccess fa = (J.FieldAccess) expr; + if (fieldName.equals(fa.getSimpleName()) && fa.getTarget() instanceof J.Identifier) { + J.Identifier target = (J.Identifier) fa.getTarget(); + if ("this".equals(target.getSimpleName())) { + return true; + } + } + } + if (expr instanceof J.Identifier) { + JavaType.Variable fieldType = c.getMethodType().getDeclaringType().getMembers().stream().filter(v -> fieldName.equals(v.getName())).findFirst().orElse(null); + if (fieldType != null) { + J.Identifier identifier = (J.Identifier) expr; + return fieldType.equals(identifier.getFieldType()); + } + } + return false; + }); + } + + @Override + public J visitVariableDeclarations(VariableDeclarations multiVariable, ExecutionContext p) { + Cursor blockCursor = getCursor().dropParentUntil(it -> it instanceof J.Block || it == Cursor.ROOT_VALUE); + if (!(blockCursor.getValue() instanceof J.Block)) { + return multiVariable; + } + VariableDeclarations mv = multiVariable; + if (blockCursor.getParent() != null && blockCursor.getParent().getValue() instanceof ClassDeclaration && + multiVariable.getVariables().size() == 1 && + fieldName.equals(multiVariable.getVariables().get(0).getName().getSimpleName())) { + + mv = (VariableDeclarations) new RemoveAnnotationVisitor(new AnnotationMatcher("@" + AUTOWIRED)).visitNonNull(multiVariable, p); + if (mv != multiVariable && multiVariable.getTypeExpression() != null) { + if (mv.getModifiers().stream().noneMatch(m -> m.getType() == J.Modifier.Type.Final)) { + Space prefix = Space.firstPrefix(mv.getVariables()); + J.Modifier m = new J.Modifier(Tree.randomId(), Space.EMPTY, Markers.EMPTY, null, J.Modifier.Type.Final, Collections.emptyList()); + if (mv.getModifiers().isEmpty()) { + mv = mv.withTypeExpression(mv.getTypeExpression().withPrefix(prefix)); + } else { + m = m.withPrefix(prefix); + } + mv = mv.withModifiers(ListUtils.concat(mv.getModifiers(), m)); + } + maybeRemoveImport(AUTOWIRED); + MethodDeclaration constructor = blockCursor.getParent().getMessage("applicableConstructor"); + ClassDeclaration c = blockCursor.getParent().getValue(); + if (constructor == null) { + doAfterVisit(new AddConstructorVisitor(c.getSimpleName(), fieldName, multiVariable.getTypeExpression())); + } else { + doAfterVisit(new AddConstructorParameterAndAssignment(constructor, fieldName, multiVariable.getTypeExpression())); + } + } + } + return mv; + } + + + private static class AddConstructorVisitor extends JavaVisitor { + private final String className; + private final String fieldName; + private final TypeTree type; + + public AddConstructorVisitor(String className, String fieldName, TypeTree type) { + this.className = className; + this.fieldName = fieldName; + this.type = type; + } + + @Override + public J visitBlock(Block block, ExecutionContext p) { + if (getCursor().getParent() != null) { + Object n = getCursor().getParent().getValue(); + if (n instanceof ClassDeclaration) { + ClassDeclaration classDecl = (ClassDeclaration) n; + JavaType.FullyQualified typeFqn = TypeUtils.asFullyQualified(type.getType()); + if (typeFqn != null && classDecl.getKind() == ClassDeclaration.Kind.Type.Class && className.equals(classDecl.getSimpleName())) { + JavaTemplate.Builder template = JavaTemplate.builder("" + + classDecl.getSimpleName() + "(" + typeFqn.getClassName() + " " + fieldName + ") {\n" + + "this." + fieldName + " = " + fieldName + ";\n" + + "}\n" + ).contextSensitive(); + FullyQualified fq = TypeUtils.asFullyQualified(type.getType()); + if (fq != null) { + template.imports(fq.getFullyQualifiedName()); + maybeAddImport(fq); + } + Optional firstMethod = block.getStatements().stream().filter(MethodDeclaration.class::isInstance).findFirst(); + + return firstMethod.map(statement -> + (J) template.build() + .apply(getCursor(), + statement.getCoordinates().before() + ) + ) + .orElseGet(() -> + template.build() + .apply( + getCursor(), + block.getCoordinates().lastStatement() + ) + ); + } + } + } + return block; + } + } + + private static class AddConstructorParameterAndAssignment extends JavaIsoVisitor { + private final MethodDeclaration constructor; + private final String fieldName; + private final String methodType; + + public AddConstructorParameterAndAssignment(MethodDeclaration constructor, String fieldName, TypeTree type) { + this.constructor = constructor; + this.fieldName = fieldName; + JavaType.FullyQualified fq = TypeUtils.asFullyQualified(type.getType()); + if (fq != null) { + methodType = fq.getClassName(); + } else { + throw new IllegalArgumentException("Unable to determine parameter type"); + } + } + + @Override + public MethodDeclaration visitMethodDeclaration(MethodDeclaration method, ExecutionContext p) { + J.MethodDeclaration md = super.visitMethodDeclaration(method, p); + if (md == this.constructor && md.getBody() != null) { + List params = md.getParameters().stream().filter(s -> !(s instanceof J.Empty)).collect(Collectors.toList()); + String paramsStr = Stream.concat(params.stream() + .map(s -> "#{}"), Stream.of(methodType + " " + fieldName)).collect(Collectors.joining(", ")); + + md = JavaTemplate.builder(paramsStr) + .contextSensitive() + .build() + .apply( + getCursor(), + md.getCoordinates().replaceParameters(), + params.toArray() + ); + updateCursor(md); + + //noinspection ConstantConditions + md = JavaTemplate.builder("this." + fieldName + " = " + fieldName + ";") + .contextSensitive() + .build() + .apply( + getCursor(), + md.getBody().getCoordinates().lastStatement() + ); + } + return md; + } + } +} diff --git a/headless-services/commons/commons-rewrite/src/main/java/org/openrewrite/java/spring/ChangeSpringPropertyValue.java b/headless-services/commons/commons-rewrite/src/main/java/org/openrewrite/java/spring/ChangeSpringPropertyValue.java new file mode 100644 index 0000000000..dded176ea7 --- /dev/null +++ b/headless-services/commons/commons-rewrite/src/main/java/org/openrewrite/java/spring/ChangeSpringPropertyValue.java @@ -0,0 +1,143 @@ +/* + * Copyright 2023 the original author or authors. + *

+ * 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 + *

+ * https://www.apache.org/licenses/LICENSE-2.0 + *

+ * 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; + +import java.util.Objects; + +import org.jspecify.annotations.Nullable; +import org.openrewrite.*; +import org.openrewrite.internal.StringUtils; +import org.openrewrite.properties.tree.Properties; +import org.openrewrite.yaml.tree.Yaml; + +public class ChangeSpringPropertyValue extends Recipe { + + @Override + public String getDisplayName() { + return "Change the value of a spring application property"; + } + + @Override + public String getDescription() { + return "Change spring application property values existing in either Properties or Yaml files."; + } + + @Option(displayName = "Property key", + description = "The name of the property key whose value is to be changed.", + example = "management.metrics.binders.files.enabled") + String propertyKey; + + @Option(displayName = "New value", + description = "The new value to be used for key specified by `propertyKey`.", + example = "management.metrics.enable.process.files") + String newValue; + + @Option(displayName = "Old value", + required = false, + description = "Only change the property value if it matches the configured `oldValue`.", + example = "false") + @Nullable + String oldValue; + + @Option(displayName = "Regex", + description = "Default false. If enabled, `oldValue` will be interpreted as a Regular Expression, and capture group contents will be available in `newValue`", + required = false) + @Nullable + Boolean regex; + + @Option(displayName = "Use relaxed binding", + description = "Whether to match the `propertyKey` using [relaxed binding](https://docs.spring.io/spring-boot/docs/2.5.6/reference/html/features.html#features.external-config.typesafe-configuration-properties.relaxed-binding) " + + "rules. Default is `true`. Set to `false` to use exact matching.", + required = false) + @Nullable + Boolean relaxedBinding; + + public ChangeSpringPropertyValue(String propertyKey, String newValue, @Nullable String oldValue, + @Nullable Boolean regex, @Nullable Boolean relaxedBinding) { + this.propertyKey = propertyKey; + this.newValue = newValue; + this.oldValue = oldValue; + this.regex = regex; + this.relaxedBinding = relaxedBinding; + } + + @Override + public Validated validate() { + return super.validate().and( + Validated.test("oldValue", "is required if `regex` is enabled", oldValue, + value -> !(Boolean.TRUE.equals(regex) && StringUtils.isNullOrEmpty(value)))); + } + + @Override + public TreeVisitor getVisitor() { + Recipe changeProperties = new org.openrewrite.properties.ChangePropertyValue(propertyKey, newValue, oldValue, regex, relaxedBinding); + Recipe changeYaml = new org.openrewrite.yaml.ChangePropertyValue(propertyKey, newValue, oldValue, regex, relaxedBinding, null); + return new TreeVisitor() { + @Override + public @Nullable Tree visit(@Nullable Tree tree, ExecutionContext ctx) { + if (tree instanceof Properties.File) { + tree = changeProperties.getVisitor().visit(tree, ctx); + } else if (tree instanceof Yaml.Documents) { + tree = changeYaml.getVisitor().visit(tree, ctx); + } + return tree; + } + }; + } + + public String getPropertyKey() { + return propertyKey; + } + + public String getNewValue() { + return newValue; + } + + public String getOldValue() { + return oldValue; + } + + public Boolean getRegex() { + return regex; + } + + public Boolean getRelaxedBinding() { + return relaxedBinding; + } + + @Override + public int hashCode() { + final int prime = 31; + int result = super.hashCode(); + result = prime * result + Objects.hash(newValue, oldValue, propertyKey, regex, relaxedBinding); + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (!super.equals(obj)) + return false; + if (getClass() != obj.getClass()) + return false; + ChangeSpringPropertyValue other = (ChangeSpringPropertyValue) obj; + return Objects.equals(newValue, other.newValue) && Objects.equals(oldValue, other.oldValue) + && Objects.equals(propertyKey, other.propertyKey) && Objects.equals(regex, other.regex) + && Objects.equals(relaxedBinding, other.relaxedBinding); + } + +} diff --git a/headless-services/commons/commons-rewrite/src/main/java/org/openrewrite/java/spring/ImplicitWebAnnotationNames.java b/headless-services/commons/commons-rewrite/src/main/java/org/openrewrite/java/spring/ImplicitWebAnnotationNames.java new file mode 100644 index 0000000000..c1d20d2ada --- /dev/null +++ b/headless-services/commons/commons-rewrite/src/main/java/org/openrewrite/java/spring/ImplicitWebAnnotationNames.java @@ -0,0 +1,135 @@ +/* + * Copyright 2021 the original author or authors. + *

+ * 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 + *

+ * https://www.apache.org/licenses/LICENSE-2.0 + *

+ * 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; + +import org.openrewrite.*; +import org.openrewrite.internal.ListUtils; +import org.openrewrite.java.JavaIsoVisitor; +import org.openrewrite.java.search.UsesType; +import org.openrewrite.java.tree.J; + +import java.util.List; +import java.util.Set; +import java.util.stream.Stream; + +import static java.util.stream.Collectors.toSet; +import static org.openrewrite.java.tree.TypeUtils.isOfClassType; + +public class ImplicitWebAnnotationNames extends Recipe { + @Override + public String getDisplayName() { + return "Remove implicit web annotation names"; + } + + @Override + public String getDescription() { + return "Removes implicit web annotation names."; + } + + @Override + public TreeVisitor getVisitor() { + return Preconditions.check(Preconditions.or( + new UsesType<>("org.springframework.web.bind.annotation.PathVariable", false), + new UsesType<>("org.springframework.web.bind.annotation.RequestParam", false), + new UsesType<>("org.springframework.web.bind.annotation.RequestHeader", false), + new UsesType<>("org.springframework.web.bind.annotation.RequestAttribute", false), + new UsesType<>("org.springframework.web.bind.annotation.CookieValue", false), + new UsesType<>("org.springframework.web.bind.annotation.ModelAttribute", false), + new UsesType<>("org.springframework.web.bind.annotation.SessionAttribute", false) + ), new ImplicitWebAnnotationNamesVisitor()); + } + + private static class ImplicitWebAnnotationNamesVisitor extends JavaIsoVisitor { + private static final Set PARAM_ANNOTATIONS = Stream.of( + "PathVariable", + "RequestParam", + "RequestHeader", + "RequestAttribute", + "CookieValue", + "ModelAttribute", + "SessionAttribute" + ).map(className -> "org.springframework.web.bind.annotation." + className).collect(toSet()); + + + @Override + public J.VariableDeclarations visitVariableDeclarations(J.VariableDeclarations multiVariable, ExecutionContext ctx) { + J.VariableDeclarations varDecls = super.visitVariableDeclarations(multiVariable, ctx); + // Fix when the annotation looses all it's arguments, and there is no prefix between the annotation and the type expression + // i.e: @Annotation(argument)Type is valid but @AnnotationType it's not + if (!varDecls.getLeadingAnnotations().isEmpty()) { + if (varDecls.getTypeExpression() != null && varDecls.getTypeExpression().getPrefix().getWhitespace().isEmpty()) { + List annotations = varDecls.getLeadingAnnotations(); + J.Annotation lastAnnotation = annotations.get(annotations.size() - 1); + if (lastAnnotation.getArguments() == null || lastAnnotation.getArguments().isEmpty()) { + varDecls = varDecls.withTypeExpression( + varDecls.getTypeExpression().withPrefix( + varDecls.getTypeExpression().getPrefix().withWhitespace(" "))); + } + } + } + return varDecls; + } + + @Override + public J.Annotation visitAnnotation(J.Annotation annotation, ExecutionContext ctx) { + J.Annotation a = super.visitAnnotation(annotation, ctx); + + if (PARAM_ANNOTATIONS.stream().anyMatch(annotationClass -> isOfClassType(annotation.getType(), annotationClass)) && + annotation.getArguments() != null && getCursor().getParentOrThrow().getValue() instanceof J.VariableDeclarations) { + + // Copying the first argument whitespace to use it later on in case we remove the original first argument. + String firstWhitespace = a.getArguments() != null && !a.getArguments().isEmpty() ? + a.getArguments().get(0).getPrefix().getWhitespace() : + null; + + a = a.withArguments(ListUtils.map(a.getArguments(), arg -> { + Cursor varDecsCursor = getCursor().getParentOrThrow(); + J.VariableDeclarations.NamedVariable namedVariable = varDecsCursor.getValue().getVariables().get(0); + if (arg instanceof J.Assignment) { + J.Assignment assignment = (J.Assignment) arg; + if (assignment.getVariable() instanceof J.Identifier && assignment.getAssignment() instanceof J.Literal) { + J.Identifier assignName = (J.Identifier) assignment.getVariable(); + if ("value".equals(assignName.getSimpleName()) || "name".equals(assignName.getSimpleName())) { + if (maybeRemoveArg(namedVariable, (J.Literal) assignment.getAssignment())) { + return null; + } + } + } + } else if (arg instanceof J.Literal) { + if (maybeRemoveArg(namedVariable, (J.Literal) arg)) { + return null; + } + } + + return arg; + })); + // Copying the original first argument whitespace to the new first argument in case the original first argument was removed. + // No need to check if the first argument has been removed. Worst case scenario we are overriding the same whitespace. + if (firstWhitespace != null) { + a = a.withArguments(ListUtils.mapFirst(a.getArguments(), arg -> arg.withPrefix(arg.getPrefix().withWhitespace(firstWhitespace)))); + } + } + + return a; + } + + private boolean maybeRemoveArg(J.VariableDeclarations.NamedVariable namedVariable, J.Literal assignValue) { + Object value = assignValue.getValue(); + assert value != null; + return namedVariable.getSimpleName().equals(value); + } + } +} diff --git a/headless-services/commons/commons-rewrite/src/main/java/org/openrewrite/java/spring/NoAutowiredOnConstructor.java b/headless-services/commons/commons-rewrite/src/main/java/org/openrewrite/java/spring/NoAutowiredOnConstructor.java new file mode 100644 index 0000000000..24a8bf6f9e --- /dev/null +++ b/headless-services/commons/commons-rewrite/src/main/java/org/openrewrite/java/spring/NoAutowiredOnConstructor.java @@ -0,0 +1,89 @@ +/* + * Copyright 2021 the original author or authors. + *

+ * 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 + *

+ * https://www.apache.org/licenses/LICENSE-2.0 + *

+ * 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; + +import org.openrewrite.ExecutionContext; +import org.openrewrite.Preconditions; +import org.openrewrite.Recipe; +import org.openrewrite.TreeVisitor; +import org.openrewrite.internal.ListUtils; +import org.openrewrite.java.AnnotationMatcher; +import org.openrewrite.java.JavaIsoVisitor; +import org.openrewrite.java.RemoveAnnotationVisitor; +import org.openrewrite.java.search.FindAnnotations; +import org.openrewrite.java.search.UsesType; +import org.openrewrite.java.tree.J; +import org.openrewrite.java.tree.Statement; + +public class NoAutowiredOnConstructor extends Recipe { + private static final AnnotationMatcher AUTOWIRED_ANNOTATION_MATCHER = + new AnnotationMatcher("@org.springframework.beans.factory.annotation.Autowired(true)"); + + @Override + public String getDisplayName() { + return "Remove the `@Autowired` annotation on inferred constructor"; + } + + @Override + public String getDescription() { + return "Spring can infer an autowired constructor when there is a single constructor on the bean. " + + "This recipe removes unneeded `@Autowired` annotations on constructors."; + } + + @Override + public TreeVisitor getVisitor() { + return Preconditions.check(new UsesType<>("org.springframework.beans.factory.annotation.Autowired", false), new JavaIsoVisitor() { + @Override + public J.ClassDeclaration visitClassDeclaration(J.ClassDeclaration classDecl, ExecutionContext ctx) { + J.ClassDeclaration cd = super.visitClassDeclaration(classDecl, ctx); + + int constructorCount = 0; + for (Statement s : cd.getBody().getStatements()) { + if (isConstructor(s)) { + constructorCount++; + if (constructorCount > 1) { + return cd; + } + } + } + + // Lombok can also provide a constructor, so keep `@Autowired` on constructors if found + if (!FindAnnotations.find(cd, "@lombok.*Constructor").isEmpty()) { + return cd; + } + + // `@ConfigurationProperties` classes usually use field injection, so keep `@Autowired` on constructors + if (!FindAnnotations.find(cd, "@org.springframework.boot.context.properties.ConfigurationProperties").isEmpty()) { + return cd; + } + + return cd.withBody(cd.getBody().withStatements( + ListUtils.map(cd.getBody().getStatements(), s -> { + if (!isConstructor(s)) { + return s; + } + maybeRemoveImport("org.springframework.beans.factory.annotation.Autowired"); + return (Statement) new RemoveAnnotationVisitor(AUTOWIRED_ANNOTATION_MATCHER).visit(s, ctx, getCursor()); + }) + )); + } + }); + } + + private static boolean isConstructor(Statement s) { + return s instanceof J.MethodDeclaration && ((J.MethodDeclaration) s).isConstructor(); + } +} diff --git a/headless-services/commons/commons-rewrite/src/main/java/org/openrewrite/java/spring/NoRepoAnnotationOnRepoInterface.java b/headless-services/commons/commons-rewrite/src/main/java/org/openrewrite/java/spring/NoRepoAnnotationOnRepoInterface.java new file mode 100644 index 0000000000..7a3534a86a --- /dev/null +++ b/headless-services/commons/commons-rewrite/src/main/java/org/openrewrite/java/spring/NoRepoAnnotationOnRepoInterface.java @@ -0,0 +1,70 @@ +/* + * Copyright 2022 the original author or authors. + *

+ * 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 + *

+ * https://www.apache.org/licenses/LICENSE-2.0 + *

+ * 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; + +import org.openrewrite.ExecutionContext; +import org.openrewrite.Preconditions; +import org.openrewrite.Recipe; +import org.openrewrite.TreeVisitor; +import org.openrewrite.java.AnnotationMatcher; +import org.openrewrite.java.JavaIsoVisitor; +import org.openrewrite.java.RemoveAnnotationVisitor; +import org.openrewrite.java.search.UsesType; +import org.openrewrite.java.tree.J; +import org.openrewrite.java.tree.JavaType; +import org.openrewrite.java.tree.TypeUtils; + +public class NoRepoAnnotationOnRepoInterface extends Recipe { + + private static final String INTERFACE_REPOSITORY = "org.springframework.data.repository.Repository"; + private static final String ANNOTATION_REPOSITORY = "org.springframework.stereotype.Repository"; + + @Override + public String getDisplayName() { + return "Remove unnecessary `@Repository` annotation from Spring Data `Repository` sub-interface"; + } + + @Override + public String getDescription() { + return "Removes superfluous `@Repository` annotation from Spring Data `Repository` sub-interfaces."; + } + + @Override + public TreeVisitor getVisitor() { + return Preconditions.check(new UsesType<>(ANNOTATION_REPOSITORY, false), new JavaIsoVisitor() { + @Override + public J.ClassDeclaration visitClassDeclaration(J.ClassDeclaration classDecl, ExecutionContext ctx) { + J.ClassDeclaration c = super.visitClassDeclaration(classDecl, ctx); + if (c.getKind() == J.ClassDeclaration.Kind.Type.Interface) { + boolean hasRepoAnnotation = c.getLeadingAnnotations().stream().anyMatch(annotation -> { + if (annotation.getArguments() == null || annotation.getArguments().isEmpty() || + annotation.getArguments().get(0) instanceof J.Empty) { + JavaType.FullyQualified type = TypeUtils.asFullyQualified(annotation.getType()); + return type != null && ANNOTATION_REPOSITORY.equals(type.getFullyQualifiedName()); + } + return false; + }); + if (hasRepoAnnotation && TypeUtils.isAssignableTo(INTERFACE_REPOSITORY, c.getType())) { + maybeRemoveImport(ANNOTATION_REPOSITORY); + return (J.ClassDeclaration) new RemoveAnnotationVisitor(new AnnotationMatcher("@" + ANNOTATION_REPOSITORY)) + .visit(c, ctx, getCursor().getParentOrThrow()); + } + } + return c; + } + }); + } +} diff --git a/headless-services/commons/commons-rewrite/src/main/java/org/openrewrite/java/spring/NoRequestMappingAnnotation.java b/headless-services/commons/commons-rewrite/src/main/java/org/openrewrite/java/spring/NoRequestMappingAnnotation.java new file mode 100644 index 0000000000..fba586c0ad --- /dev/null +++ b/headless-services/commons/commons-rewrite/src/main/java/org/openrewrite/java/spring/NoRequestMappingAnnotation.java @@ -0,0 +1,186 @@ +/* + * Copyright 2021 the original author or authors. + *

+ * 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 + *

+ * https://www.apache.org/licenses/LICENSE-2.0 + *

+ * 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; + +import org.jspecify.annotations.Nullable; +import org.openrewrite.ExecutionContext; +import org.openrewrite.Preconditions; +import org.openrewrite.Recipe; +import org.openrewrite.TreeVisitor; +import org.openrewrite.internal.ListUtils; +import org.openrewrite.java.AnnotationMatcher; +import org.openrewrite.java.ChangeType; +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.Space; + +import java.time.Duration; +import java.util.Collections; +import java.util.List; +import java.util.Optional; +import java.util.Set; + +/** + * Replace method declaration @RequestMapping annotations with the associated variant + * as defined by the request method type (GET, POST, PUT, PATCH, DELETE) + *

+ * (HEAD, OPTIONS, TRACE) methods do not have associated RequestMapping variant and are not converted + *

+ */ +public class NoRequestMappingAnnotation extends Recipe { + + @Override + public String getDisplayName() { + return "Remove `@RequestMapping` annotations"; + } + + @Override + public String getDescription() { + return "Replace method declaration `@RequestMapping` annotations with `@GetMapping`, `@PostMapping`, etc. when possible."; + } + + @Override + public Set getTags() { + return Collections.singleton("RSPEC-S4488"); + } + + @Override + public Duration getEstimatedEffortPerOccurrence() { + return Duration.ofMinutes(2); + } + + @Override + public TreeVisitor getVisitor() { + return Preconditions.check(new UsesType<>("org.springframework.web.bind.annotation.RequestMapping", false), + new NoRequestMappingAnnotationVisitor()); + } + + private static class NoRequestMappingAnnotationVisitor extends JavaIsoVisitor { + private static final AnnotationMatcher REQUEST_MAPPING_ANNOTATION_MATCHER = new AnnotationMatcher("@org.springframework.web.bind.annotation.RequestMapping"); + + @Override + public J.Annotation visitAnnotation(J.Annotation annotation, ExecutionContext ctx) { + J.Annotation a = super.visitAnnotation(annotation, ctx); + if (REQUEST_MAPPING_ANNOTATION_MATCHER.matches(a) && getCursor().getParentOrThrow().getValue() instanceof J.MethodDeclaration) { + Optional requestMethodArg = requestMethodArgument(a); + Optional requestType = requestMethodArg.map(this::requestMethodType); + String resolvedRequestMappingAnnotationClassName = requestType.map(this::associatedRequestMapping).orElse(null); + if (resolvedRequestMappingAnnotationClassName == null) { + // Without a method argument @RequestMapping matches all request methods, so we can't safely convert + return a; + } + + maybeRemoveImport("org.springframework.web.bind.annotation.RequestMapping"); + maybeRemoveImport("org.springframework.web.bind.annotation.RequestMethod"); + requestType.ifPresent(requestMethod -> maybeRemoveImport("org.springframework.web.bind.annotation.RequestMethod." + requestMethod)); + + // Remove the argument + if (methodArgumentHasSingleType(requestMethodArg.get())) { + if (a.getArguments() != null) { + a = a.withArguments(ListUtils.map(a.getArguments(), arg -> requestMethodArg.get().equals(arg) ? null : arg)); + } + } + + // Change the Annotation Type + maybeAddImport("org.springframework.web.bind.annotation." + resolvedRequestMappingAnnotationClassName); + a = (J.Annotation) new ChangeType("org.springframework.web.bind.annotation.RequestMapping", + "org.springframework.web.bind.annotation." + resolvedRequestMappingAnnotationClassName, false) + .getVisitor().visit(a, ctx, getCursor().getParentOrThrow()); + + // if there is only one remaining argument now, and it is "path" or "value", then we can drop the key name + if (a != null && a.getArguments() != null && a.getArguments().size() == 1) { + a = a.withArguments(ListUtils.map(a.getArguments(), arg -> { + if (arg instanceof J.Assignment && ((J.Assignment) arg).getVariable() instanceof J.Identifier) { + J.Identifier ident = (J.Identifier) ((J.Assignment) arg).getVariable(); + if ("path".equals(ident.getSimpleName()) || "value".equals(ident.getSimpleName())) { + return ((J.Assignment) arg).getAssignment().withPrefix(Space.EMPTY); + } + } + return arg; + })); + } + } + return a != null ? a : annotation; + } + + private Optional requestMethodArgument(J.Annotation annotation) { + if (annotation.getArguments() == null) { + return Optional.empty(); + } + return annotation.getArguments().stream() + .filter(arg -> arg instanceof J.Assignment && + ((J.Assignment) arg).getVariable() instanceof J.Identifier && + "method".equals(((J.Identifier) ((J.Assignment) arg).getVariable()).getSimpleName())) + .map(J.Assignment.class::cast) + .findFirst(); + } + + private boolean methodArgumentHasSingleType(J.Assignment assignment) { + if (!(assignment.getAssignment() instanceof J.NewArray)) { + return true; + } + J.NewArray newArray = (J.NewArray) assignment.getAssignment(); + return newArray.getInitializer() != null && newArray.getInitializer().size() == 1; + } + + private @Nullable String requestMethodType(J.@Nullable Assignment assignment) { + if(assignment == null) { + return null; + } + if (assignment.getAssignment() instanceof J.Identifier) { + return ((J.Identifier) assignment.getAssignment()).getSimpleName(); + } else if (assignment.getAssignment() instanceof J.FieldAccess) { + return ((J.FieldAccess) assignment.getAssignment()).getSimpleName(); + } else if (methodArgumentHasSingleType(assignment)) { + if(assignment.getAssignment() instanceof J.NewArray) { + J.NewArray newArray = (J.NewArray) assignment.getAssignment(); + List initializer = newArray.getInitializer(); + if(initializer == null || initializer.size() != 1) { + return null; + } + Expression methodName = initializer.get(0); + if(methodName instanceof J.Identifier) { + return ((J.Identifier)methodName).getSimpleName(); + } else if(methodName instanceof J.FieldAccess) { + return ((J.FieldAccess) methodName).getSimpleName(); + } + } else if(assignment.getAssignment() instanceof J.Identifier) { + return ((J.Identifier) assignment.getAssignment()).getSimpleName(); + } + } + return null; + } + + private @Nullable String associatedRequestMapping(String method) { + switch (method) { + case "POST": + case "PUT": + case "DELETE": + case "PATCH": + case "GET": + return method.charAt(0) + method.toLowerCase().substring(1) + "Mapping"; + } + // HEAD, OPTIONS, TRACE do not have associated RequestMapping variant + return null; + } + } +} diff --git a/headless-services/commons/commons-rewrite/src/main/java/org/openrewrite/java/spring/SpringExecutionContextView.java b/headless-services/commons/commons-rewrite/src/main/java/org/openrewrite/java/spring/SpringExecutionContextView.java new file mode 100644 index 0000000000..8a0b15a4ea --- /dev/null +++ b/headless-services/commons/commons-rewrite/src/main/java/org/openrewrite/java/spring/SpringExecutionContextView.java @@ -0,0 +1,60 @@ +/* + * Copyright 2020 the original author or authors. + *

+ * 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 + *

+ * https://www.apache.org/licenses/LICENSE-2.0 + *

+ * 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; + +import org.openrewrite.DelegatingExecutionContext; +import org.openrewrite.ExecutionContext; + +import java.util.Arrays; +import java.util.List; + +public class SpringExecutionContextView extends DelegatingExecutionContext { + + private static final String DEFAULT_APPLICATION_CONFIGURATION_PATHS = "org.openrewrite.java.spring.defaultApplicationConfigurationPaths"; + + public SpringExecutionContextView(ExecutionContext delegate) { + super(delegate); + } + + public static SpringExecutionContextView view(ExecutionContext ctx) { + if (ctx instanceof SpringExecutionContextView) { + return (SpringExecutionContextView) ctx; + } + return new SpringExecutionContextView(ctx); + } + + /** + * The path expressions used to find a spring boot application's default configuration file(s). The default masks used to + * find the application's root configuration are "**/application.properties", "**/application.yml", and "**/application.yaml" + * + * @param pathExpressions A list of expressions that will be used as masks to find an application's default configuration file(s) + * @return this + */ + public SpringExecutionContextView setDefaultApplicationConfigurationPaths(List pathExpressions) { + putMessage(DEFAULT_APPLICATION_CONFIGURATION_PATHS, pathExpressions); + return this; + } + + /** + * The path expressions used to find a spring boot application's default configuration file. The default masks used to + * find the application's root configuration are "**/application.properties", "**/application.yml", and "**/application.yaml" + * + * @return A list of file paths expression that will be used to find a spring boot application's default configuration file(s) + */ + public List getDefaultApplicationConfigurationPaths() { + return getMessage(DEFAULT_APPLICATION_CONFIGURATION_PATHS, Arrays.asList("**/application.yml", "**/application.properties", "**/application.yaml")); + } +} diff --git a/headless-services/commons/commons-rewrite/src/main/java/org/openrewrite/java/spring/boot2/AddConfigurationAnnotationIfBeansPresent.java b/headless-services/commons/commons-rewrite/src/main/java/org/openrewrite/java/spring/boot2/AddConfigurationAnnotationIfBeansPresent.java new file mode 100644 index 0000000000..70dd85e99d --- /dev/null +++ b/headless-services/commons/commons-rewrite/src/main/java/org/openrewrite/java/spring/boot2/AddConfigurationAnnotationIfBeansPresent.java @@ -0,0 +1,133 @@ +/* + * Copyright 2023 the original author or authors. + *

+ * 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 + *

+ * https://www.apache.org/licenses/LICENSE-2.0 + *

+ * 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.boot2; + +import org.openrewrite.*; +import org.openrewrite.java.AnnotationMatcher; +import org.openrewrite.java.JavaIsoVisitor; +import org.openrewrite.java.JavaParser; +import org.openrewrite.java.JavaTemplate; +import org.openrewrite.java.search.UsesType; +import org.openrewrite.java.tree.J; +import org.openrewrite.java.tree.JavaType; +import org.openrewrite.java.tree.Statement; +import org.openrewrite.java.tree.TypeUtils; + +import java.util.Comparator; + +public class AddConfigurationAnnotationIfBeansPresent extends Recipe { + + private static final String FQN_BEAN = "org.springframework.context.annotation.Bean"; + private static final String CONFIGURATION_PACKAGE = "org.springframework.context.annotation"; + private static final String CONFIGURATION_SIMPLE_NAME = "Configuration"; + private static final String FQN_CONFIGURATION = CONFIGURATION_PACKAGE + "." + CONFIGURATION_SIMPLE_NAME; + private static final AnnotationMatcher BEAN_ANNOTATION_MATCHER = new AnnotationMatcher("@" + FQN_BEAN, true); + private static final AnnotationMatcher CONFIGURATION_ANNOTATION_MATCHER = new AnnotationMatcher("@" + FQN_CONFIGURATION, true); + + + @Override + public String getDisplayName() { + return "Add missing `@Configuration` annotation"; + } + + @Override + public String getDescription() { + return "Class having `@Bean` annotation over any methods but missing `@Configuration` annotation over the declaring class would have `@Configuration` annotation added."; + } + + @Override + public TreeVisitor getVisitor() { + return Preconditions.check(new UsesType<>(FQN_BEAN, false), new JavaIsoVisitor() { + @Override + public J.ClassDeclaration visitClassDeclaration(J.ClassDeclaration classDecl, ExecutionContext ctx) { + J.ClassDeclaration c = super.visitClassDeclaration(classDecl, ctx); + if (isApplicableClass(c, getCursor())) { + c = addConfigurationAnnotation(c); + } + return c; + } + + private J.ClassDeclaration addConfigurationAnnotation(J.ClassDeclaration c) { + maybeAddImport(FQN_CONFIGURATION); + return JavaTemplate.builder("@" + CONFIGURATION_SIMPLE_NAME) + .imports(FQN_CONFIGURATION) + .javaParser(JavaParser.fromJavaVersion().dependsOn("package " + CONFIGURATION_PACKAGE + + "; public @interface " + CONFIGURATION_SIMPLE_NAME + " {}")) + .build().apply( + getCursor(), + c.getCoordinates().addAnnotation(Comparator.comparing(J.Annotation::getSimpleName)) + ); + } + }); + } + + public static boolean isApplicableClass(J.ClassDeclaration classDecl, Cursor cursor) { + if (classDecl.getKind() != J.ClassDeclaration.Kind.Type.Class) { + return false; + } + + boolean isStatic = false; + for (J.Modifier m : classDecl.getModifiers()) { + if (m.getType() == J.Modifier.Type.Abstract) { + return false; + } else if (m.getType() == J.Modifier.Type.Static) { + isStatic = true; + } + } + + if (!isStatic) { + // no static keyword? check if it is top level class in the CU + Object enclosing = cursor.dropParentUntil(it -> it instanceof J.ClassDeclaration || it == Cursor.ROOT_VALUE).getValue(); + if (enclosing instanceof J.ClassDeclaration) { + return false; + } + } + + // check if '@Configuration' is already over the class + for (J.Annotation a : classDecl.getLeadingAnnotations()) { + JavaType.FullyQualified aType = TypeUtils.asFullyQualified(a.getType()); + if (aType != null && CONFIGURATION_ANNOTATION_MATCHER.matchesAnnotationOrMetaAnnotation(aType)) { + // Found '@Configuration' annotation + return false; + } + } + // No '@Configuration' present. Check if any methods have '@Bean' annotation + for (Statement s : classDecl.getBody().getStatements()) { + if (s instanceof J.MethodDeclaration) { + if (isBeanMethod((J.MethodDeclaration) s)) { + return true; + } + } + } + + return false; + } + + private static boolean isBeanMethod(J.MethodDeclaration methodDecl) { + for (J.Modifier m : methodDecl.getModifiers()) { + if (m.getType() == J.Modifier.Type.Abstract || m.getType() == J.Modifier.Type.Static) { + return false; + } + } + for (J.Annotation a : methodDecl.getLeadingAnnotations()) { + JavaType.FullyQualified aType = TypeUtils.asFullyQualified(a.getType()); + if (aType != null && BEAN_ANNOTATION_MATCHER.matchesAnnotationOrMetaAnnotation(aType)) { + return true; + } + } + return false; + } +} diff --git a/headless-services/commons/commons-rewrite/src/main/java/org/openrewrite/java/spring/boot2/UnnecessarySpringExtension.java b/headless-services/commons/commons-rewrite/src/main/java/org/openrewrite/java/spring/boot2/UnnecessarySpringExtension.java new file mode 100644 index 0000000000..0d45315ac8 --- /dev/null +++ b/headless-services/commons/commons-rewrite/src/main/java/org/openrewrite/java/spring/boot2/UnnecessarySpringExtension.java @@ -0,0 +1,113 @@ +/* + * Copyright 2021 the original author or authors. + *

+ * 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 + *

+ * https://www.apache.org/licenses/LICENSE-2.0 + *

+ * 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.boot2; + +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.RemoveAnnotation; +import org.openrewrite.java.search.FindAnnotations; +import org.openrewrite.java.search.UsesType; +import org.openrewrite.java.tree.J; +import org.openrewrite.java.tree.JavaType; +import org.openrewrite.java.tree.TypeUtils; + +import java.util.Arrays; +import java.util.List; +import java.util.concurrent.atomic.AtomicBoolean; + +public class UnnecessarySpringExtension extends Recipe { + + // All the following annotations apply the @SpringExtension + private static final List SPRING_BOOT_TEST_ANNOTATIONS = Arrays.asList( + "org.springframework.boot.test.context.SpringBootTest", + "org.springframework.boot.test.autoconfigure.jdbc.JdbcTest", + "org.springframework.boot.test.autoconfigure.web.client.RestClientTest", + "org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest", + "org.springframework.boot.test.autoconfigure.web.reactive.WebFluxTest", + "org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest", + "org.springframework.boot.test.autoconfigure.webservices.client.WebServiceClientTest", + "org.springframework.boot.test.autoconfigure.jooq.JooqTest", + "org.springframework.boot.test.autoconfigure.json.JsonTest", + "org.springframework.boot.test.autoconfigure.data.cassandra.DataCassandraTest", + "org.springframework.boot.test.autoconfigure.data.jdbc.DataJdbcTest", + "org.springframework.boot.test.autoconfigure.data.ldap.DataLdapTest", + "org.springframework.boot.test.autoconfigure.data.mongo.DataMongoTest", + "org.springframework.boot.test.autoconfigure.data.neo4j.DataNeo4jTest", + "org.springframework.boot.test.autoconfigure.data.r2dbc.DataR2dbcTest", + "org.springframework.boot.test.autoconfigure.data.redis.DataRedisTest", + "org.springframework.batch.test.context.SpringBatchTest", + "org.springframework.test.context.junit.jupiter.SpringJUnitConfig" + ); + private static final String EXTEND_WITH_SPRING_EXTENSION_ANNOTATION_PATTERN = "@org.junit.jupiter.api.extension.ExtendWith(org.springframework.test.context.junit.jupiter.SpringExtension.class)"; + + @Override + public String getDisplayName() { + return "Remove `@SpringExtension`"; + } + + @Override + public String getDescription() { + return "`@SpringBootTest` and all test slice annotations already applies `@SpringExtension` as of Spring Boot 2.1.0."; + } + + @Override + public TreeVisitor getVisitor() { + return Preconditions.check(new UsesType<>("org.springframework.test.context.junit.jupiter.SpringExtension", false), + new JavaIsoVisitor() { + @Override + public J.ClassDeclaration visitClassDeclaration(J.ClassDeclaration classDecl, ExecutionContext ctx) { + + // Clear the class body to make annotation search and replace faster + // noinspection ConstantConditions + J.ClassDeclaration c = classDecl.withBody(null); + + AtomicBoolean annotationFound = new AtomicBoolean(false); + new FindBootTestAnnotation().visit(c, annotationFound); + + if (annotationFound.get()) { + if (!FindAnnotations.find(c, EXTEND_WITH_SPRING_EXTENSION_ANNOTATION_PATTERN).isEmpty()) { + c = (J.ClassDeclaration) new RemoveAnnotation(EXTEND_WITH_SPRING_EXTENSION_ANNOTATION_PATTERN) + .getVisitor().visit(c, ctx, getCursor().getParentOrThrow()); + assert c != null; + maybeRemoveImport("org.springframework.test.context.junit.jupiter.SpringExtension"); + maybeRemoveImport("org.junit.jupiter.api.extension.ExtendWith"); + return super.visitClassDeclaration(c.withBody(classDecl.getBody()), ctx); + } + } + return super.visitClassDeclaration(classDecl, ctx); + } + }); + } + + // Using this visitor vs making 15 calls to findAnnotations. + private static class FindBootTestAnnotation extends JavaIsoVisitor { + + @Override + public J.Annotation visitAnnotation(J.Annotation annotation, AtomicBoolean found) { + J.Annotation a = super.visitAnnotation(annotation, found); + if (!found.get()) { + JavaType.FullyQualified fullyQualified = TypeUtils.asFullyQualified(a.getType()); + if (fullyQualified != null && SPRING_BOOT_TEST_ANNOTATIONS.contains(fullyQualified.getFullyQualifiedName())) { + found.set(true); + } + } + return a; + } + } +} diff --git a/headless-services/commons/commons-rewrite/src/main/java/org/openrewrite/java/spring/boot3/PreciseBeanType.java b/headless-services/commons/commons-rewrite/src/main/java/org/openrewrite/java/spring/boot3/PreciseBeanType.java new file mode 100644 index 0000000000..d8814ccd0f --- /dev/null +++ b/headless-services/commons/commons-rewrite/src/main/java/org/openrewrite/java/spring/boot3/PreciseBeanType.java @@ -0,0 +1,115 @@ +/* + * Copyright 2022 the original author or authors. + *

+ * 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 + *

+ * https://www.apache.org/licenses/LICENSE-2.0 + *

+ * 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.boot3; + +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 org.openrewrite.java.tree.TypeTree; +import org.openrewrite.java.tree.TypeUtils; + +public class PreciseBeanType extends Recipe { + private static final String BEAN = "org.springframework.context.annotation.Bean"; + + private static final String MSG_KEY = "returnType"; + + @Override + public String getDisplayName() { + return "Bean methods should return concrete types"; + } + + @Override + public String getDescription() { + return "Replace Bean method return types with concrete types being returned. This is required for Spring 6 AOT."; + } + + @Override + public TreeVisitor getVisitor() { + return Preconditions.check(new UsesType<>(BEAN, false), new JavaIsoVisitor() { + @Override + public J.MethodDeclaration visitMethodDeclaration(J.MethodDeclaration method, ExecutionContext ctx) { + J.MethodDeclaration m = super.visitMethodDeclaration(method, ctx); + Object o = getCursor().pollMessage(MSG_KEY); + if (o != null && (method.getReturnTypeExpression() != null && !o.equals(method.getReturnTypeExpression().getType())) && isBeanMethod(m)) { + if (o instanceof JavaType.FullyQualified) { + JavaType.FullyQualified actualType = (JavaType.FullyQualified) o; + if (m.getReturnTypeExpression() instanceof J.Identifier) { + J.Identifier identifierReturnExpr = (J.Identifier) m.getReturnTypeExpression(); + maybeAddImport(actualType); + if (identifierReturnExpr.getType() instanceof JavaType.FullyQualified) { + maybeRemoveImport((JavaType.FullyQualified) identifierReturnExpr.getType()); + } + m = m.withReturnTypeExpression(identifierReturnExpr + .withType(actualType) + .withSimpleName(actualType.getClassName()) + ); + } else if (m.getReturnTypeExpression() instanceof J.ParameterizedType) { + J.ParameterizedType parameterizedType = (J.ParameterizedType) m.getReturnTypeExpression(); + maybeAddImport(actualType); + if (parameterizedType.getType() instanceof JavaType.FullyQualified) { + maybeRemoveImport((JavaType.FullyQualified) parameterizedType.getType()); + } + m = m.withReturnTypeExpression(parameterizedType + .withType(actualType) + .withClazz(TypeTree.build(actualType.getClassName()).withType(actualType)) + ); + } + + } else if (o instanceof JavaType.Array) { + JavaType.Array actualType = (JavaType.Array) o; + if (m.getReturnTypeExpression() instanceof J.ArrayType && actualType.getElemType() instanceof JavaType.FullyQualified) { + JavaType.FullyQualified actualElementType = (JavaType.FullyQualified) actualType.getElemType(); + J.ArrayType arrayType = (J.ArrayType) m.getReturnTypeExpression(); + maybeAddImport(actualElementType); + if (arrayType.getElementType() instanceof JavaType.FullyQualified) { + maybeRemoveImport((JavaType.FullyQualified) arrayType.getElementType()); + } + m = m.withReturnTypeExpression(arrayType + .withElementType(TypeTree.build(actualElementType.getClassName()).withType(actualType)) + ); + } + } + } + return m; + } + + private boolean isBeanMethod(J.MethodDeclaration m) { + for (J.Annotation leadingAnnotation : m.getLeadingAnnotations()) { + if (TypeUtils.isOfClassType(leadingAnnotation.getType(), BEAN)) { + return true; + } + } + return false; + } + + @Override + public J.Return visitReturn(J.Return _return, ExecutionContext ctx) { + if (_return.getExpression() != null && _return.getExpression().getType() != null) { + Cursor methodCursor = getCursor(); + while (methodCursor != null && !(methodCursor.getValue() instanceof J.Lambda || methodCursor.getValue() instanceof J.MethodDeclaration)) { + methodCursor = methodCursor.getParent(); + } + if (methodCursor != null && methodCursor.getValue() instanceof J.MethodDeclaration) { + methodCursor.putMessage(MSG_KEY, _return.getExpression().getType()); + } + } + return super.visitReturn(_return, ctx); + } + }); + } +} diff --git a/headless-services/commons/commons-rewrite/src/main/java/org/openrewrite/java/spring/framework/BeanMethodsNotPublic.java b/headless-services/commons/commons-rewrite/src/main/java/org/openrewrite/java/spring/framework/BeanMethodsNotPublic.java new file mode 100644 index 0000000000..eb3cc38889 --- /dev/null +++ b/headless-services/commons/commons-rewrite/src/main/java/org/openrewrite/java/spring/framework/BeanMethodsNotPublic.java @@ -0,0 +1,59 @@ +/* + * Copyright 2021 the original author or authors. + *

+ * 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 + *

+ * https://www.apache.org/licenses/LICENSE-2.0 + *

+ * 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.framework; + +import org.openrewrite.ExecutionContext; +import org.openrewrite.Preconditions; +import org.openrewrite.Recipe; +import org.openrewrite.TreeVisitor; +import org.openrewrite.java.AnnotationMatcher; +import org.openrewrite.java.ChangeMethodAccessLevelVisitor; +import org.openrewrite.java.JavaIsoVisitor; +import org.openrewrite.java.MethodMatcher; +import org.openrewrite.java.search.UsesType; +import org.openrewrite.java.service.AnnotationService; +import org.openrewrite.java.tree.J; +import org.openrewrite.java.tree.TypeUtils; + +public class BeanMethodsNotPublic extends Recipe { + private static final String BEAN = "org.springframework.context.annotation.Bean"; + private static final AnnotationMatcher BEAN_ANNOTATION_MATCHER = new AnnotationMatcher("@" + BEAN); + + @Override + public String getDisplayName() { + return "Remove `public` from `@Bean` methods"; + } + + @Override + public String getDescription() { + return "Remove public modifier from `@Bean` methods. They no longer have to be public visibility to be usable by Spring."; + } + + @Override + public TreeVisitor getVisitor() { + return Preconditions.check(new UsesType<>(BEAN, false), new JavaIsoVisitor() { + @Override + public J.MethodDeclaration visitMethodDeclaration(J.MethodDeclaration method, ExecutionContext ctx) { + if (service(AnnotationService.class).matches(getCursor(), BEAN_ANNOTATION_MATCHER) && + !TypeUtils.isOverride(method.getMethodType())) { + // remove public modifier and copy any associated comments to the method + doAfterVisit(new ChangeMethodAccessLevelVisitor<>(new MethodMatcher(method), null)); + } + return super.visitMethodDeclaration(method, ctx); + } + }); + } +} diff --git a/headless-services/commons/commons-rewrite/src/main/java/org/openrewrite/java/spring/security5/AuthorizeHttpRequests.java b/headless-services/commons/commons-rewrite/src/main/java/org/openrewrite/java/spring/security5/AuthorizeHttpRequests.java new file mode 100644 index 0000000000..95c85545d0 --- /dev/null +++ b/headless-services/commons/commons-rewrite/src/main/java/org/openrewrite/java/spring/security5/AuthorizeHttpRequests.java @@ -0,0 +1,142 @@ +/* + * Copyright 2023 the original author or authors. + *

+ * 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 + *

+ * https://www.apache.org/licenses/LICENSE-2.0 + *

+ * 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.security5; + +import org.openrewrite.Cursor; +import org.openrewrite.ExecutionContext; +import org.openrewrite.Recipe; +import org.openrewrite.TreeVisitor; +import org.openrewrite.java.ChangeType; +import org.openrewrite.java.JavaVisitor; +import org.openrewrite.java.MethodMatcher; +import org.openrewrite.java.tree.*; +import org.openrewrite.marker.Markers; + +import java.util.ArrayList; +import java.util.List; + +public class AuthorizeHttpRequests extends Recipe { + + private static final String MSG_ADD_COMMENT = "add-comment"; + + private static final String AUTHORIZE_HTTP_REQUESTS = "authorizeHttpRequests"; + + private static final MethodMatcher MATCH_AUTHORIZE_REQUESTS = new MethodMatcher( + "org.springframework.security.config.annotation.web.builders.HttpSecurity authorizeRequests(..)"); + + private static final MethodMatcher MATCH_ACCESS_DECISION_MANAGER = new MethodMatcher( + "org.springframework.security.config.annotation.web.configurers.AbstractInterceptUrlConfigurer$AbstractInterceptUrlRegistry accessDecisionManager(..)"); + + @Override + public String getDisplayName() { + return "Replace `HttpSecurity.authorizeRequests(...)` with `HttpSecurity.authorizeHttpRequests(...)` and `ExpressionUrlAuthorizationConfigurer`, `AbstractInterceptUrlConfigurer` with `AuthorizeHttpRequestsConfigurer`, etc"; + } + + @Override + public String getDescription() { + return "Replace `HttpSecurity.authorizeRequests(...)` deprecated in Spring Security 6 with `HttpSecurity.authorizeHttpRequests(...)` and all method calls on the resultant object respectively. Replace deprecated `AbstractInterceptUrlConfigurer` and its deprecated subclasses with `AuthorizeHttpRequestsConfigurer` and its corresponding subclasses."; + } + + @Override + public TreeVisitor getVisitor() { + return new JavaVisitor() { + private void changeTypesAfterVisit() { + doAfterVisit(new ChangeType( + "org.springframework.security.config.annotation.web.configurers.ExpressionUrlAuthorizationConfigurer.ExpressionInterceptUrlRegistry", + "org.springframework.security.config.annotation.web.configurers.AuthorizeHttpRequestsConfigurer.AuthorizationManagerRequestMatcherRegistry", + false).getVisitor()); + doAfterVisit(new ChangeType( + "org.springframework.security.config.annotation.web.configurers.ExpressionUrlAuthorizationConfigurer", + "org.springframework.security.config.annotation.web.configurers.AuthorizeHttpRequestsConfigurer", + false).getVisitor()); + doAfterVisit(new ChangeType( + "org.springframework.security.config.annotation.web.configurers.AbstractInterceptUrlConfigurer", + "org.springframework.security.config.annotation.web.configurers.AuthorizeHttpRequestsConfigurer", + false).getVisitor()); + } + + @Override + public J visitMethodInvocation(J.MethodInvocation method, ExecutionContext ctx) { + J visited = super.visitMethodInvocation(method, ctx); + if (visited instanceof J.MethodInvocation) { + J.MethodInvocation m = (J.MethodInvocation) visited; + JavaType.Method methodType = method.getMethodType(); + if (methodType != null) { + if (MATCH_AUTHORIZE_REQUESTS.matches(methodType)) { + changeTypesAfterVisit(); + return processAuthorizeRequests(m); + } else if (MATCH_ACCESS_DECISION_MANAGER.matches(methodType)) { + changeTypesAfterVisit(); + return processAccessDecisionManager(m, ctx); + } + } + String commentToAdd = getCursor().pollMessage(MSG_ADD_COMMENT); + if (commentToAdd != null) { + return addTextCommentAfterSelect(m, commentToAdd); + } + } + return visited; + } + + private J.MethodInvocation processAuthorizeRequests(J.MethodInvocation m) { + JavaType.Method methodType = m.getMethodType(); + JavaType.Method newMethodType = methodType.getDeclaringType().getMethods().stream() + .filter(nm -> AUTHORIZE_HTTP_REQUESTS.equals(nm.getName())) + .filter(nm -> nm.getParameterTypes().size() == methodType.getParameterTypes().size()) + .findFirst().orElse(null); + if (newMethodType != null) { + m = m + .withName(m.getName().withSimpleName(AUTHORIZE_HTTP_REQUESTS)) + .withMethodType(newMethodType); + } + return m; + } + + private J processAccessDecisionManager(J.MethodInvocation m, ExecutionContext ctx) { + StringBuilder commentText = new StringBuilder(); + commentText.append("TODO: replace removed '."); + commentText.append(m.getSimpleName()); + commentText.append('('); + commentText.append(String.join(", ", m.getArguments().stream().map(a -> a.print(getCursor())).toArray(String[]::new))); + commentText.append(");' with appropriate call to 'access(AuthorizationManager)' after antMatcher(...) call etc."); + + List newComments = new ArrayList<>(m.getComments()); + newComments.addAll(m.getSelect().getComments()); + + Expression selectExpr = m.getSelect(); + Cursor parentInvocationCursor = getCursor().getParent(2); + if (parentInvocationCursor == null || !(parentInvocationCursor.getValue() instanceof J.MethodInvocation)) { + // top level method invocation + newComments.add(new TextComment(true, commentText.toString(), newComments.isEmpty() ? "\n" + m.getPrefix().getIndent() : newComments.get(0).getSuffix(), Markers.EMPTY)); + selectExpr = selectExpr.withPrefix(m.getPrefix()); + } else { + // parent is method invocation + parentInvocationCursor.putMessage(MSG_ADD_COMMENT, commentText.toString()); + } + return selectExpr.withComments(newComments); + } + + private J.MethodInvocation addTextCommentAfterSelect(J.MethodInvocation m, String s) { + J.MethodInvocation.Padding padding = m.getPadding(); + Space afterSelect = padding.getSelect().getAfter(); + List newComments = new ArrayList<>(afterSelect.getComments()); + newComments.add(new TextComment(true, s, newComments.isEmpty() ? "\n" + afterSelect.getIndent() : newComments.get(0).getSuffix(), Markers.EMPTY)); + JRightPadded paddedSelect = padding.getSelect().withAfter(afterSelect.withComments(newComments)); + return new J.MethodInvocation(m.getId(), m.getPrefix(), m.getMarkers(), paddedSelect, padding.getTypeParameters(), m.getName(), padding.getArguments(), m.getMethodType()); + } + }; + } +} diff --git a/headless-services/commons/commons-rewrite/src/main/java/org/openrewrite/java/spring/security5/ConvertToSecurityDslVisitor.java b/headless-services/commons/commons-rewrite/src/main/java/org/openrewrite/java/spring/security5/ConvertToSecurityDslVisitor.java new file mode 100644 index 0000000000..c08efd3ab0 --- /dev/null +++ b/headless-services/commons/commons-rewrite/src/main/java/org/openrewrite/java/spring/security5/ConvertToSecurityDslVisitor.java @@ -0,0 +1,306 @@ +/* + * Copyright 2023 the original author or authors. + *

+ * 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 + *

+ * https://www.apache.org/licenses/LICENSE-2.0 + *

+ * 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.security5; + +import org.jspecify.annotations.Nullable; +import org.openrewrite.Cursor; +import org.openrewrite.Tree; +import org.openrewrite.internal.ListUtils; +import org.openrewrite.internal.StringUtils; +import org.openrewrite.java.JavaIsoVisitor; +import org.openrewrite.java.MethodMatcher; +import org.openrewrite.java.tree.*; +import org.openrewrite.marker.Markers; +import org.openrewrite.marker.Markup; + +import java.util.*; + +import static java.util.Collections.emptyList; +import static java.util.Objects.requireNonNull; + +public class ConvertToSecurityDslVisitor

extends JavaIsoVisitor

{ + + private static final String MSG_FLATTEN_CHAIN = "http-security-dsl-flatten-invocation-chain"; + private static final String MSG_TOP_INVOCATION = "top-method-invocation"; + + private static final String FQN_CUSTOMIZER = "org.springframework.security.config.Customizer"; + private static final JavaType.FullyQualified CUSTOMIZER_SHALLOW_TYPE = JavaType.ShallowClass.build(FQN_CUSTOMIZER); + + private static final MethodMatcher XSS_PROTECTION_ENABLED = new MethodMatcher("org.springframework.security.config.annotation.web.configurers.HeadersConfigurer.XXssConfig xssProtectionEnabled(boolean)"); + + private final String securityFqn; + private final Collection convertableMethods; + + /** + * Optionally used to determine the behavior for any convertableMethods which have an argument. + * Each key should be a method name from convertableMethods. + * A non-null value will be used to create a new methodInvocation with that name, + * and the existing arg will be moved to that new methodInvocation. + * A null value will keep the existing argument in the converted method. + */ + private final Map argReplacements; + + /** + * Optionally used to specify replacement method names if they do not match the original method names + */ + private final Map methodRenames; + + public ConvertToSecurityDslVisitor(String securityFqn, Collection convertableMethods) { + this(securityFqn, convertableMethods, new HashMap<>()); + } + + public ConvertToSecurityDslVisitor(String securityFqn, Collection convertableMethods, + Map argReplacements) { + this(securityFqn, convertableMethods, argReplacements, new HashMap<>()); + } + + public ConvertToSecurityDslVisitor(String securityFqn, Collection convertableMethods, + Map argReplacements, Map methodRenames) { + this.securityFqn = securityFqn; + this.convertableMethods = convertableMethods; + this.argReplacements = argReplacements; + this.methodRenames = methodRenames; + } + + @Override + public J.MethodInvocation visitMethodInvocation(J.MethodInvocation initialMethod, P executionContext) { + J.MethodInvocation method = super.visitMethodInvocation(initialMethod, executionContext); + if (isApplicableMethod(method)) { + J.MethodInvocation m = method; + method = createDesiredReplacement(method) + .map(newMethodType -> { + List chain = computeAndMarkChain(); + boolean keepArg = keepArg(m.getSimpleName()); + String paramName = keepArg ? "configurer" : generateParamNameFromMethodName(m.getSimpleName()); + return m + .withMethodType(newMethodType) + .withName(m.getName().withSimpleName(newMethodType.getName())) + .withArguments(ListUtils.concat( + keepArg ? m.getArguments().get(0) : null, + Collections.singletonList(chain.isEmpty() ? + createDefaultsCall() : + createLambdaParam(paramName, newMethodType.getParameterTypes().get(keepArg ? 1 : 0), chain)) + ) + ); + }) + .orElse(method); + } + Boolean msg = getCursor().pollMessage(MSG_FLATTEN_CHAIN); + if (Boolean.TRUE.equals(msg)) { + method = requireNonNull(method.getSelect()) + .withPrefix(method.getPrefix()) + .withComments(method.getComments()); + } + // Auto-format the top invocation call if anything has changed down the tree + Cursor grandParent = getCursor().getParent(2); + if (initialMethod != method && (grandParent == null || !(grandParent.getValue() instanceof J.MethodInvocation))) { + method = autoFormat(method, executionContext); + } + return method; + } + + private static String generateParamNameFromMethodName(String n) { + int i = n.length() - 1; + //noinspection StatementWithEmptyBody + for (; i >= 0 && Character.isLowerCase(n.charAt(i)); i--) {} + if (i >= 0) { + return StringUtils.uncapitalize(i == 0 ? n : n.substring(i)); + } + return n; + } + + private J.Lambda createLambdaParam(String paramName, JavaType paramType, List chain) { + J.Identifier param = createIdentifier(paramName, paramType); + J.MethodInvocation body = unfoldMethodInvocationChain(createIdentifier(paramName, paramType), chain); + return new J.Lambda(Tree.randomId(), Space.EMPTY, Markers.EMPTY, + new J.Lambda.Parameters(Tree.randomId(), Space.EMPTY, Markers.EMPTY, false, Collections.singletonList(new JRightPadded<>(param, Space.EMPTY, Markers.EMPTY))), + Space.build(" ", emptyList()), + body, + JavaType.Primitive.Void + ); + } + + private J.Identifier createIdentifier(String name, JavaType type) { + return new J.Identifier(Tree.randomId(), Space.EMPTY, Markers.EMPTY, emptyList(), name, type, null); + } + + private J.MethodInvocation unfoldMethodInvocationChain(J.Identifier core, List chain) { + Expression select = core; + J.MethodInvocation invocation = null; + for (J.MethodInvocation inv : chain) { + invocation = inv.withSelect(select); + if (XSS_PROTECTION_ENABLED.matches(invocation)) { + if (J.Literal.isLiteralValue(invocation.getArguments().get(0), false)) { + invocation = invocation.withName(invocation.getName().withSimpleName("disable")).withArguments(null); + JavaType.Method methodType = invocation.getMethodType(); + if (methodType != null) { + methodType = methodType.withParameterNames(emptyList()).withParameterTypes(emptyList()); + invocation = invocation.withMethodType(methodType).withName(invocation.getName().withType(methodType)); + } + } else { + // Enabled by default; but returning `null` will cause issues, so we use `and()` as a placeholder + invocation = invocation.withName(invocation.getName().withSimpleName("and")).withArguments(null); + JavaType.Method methodType = invocation.getMethodType(); + if (methodType != null) { + methodType = methodType.withParameterNames(emptyList()).withParameterTypes(emptyList()); + invocation = invocation.withMethodType(methodType).withName(invocation.getName().withType(methodType)); + } + } + } + select = invocation; + } + // Check if top-level invocation to remove the prefix as the prefix is space before the root call, i.e. before httpSecurity identifier. We don't want to have inside the lambda + assert invocation != null; + if (invocation.getMarkers().getMarkers().stream().filter(Markup.Info.class::isInstance).map(Markup.Info.class::cast).anyMatch(marker -> MSG_TOP_INVOCATION.equals(marker.getMessage()))) { + invocation = invocation + .withMarkers(invocation.getMarkers().removeByType(Markup.Info.class)) + .withPrefix(Space.EMPTY); + } + return invocation; + } + + private boolean isApplicableMethod(J.MethodInvocation m) { + JavaType.Method type = m.getMethodType(); + if (type != null) { + JavaType.FullyQualified declaringType = type.getDeclaringType(); + return securityFqn.equals(declaringType.getFullyQualifiedName()) && + (type.getParameterTypes().isEmpty() || hasHandleableArg(m)) && + convertableMethods.contains(m.getSimpleName()); + } + return false; + } + + private boolean hasHandleableArg(J.MethodInvocation m) { + return argReplacements.containsKey(m.getSimpleName()) && + m.getMethodType() != null && + m.getMethodType().getParameterTypes().size() == 1 && + !TypeUtils.isAssignableTo(FQN_CUSTOMIZER, m.getMethodType().getParameterTypes().get(0)); + } + + private Optional createDesiredReplacement(J.MethodInvocation m) { + JavaType.Method methodType = m.getMethodType(); + if (methodType == null) { + return Optional.empty(); + } + JavaType.Parameterized customizerArgType = new JavaType.Parameterized(null, + CUSTOMIZER_SHALLOW_TYPE, Collections.singletonList(methodType.getReturnType())); + boolean keepArg = keepArg(m.getSimpleName()); + List paramNames = keepArg ? ListUtils.concat(methodType.getParameterNames(), "arg1") : + Collections.singletonList("arg0"); + List paramTypes = keepArg ? ListUtils.concat(methodType.getParameterTypes(), customizerArgType) : + Collections.singletonList(customizerArgType); + return Optional.of(methodType.withReturnType(methodType.getDeclaringType()) + .withName(methodRenames.getOrDefault(methodType.getName(), methodType.getName())) + .withParameterNames(paramNames) + .withParameterTypes(paramTypes) + ); + } + + private boolean keepArg(String methodName) { + return argReplacements.containsKey(methodName) && argReplacements.get(methodName) == null; + } + + private Optional createDesiredReplacementForArg(J.MethodInvocation m) { + JavaType.Method methodType = m.getMethodType(); + if (methodType == null || !hasHandleableArg(m) || keepArg(m.getSimpleName()) || !(methodType.getReturnType() instanceof JavaType.Class)) { + return Optional.empty(); + } + return Optional.of( + methodType.withName(argReplacements.get(m.getSimpleName())) + .withDeclaringType((JavaType.FullyQualified) methodType.getReturnType()) + ); + } + + // this method is unused in this repo, but, useful in Spring Tool Suite integration + @SuppressWarnings("unused") + public boolean isApplicableTopLevelMethodInvocation(J.MethodInvocation m) { + if (isApplicableMethod(m)) { + return true; + } else if (m.getSelect() instanceof J.MethodInvocation) { + return isApplicableTopLevelMethodInvocation((J.MethodInvocation) m.getSelect()); + } + return false; + } + + private boolean isApplicableCallCursor(@Nullable Cursor c) { + if (c == null) { + return false; + } + + if (!(c.getValue() instanceof J.MethodInvocation)) { + return false; + } + + J.MethodInvocation inv = c.getValue(); + return !isAndMethod(inv) && !isDisableMethod(inv); + } + + private List computeAndMarkChain() { + List chain = new ArrayList<>(); + Cursor cursor = getCursor(); + J.MethodInvocation initialMethodInvocation = cursor.getValue(); + createDesiredReplacementForArg(initialMethodInvocation).ifPresent(methodType -> + chain.add(initialMethodInvocation.withName( + initialMethodInvocation.getName().withType(methodType).withSimpleName(methodType.getName())))); + cursor = cursor.getParent(2); + for (; isApplicableCallCursor(cursor); cursor = cursor.getParent(2)) { + cursor.putMessage(MSG_FLATTEN_CHAIN, true); + chain.add(cursor.getValue()); + } + if (cursor != null && cursor.getValue() instanceof J.MethodInvocation) { + if (isAndMethod(cursor.getValue())) { + cursor.putMessage(MSG_FLATTEN_CHAIN, true); + cursor = cursor.getParent(2); + } else if (isDisableMethod(cursor.getValue())) { + cursor.putMessage(MSG_FLATTEN_CHAIN, true); + chain.add(cursor.getValue()); + cursor = cursor.getParent(2); + } + } + if (cursor == null || chain.isEmpty()) { + return emptyList(); + } + if (!(cursor.getValue() instanceof J.MethodInvocation)) { + // top invocation is at the end of the chain - mark it. We'd need to strip off prefix from this invocation later + J.MethodInvocation topInvocation = chain.remove(chain.size() - 1); + // removed above, now add it back with the marker + chain.add(topInvocation.withMarkers(topInvocation.getMarkers().addIfAbsent(new Markup.Info(Tree.randomId(), MSG_TOP_INVOCATION, null)))); + } + return chain; + } + + private boolean isAndMethod(J.MethodInvocation method) { + return "and".equals(method.getSimpleName()) && + (method.getArguments().isEmpty() || method.getArguments().get(0) instanceof J.Empty) && + TypeUtils.isAssignableTo(securityFqn, method.getType()); + } + + private boolean isDisableMethod(J.MethodInvocation method) { + return new MethodMatcher("org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer disable()", true).matches(method); + } + + private J.MethodInvocation createDefaultsCall() { + JavaType.Method methodType = new JavaType.Method(null, 9, CUSTOMIZER_SHALLOW_TYPE, "withDefaults", + new JavaType.GenericTypeVariable(null, "T", JavaType.GenericTypeVariable.Variance.INVARIANT, null), + Collections.emptyList(), Collections.emptyList(), null, null, null, null); + maybeAddImport(methodType.getDeclaringType().getFullyQualifiedName(), methodType.getName()); + return new J.MethodInvocation(Tree.randomId(), Space.EMPTY, Markers.EMPTY, null, null, + new J.Identifier(Tree.randomId(), Space.EMPTY, Markers.EMPTY, emptyList(), "withDefaults", null, null), + JContainer.empty(), methodType) + .withSelect(null); + } + +} diff --git a/headless-services/commons/commons-rewrite/src/main/java/org/openrewrite/java/spring/security5/HttpSecurityLambdaDsl.java b/headless-services/commons/commons-rewrite/src/main/java/org/openrewrite/java/spring/security5/HttpSecurityLambdaDsl.java new file mode 100644 index 0000000000..079736256b --- /dev/null +++ b/headless-services/commons/commons-rewrite/src/main/java/org/openrewrite/java/spring/security5/HttpSecurityLambdaDsl.java @@ -0,0 +1,55 @@ +/* + * Copyright 2023 the original author or authors. + *

+ * 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 + *

+ * https://www.apache.org/licenses/LICENSE-2.0 + *

+ * 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.security5; + +import org.openrewrite.ExecutionContext; +import org.openrewrite.Preconditions; +import org.openrewrite.Recipe; +import org.openrewrite.TreeVisitor; +import org.openrewrite.java.search.UsesType; + +import java.util.Arrays; +import java.util.Collection; + +public final class HttpSecurityLambdaDsl extends Recipe { + + private static final String FQN_HTTP_SECURITY = "org.springframework.security.config.annotation.web.builders.HttpSecurity"; + + private static final Collection APPLICABLE_METHOD_NAMES = Arrays.asList( + "anonymous", "authorizeHttpRequests", "authorizeRequests", "cors", "csrf", "exceptionHandling", "formLogin", + "headers", "httpBasic", "jee", "logout", "oauth2Client", "oauth2Login", "oauth2ResourceServer", + "openidLogin", "portMapper", "rememberMe", "requestCache", "requestMatchers", "requiresChannel", + "saml2Login", "securityContext", "servletApi", "sessionManagement", "x509"); + + @Override + public String getDisplayName() { + return "Convert `HttpSecurity` chained calls into Lambda DSL"; + } + + @Override + public String getDescription() { + return "Converts `HttpSecurity` chained call from Spring Security pre 5.2.x into new lambda DSL style calls and removes `and()` methods."; + } + + @Override + public TreeVisitor getVisitor() { + return Preconditions.check( + new UsesType<>(FQN_HTTP_SECURITY, true), + new ConvertToSecurityDslVisitor<>(FQN_HTTP_SECURITY, APPLICABLE_METHOD_NAMES) + ); + } + +} diff --git a/headless-services/commons/commons-rewrite/src/main/java/org/openrewrite/java/spring/security5/ServerHttpSecurityLambdaDsl.java b/headless-services/commons/commons-rewrite/src/main/java/org/openrewrite/java/spring/security5/ServerHttpSecurityLambdaDsl.java new file mode 100644 index 0000000000..29004feba6 --- /dev/null +++ b/headless-services/commons/commons-rewrite/src/main/java/org/openrewrite/java/spring/security5/ServerHttpSecurityLambdaDsl.java @@ -0,0 +1,54 @@ +/* + * Copyright 2023 the original author or authors. + *

+ * 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 + *

+ * https://www.apache.org/licenses/LICENSE-2.0 + *

+ * 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.security5; + +import org.openrewrite.ExecutionContext; +import org.openrewrite.Preconditions; +import org.openrewrite.Recipe; +import org.openrewrite.TreeVisitor; +import org.openrewrite.java.search.UsesType; + +import java.util.Arrays; +import java.util.Collection; + +public final class ServerHttpSecurityLambdaDsl extends Recipe { + + private static final String FQN_SERVER_HTTP_SECURITY = "org.springframework.security.config.web.server.ServerHttpSecurity"; + + private static final Collection APPLICABLE_METHOD_NAMES = Arrays.asList( + "anonymous", "authorizeExchange", "cors", "csrf", "exceptionHandling", "formLogin", + "headers", "httpBasic", "logout", "oauth2Client", "oauth2Login", "oauth2ResourceServer", + "redirectToHttps", "requestCache", "x509"); + + @Override + public String getDisplayName() { + return "Convert `ServerHttpSecurity` chained calls into Lambda DSL"; + } + + @Override + public String getDescription() { + return "Converts `ServerHttpSecurity` chained call from Spring Security pre 5.2.x into new lambda DSL style calls and removes `and()` methods."; + } + + @Override + public TreeVisitor getVisitor() { + return Preconditions.check( + new UsesType<>(FQN_SERVER_HTTP_SECURITY, true), + new ConvertToSecurityDslVisitor<>(FQN_SERVER_HTTP_SECURITY, APPLICABLE_METHOD_NAMES) + ); + } + +} diff --git a/headless-services/commons/commons-rewrite/src/main/java/org/openrewrite/java/spring/security5/WebSecurityConfigurerAdapter.java b/headless-services/commons/commons-rewrite/src/main/java/org/openrewrite/java/spring/security5/WebSecurityConfigurerAdapter.java new file mode 100644 index 0000000000..03661c23f9 --- /dev/null +++ b/headless-services/commons/commons-rewrite/src/main/java/org/openrewrite/java/spring/security5/WebSecurityConfigurerAdapter.java @@ -0,0 +1,553 @@ +/* + * Copyright 2022 the original author or authors. + *

+ * 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 + *

+ * https://www.apache.org/licenses/LICENSE-2.0 + *

+ * 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.security5; + +import org.jspecify.annotations.Nullable; +import org.openrewrite.*; +import org.openrewrite.internal.ListUtils; +import org.openrewrite.java.JavaIsoVisitor; +import org.openrewrite.java.JavaParser; +import org.openrewrite.java.JavaTemplate; +import org.openrewrite.java.MethodMatcher; +import org.openrewrite.java.search.UsesType; +import org.openrewrite.java.tree.*; +import org.openrewrite.marker.Markers; +import org.openrewrite.marker.SearchResult; + +import java.util.*; +import java.util.concurrent.atomic.AtomicReference; +import java.util.stream.Collectors; + +import static java.util.Collections.emptyList; + +/** + * @author Alex Boyko + */ +public class WebSecurityConfigurerAdapter extends Recipe { + + private static final Collection EXPLICIT_ACCESS_LEVELS = Arrays.asList(J.Modifier.Type.Public, + J.Modifier.Type.Private, J.Modifier.Type.Protected); + + private static final String FQN_CONFIGURATION = "org.springframework.context.annotation.Configuration"; + private static final String FQN_WEB_SECURITY_CONFIGURER_ADAPTER = "org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter"; + private static final String FQN_SECURITY_FILTER_CHAIN = "org.springframework.security.web.SecurityFilterChain"; + private static final String FQN_OVERRIDE = "java.lang.Override"; + private static final String FQN_WEB_SECURITY_CUSTOMIZER = "org.springframework.security.config.annotation.web.configuration.WebSecurityCustomizer"; + private static final String FQN_INMEMORY_AUTH_CONFIG = "org.springframework.security.config.annotation.authentication.configurers.provisioning.InMemoryUserDetailsManagerConfigurer"; + private static final String FQN_INMEMORY_AUTH_MANAGER = "org.springframework.security.provisioning.InMemoryUserDetailsManager"; + private static final String FQN_JDBC_AUTH_CONFIG = "org.springframework.security.config.annotation.authentication.configurers.provisioning.JdbcUserDetailsManagerConfigurer"; + private static final String FQN_LDAP_AUTH_CONFIG = "org.springframework.security.config.annotation.authentication.configurers.ldap.LdapAuthenticationProviderConfigurer"; + private static final String FQN_AUTH_MANAGER_BUILDER = "org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder"; + private static final String FQN_USER = "org.springframework.security.core.userdetails.User"; + private static final String FQN_USER_DETAILS_BUILDER = "org.springframework.security.core.userdetails.User$UserBuilder"; + private static final String FQN_USER_DETAILS = "org.springframework.security.core.userdetails.UserDetails"; + private static final String BEAN_PKG = "org.springframework.context.annotation"; + private static final String BEAN_SIMPLE_NAME = "Bean"; + private static final String FQN_BEAN = BEAN_PKG + "." + BEAN_SIMPLE_NAME; + private static final String BEAN_ANNOTATION = "@" + BEAN_SIMPLE_NAME; + + private static final MethodMatcher CONFIGURE_HTTP_SECURITY_METHOD_MATCHER = + new MethodMatcher("org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter configure(org.springframework.security.config.annotation.web.builders.HttpSecurity)", true); + private static final MethodMatcher CONFIGURE_WEB_SECURITY_METHOD_MATCHER = + new MethodMatcher("org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter configure(org.springframework.security.config.annotation.web.builders.WebSecurity)", true); + private static final MethodMatcher CONFIGURE_AUTH_MANAGER_SECURITY_METHOD_MATCHER = + new MethodMatcher("org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter configure(org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder)", true); + + private static final MethodMatcher USER_DETAILS_SERVICE_BEAN_METHOD_MATCHER = + new MethodMatcher("org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter userDetailsServiceBean()", true); + private static final MethodMatcher AUTHENTICATION_MANAGER_BEAN_METHOD_MATCHER = + new MethodMatcher("org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter authenticationManagerBean()", true); + + private static final MethodMatcher AUTH_INMEMORY_WITH_USER = + new MethodMatcher("org.springframework.security.config.annotation.authentication.configurers.provisioning.UserDetailsManagerConfigurer withUser(..)"); + + private static final String HAS_CONFLICT = "has-conflict"; + + private static final String FLATTEN_CLASSES = "flatten-classes"; + + private enum AuthType { + NONE, + LDAP, + JDBC, + INMEMORY + } + + @Override + public String getDisplayName() { + return "Spring Security 5.4 introduces the ability to configure `HttpSecurity` by creating a `SecurityFilterChain` bean"; + } + + @Override + public String getDescription() { + return "The Spring Security `WebSecurityConfigurerAdapter` was deprecated 5.7, this recipe will transform `WebSecurityConfigurerAdapter` classes by using a component based approach. Check out the [spring-security-without-the-websecurityconfigureradapter](https://spring.io/blog/2022/02/21/spring-security-without-the-websecurityconfigureradapter) blog for more details."; + } + + @Override + public TreeVisitor getVisitor() { + return Preconditions.check(new UsesType<>(FQN_WEB_SECURITY_CONFIGURER_ADAPTER, false), new JavaIsoVisitor() { + + @Override + public J.@Nullable ClassDeclaration visitClassDeclaration(J.ClassDeclaration classDecl, ExecutionContext ctx) { + boolean isWebSecurityConfigurerAdapterClass = TypeUtils.isAssignableTo(FQN_WEB_SECURITY_CONFIGURER_ADAPTER, classDecl.getType()) && + isAnnotatedWith(classDecl.getLeadingAnnotations(), FQN_CONFIGURATION); + boolean hasConflict = false; + if (isWebSecurityConfigurerAdapterClass) { + for (Statement s : classDecl.getBody().getStatements()) { + if (s instanceof J.MethodDeclaration) { + J.MethodDeclaration method = (J.MethodDeclaration) s; + if (isConflictingMethod(method)) { + hasConflict = true; + break; + } + } + } + getCursor().putMessage(HAS_CONFLICT, hasConflict); + maybeRemoveImport(FQN_WEB_SECURITY_CONFIGURER_ADAPTER); + } + classDecl = super.visitClassDeclaration(classDecl, ctx); + if (!isWebSecurityConfigurerAdapterClass) { + classDecl = processAnyClass(classDecl, ctx); + } else if (!hasConflict) { + classDecl = processSecurityAdapterClass(classDecl); + } + return classDecl; + } + + private J.@Nullable ClassDeclaration processSecurityAdapterClass(J.ClassDeclaration classDecl) { + classDecl = classDecl.withExtends(null); + // Flatten configuration classes if applicable + Cursor enclosingClassCursor = getCursor().getParent(); + while (enclosingClassCursor != null && !(enclosingClassCursor.getValue() instanceof J.ClassDeclaration)) { + enclosingClassCursor = enclosingClassCursor.getParent(); + } + if (enclosingClassCursor != null && enclosingClassCursor.getValue() instanceof J.ClassDeclaration) { + J.ClassDeclaration enclosingClass = enclosingClassCursor.getValue(); + if (enclosingClass.getType() != null && isMetaAnnotated(enclosingClass.getType(), FQN_CONFIGURATION, new HashSet<>()) && canMergeClassDeclarations(enclosingClass, classDecl)) { + // can flatten. Outer class is annotated as configuration bean + List classesToFlatten = enclosingClassCursor.getMessage(FLATTEN_CLASSES); + if (classesToFlatten == null) { + classesToFlatten = new ArrayList<>(); + enclosingClassCursor.putMessage(FLATTEN_CLASSES, classesToFlatten); + } + // only applicable to former subclasses of WebSecurityConfigurerAdapter - other classes won't be flattened + classesToFlatten.add(classDecl); + // Remove imports for annotations being removed together with class declaration + // It is impossible in the general case to tell whether some of these annotations might apply to the bean methods + // However, a set of hardcoded annotations can be moved in the future + for (J.Annotation a : classDecl.getLeadingAnnotations()) { + JavaType.FullyQualified type = TypeUtils.asFullyQualified(a.getType()); + if (type != null) { + maybeRemoveImport(type); + } + } + classDecl = null; // remove class + } + } + return classDecl; + } + + private boolean canMergeClassDeclarations(J.ClassDeclaration a, J.ClassDeclaration b) { + Set aVars = getAllVarNames(a); + Set bVars = getAllVarNames(b); + for (String av : aVars) { + if (bVars.contains(av)) { + return false; + } + } + Set aMethods = getAllMethodSignatures(a); + Set bMethods = getAllMethodSignatures(b); + for (String am : aMethods) { + if (bMethods.contains(am)) { + return false; + } + } + return true; + } + + private Set getAllVarNames(J.ClassDeclaration c) { + return c.getBody().getStatements().stream() + .filter(J.VariableDeclarations.class::isInstance) + .map(J.VariableDeclarations.class::cast) + .flatMap(vd -> vd.getVariables().stream()) + .map(v -> v.getName().getSimpleName()) + .collect(Collectors.toSet()); + } + + private Set getAllMethodSignatures(J.ClassDeclaration c) { + return c.getBody().getStatements().stream() + .filter(J.MethodDeclaration.class::isInstance) + .map(J.MethodDeclaration.class::cast) + .map(this::simpleMethodSignature) + .collect(Collectors.toSet()); + } + + private String simpleMethodSignature(J.MethodDeclaration method) { + String fullSignature = MethodMatcher.methodPattern(method); + int firstSpaceIdx = fullSignature.indexOf(' '); + return firstSpaceIdx < 0 ? fullSignature : fullSignature.substring(firstSpaceIdx + 1); + } + + private J.ClassDeclaration processAnyClass(J.ClassDeclaration classDecl, ExecutionContext ctx) { + // regular class case + List toFlatten = getCursor().pollMessage(FLATTEN_CLASSES); + if (toFlatten != null) { + // The message won't be 'null' for a configuration class + List statements = new ArrayList<>(classDecl.getBody().getStatements().size() + toFlatten.size()); + statements.addAll(classDecl.getBody().getStatements()); + for (J.ClassDeclaration fc : toFlatten) { + for (Statement s : fc.getBody().getStatements()) { + if (s instanceof J.MethodDeclaration) { + J.MethodDeclaration m = (J.MethodDeclaration) s; + if (isAnnotatedWith(m.getLeadingAnnotations(), FQN_BEAN) && m.getMethodType() != null) { + JavaType.FullyQualified beanType = TypeUtils.asFullyQualified(m.getMethodType().getReturnType()); + if (beanType == null) { + continue; + } + String uniqueName = computeBeanNameFromClassName(fc.getSimpleName(), beanType.getClassName()); + s = m + .withName(m.getName().withSimpleName(uniqueName)) + .withMethodType(m.getMethodType().withName(uniqueName)); + s = autoFormat(s, ctx, new Cursor(getCursor(), classDecl.getBody())); + } + } + statements.add(s); + } + } + classDecl = classDecl.withBody(classDecl.getBody().withStatements(statements)); + } + return classDecl; + } + + private boolean isConflictingMethod(J.MethodDeclaration m) { + String methodName = m.getSimpleName(); + JavaType.Method methodType = m.getMethodType(); + return (methodType == null && ("authenticationManagerBean".equals(methodName) || "userDetailsServiceBean".equals(methodName))) || + (USER_DETAILS_SERVICE_BEAN_METHOD_MATCHER.matches(methodType) || + AUTHENTICATION_MANAGER_BEAN_METHOD_MATCHER.matches(methodType) || + (CONFIGURE_AUTH_MANAGER_SECURITY_METHOD_MATCHER.matches(methodType) && inConflictingAuthConfigMethod(m))); + } + + private boolean inConflictingAuthConfigMethod(J.MethodDeclaration m) { + AuthType authType = getAuthType(m); + return authType != WebSecurityConfigurerAdapter.AuthType.INMEMORY; + } + + @Override + public J.MethodDeclaration visitMethodDeclaration(J.MethodDeclaration m, ExecutionContext ctx) { + Cursor classCursor = getCursor().dropParentUntil(it -> it instanceof J.ClassDeclaration || it == Cursor.ROOT_VALUE); + if (!(classCursor.getValue() instanceof J.ClassDeclaration)) { + return m; + } + if (isConflictingMethod(m)) { + m = SearchResult.found(m, "Migrate manually based on https://spring.io/blog/2022/02/21/spring-security-without-the-websecurityconfigureradapter"); + } else if (!classCursor.getMessage(HAS_CONFLICT, true)) { + J.ClassDeclaration c = classCursor.getValue(); + if (CONFIGURE_HTTP_SECURITY_METHOD_MATCHER.matches(m, c)) { + m = changeToBeanMethod(m, c, FQN_SECURITY_FILTER_CHAIN, "filterChain", true); + } else if (CONFIGURE_WEB_SECURITY_METHOD_MATCHER.matches(m, c)) { + m = changeToBeanMethod(m, c, FQN_WEB_SECURITY_CUSTOMIZER, "webSecurityCustomizer", false); + } else if (CONFIGURE_AUTH_MANAGER_SECURITY_METHOD_MATCHER.matches(m, c)) { + AuthType authType = getAuthType(m); + switch (authType) { + case INMEMORY: + m = changeToBeanMethod(m, c, FQN_INMEMORY_AUTH_MANAGER, "inMemoryAuthManager", false); + break; + case JDBC: + //TODO: implement + break; + case LDAP: + //TODO: implement + break; + default: + throw new IllegalStateException(); + } + } + } + return super.visitMethodDeclaration(m, ctx); + } + + private J.MethodDeclaration changeToBeanMethod(J.MethodDeclaration m, J.ClassDeclaration c, String fqnReturnType, String newMethodName, boolean keepParams) { + JavaType.FullyQualified inmemoryAuthConfigType = (JavaType.FullyQualified) JavaType.buildType(fqnReturnType); + JavaType.Method type = m.getMethodType(); + if (type != null) { + type = type.withName(newMethodName).withReturnType(inmemoryAuthConfigType); + if (!keepParams) { + type = type + .withParameterTypes(Collections.emptyList()) + .withParameterNames(Collections.emptyList()); + for (JavaType pt : type.getParameterTypes()) { + JavaType.FullyQualified fqt = TypeUtils.asFullyQualified(pt); + if (fqt != null) { + maybeRemoveImport(fqt); + } + } + } + } + + Space returnPrefix = m.getReturnTypeExpression() == null ? Space.EMPTY : m.getReturnTypeExpression().getPrefix(); + m = m.withLeadingAnnotations(ListUtils.map(m.getLeadingAnnotations(), anno -> { + if (TypeUtils.isOfClassType(anno.getType(), FQN_OVERRIDE)) { + maybeRemoveImport(FQN_OVERRIDE); + return null; + } + return anno; + })) + .withReturnTypeExpression(new J.Identifier(Tree.randomId(), returnPrefix, Markers.EMPTY, emptyList(), inmemoryAuthConfigType.getClassName(), inmemoryAuthConfigType, null)) + .withName(m.getName().withSimpleName(newMethodName)) + .withMethodType(type) + .withModifiers(ListUtils.map(m.getModifiers(), modifier -> EXPLICIT_ACCESS_LEVELS.contains(modifier.getType()) ? null : modifier)); + + if (!keepParams) { + m = m.withParameters(Collections.emptyList()); + } + + maybeAddImport(inmemoryAuthConfigType); + // not calling `updateCursor()` here because `visitBlock()` currently requires + // the original to be stored in the cursor + return addBeanAnnotation(m, new Cursor(getCursor().getParentOrThrow(), m)); + } + + @Override + public J.Block visitBlock(J.Block block, ExecutionContext ctx) { + J.Block b = super.visitBlock(block, ctx); + updateCursor(b); + if (getCursor().getParent() != null && getCursor().getParent().getValue() instanceof J.MethodDeclaration) { + J.MethodDeclaration parentMethod = getCursor().getParent().getValue(); + Cursor classDeclCursor = getCursor().dropParentUntil(it -> it instanceof J.ClassDeclaration || it == Cursor.ROOT_VALUE); + if (!(classDeclCursor.getValue() instanceof J.ClassDeclaration)) { + return b; + } + J.ClassDeclaration classDecl = classDeclCursor.getValue(); + if (!classDeclCursor.getMessage(HAS_CONFLICT, true)) { + if (CONFIGURE_HTTP_SECURITY_METHOD_MATCHER.matches(parentMethod, classDecl)) { + b = handleHttpSecurity(b, parentMethod); + } else if (CONFIGURE_WEB_SECURITY_METHOD_MATCHER.matches(parentMethod, classDecl)) { + b = handleWebSecurity(b, parentMethod); + } else if (CONFIGURE_AUTH_MANAGER_SECURITY_METHOD_MATCHER.matches(parentMethod, classDecl)) { + AuthType authType = getAuthType(parentMethod); + switch (authType) { + case INMEMORY: + b = handleAuthInMemory(b, parentMethod); + break; + case LDAP: + //TODO: implement + break; + case JDBC: + //TODO: implement + break; + default: + break; + } + } + } + } + return b; + } + + private J.Block handleHttpSecurity(J.Block b, J.MethodDeclaration parentMethod) { + return JavaTemplate.builder("return #{any(org.springframework.security.config.annotation.SecurityBuilder)}.build();") + .contextSensitive() + .javaParser(JavaParser.fromJavaVersion() + .dependsOn("package org.springframework.security.config.annotation;" + + "public interface SecurityBuilder {\n" + + " O build() throws Exception;" + + "}")) + .imports("org.springframework.security.config.annotation.SecurityBuilder") + .build() + .apply( + getCursor(), + b.getCoordinates().lastStatement(), + ((J.VariableDeclarations) parentMethod.getParameters().get(0)).getVariables().get(0).getName() + ); + } + + private J.Block handleWebSecurity(J.Block b, J.MethodDeclaration parentMethod) { + String t = "return (" + ((J.VariableDeclarations) parentMethod.getParameters().get(0)).getVariables().get(0).getName().getSimpleName() + ") -> #{any()};"; + b = JavaTemplate.builder(t) + .contextSensitive() + .javaParser(JavaParser.fromJavaVersion()) + .build() + .apply( + getCursor(), + b.getCoordinates().firstStatement(), b + ); + return b.withStatements(ListUtils.map(b.getStatements(), (index, stmt) -> { + if (index == 0) { + return stmt; + } + return null; + })); + } + + private J.Block handleAuthInMemory(J.Block b, J.MethodDeclaration parentMethod) { + Expression userExpr = findUserParameterExpression(b.getStatements().get(b.getStatements().size() - 1)); + String typeStr = ""; + if (userExpr != null) { + if (userExpr.getType() instanceof JavaType.Primitive) { + typeStr = ((JavaType.Primitive) userExpr.getType()).getClassName(); + } else if (userExpr.getType() instanceof JavaType.FullyQualified) { + typeStr = ((JavaType.FullyQualified) userExpr.getType()).getFullyQualifiedName(); + } + } + String t; + Object[] templateParams = new Object[0]; + switch (typeStr) { + case FQN_USER_DETAILS_BUILDER: + t = "return new InMemoryUserDetailsManager(#{any()}.build());"; + templateParams = new Object[]{userExpr}; + break; + case FQN_USER_DETAILS: + t = "return new InMemoryUserDetailsManager(#{any()});"; + templateParams = new Object[]{userExpr}; + break; + case "java.lang.String": + t = "return new InMemoryUserDetailsManager(User.builder().username(#{any()}).build());"; + templateParams = new Object[]{userExpr}; + maybeAddImport(FQN_USER); + break; + default: + t = "return new InMemoryUserDetailsManager();"; + b = SearchResult.found(b, "Unrecognized type of user expression " + userExpr + "\n.Please correct manually"); + } + JavaTemplate template = JavaTemplate.builder(t) + .contextSensitive() + .javaParser(JavaParser.fromJavaVersion() + .dependsOn( + "package org.springframework.security.core.userdetails;\n" + + "public interface UserDetails {}\n", + + "package org.springframework.security.provisioning;\n" + + "public class InMemoryUserDetailsManager {\n" + + " public InMemoryUserDetailsManager(org.springframework.security.core.userdetails.UserDetails user) {}\n" + + "}", + + "package org.springframework.security.core.userdetails;\n" + + "public class User {\n" + + " public static UserBuilder builder() {}\n" + + " public interface UserBuilder {\n" + + " UserBuilder username(String s);\n" + + " UserDetails build();\n" + + " }\n" + + "}\n" + )) + .imports(FQN_INMEMORY_AUTH_MANAGER, FQN_USER_DETAILS_BUILDER, FQN_USER) + .build(); + List allExceptLastStatements = b.getStatements(); + allExceptLastStatements.remove(b.getStatements().size() - 1); + b = b.withStatements(allExceptLastStatements); + b = template.apply(updateCursor(b), b.getCoordinates().lastStatement(), templateParams); + maybeAddImport(FQN_INMEMORY_AUTH_MANAGER); + maybeRemoveImport(FQN_AUTH_MANAGER_BUILDER); + return b; + } + + private J.MethodDeclaration addBeanAnnotation(J.MethodDeclaration m, Cursor c) { + maybeAddImport(FQN_BEAN); + return JavaTemplate.builder(BEAN_ANNOTATION) + .imports(FQN_BEAN) + .javaParser(JavaParser.fromJavaVersion() + .dependsOn("package " + BEAN_PKG + "; public @interface " + BEAN_SIMPLE_NAME + " {}")) + .build() + .apply( + c, + m.getCoordinates().addAnnotation(Comparator.comparing(J.Annotation::getSimpleName)) + ); + } + }); + } + + private static String computeBeanNameFromClassName(String className, String beanType) { + String lowerCased = Character.toLowerCase(className.charAt(0)) + className.substring(1); + String newName = lowerCased + .replace("WebSecurityConfigurerAdapter", beanType) + .replace("SecurityConfigurerAdapter", beanType) + .replace("ConfigurerAdapter", beanType) + .replace("Adapter", beanType); + if (lowerCased.equals(newName)) { + newName = newName + beanType; + } + return newName; + } + + private static boolean isMetaAnnotated(JavaType.FullyQualified t, String fqn, Set visited) { + for (JavaType.FullyQualified a : t.getAnnotations()) { + if (!visited.contains(a)) { + visited.add(a); + if (fqn.equals(a.getFullyQualifiedName())) { + return true; + } else { + boolean metaAnnotated = isMetaAnnotated(a, fqn, visited); + if (metaAnnotated) { + return true; + } + } + } + } + return false; + } + + private static boolean isAnnotatedWith(Collection annotations, String annotationType) { + return annotations.stream().anyMatch(a -> TypeUtils.isOfClassType(a.getType(), annotationType)); + } + + private static AuthType getAuthType(J.MethodDeclaration m) { + if (m.getBody() == null || m.getBody().getStatements().isEmpty()) { + return AuthType.NONE; + } + Statement lastStatement = m.getBody().getStatements().get(m.getBody().getStatements().size() - 1); + if (lastStatement instanceof J.MethodInvocation) { + for (J.MethodInvocation invocation = (J.MethodInvocation) lastStatement; invocation != null; ) { + Expression target = invocation.getSelect(); + if (target != null) { + JavaType.FullyQualified type = TypeUtils.asFullyQualified(target.getType()); + if (type != null) { + switch (type.getFullyQualifiedName()) { + case FQN_INMEMORY_AUTH_CONFIG: + return AuthType.INMEMORY; + case FQN_LDAP_AUTH_CONFIG: + return AuthType.LDAP; + case FQN_JDBC_AUTH_CONFIG: + return AuthType.JDBC; + } + } + if (target instanceof J.MethodInvocation) { + invocation = (J.MethodInvocation) target; + continue; + } + } + invocation = null; + } + + } + return AuthType.NONE; + } + + private @Nullable Expression findUserParameterExpression(Statement s) { + AtomicReference context = new AtomicReference<>(); + new JavaIsoVisitor>() { + @Override + public J.MethodInvocation visitMethodInvocation(J.MethodInvocation method, AtomicReference ref) { + if (AUTH_INMEMORY_WITH_USER.matches(method)) { + ref.set(method.getArguments().get(0)); + return method; + } + return super.visitMethodInvocation(method, ref); + } + }.visit(s, context); + return context.get(); + } + +} diff --git a/headless-services/commons/commons-rewrite/src/main/java/org/springframework/ide/vscode/commons/rewrite/java/AddAnnotationOverMethod.java b/headless-services/commons/commons-rewrite/src/main/java/org/springframework/ide/vscode/commons/rewrite/java/AddAnnotationOverMethod.java index 1ec9b7807b..21abea0cf7 100644 --- a/headless-services/commons/commons-rewrite/src/main/java/org/springframework/ide/vscode/commons/rewrite/java/AddAnnotationOverMethod.java +++ b/headless-services/commons/commons-rewrite/src/main/java/org/springframework/ide/vscode/commons/rewrite/java/AddAnnotationOverMethod.java @@ -99,7 +99,7 @@ public MethodDeclaration visitMethodDeclaration(MethodDeclaration method, Execut if (attributes != null) { for (Attribute attr : attributes) { m = (MethodDeclaration) new AddOrUpdateAnnotationAttribute(annotationType, attr.name(), - attr.value(), true, false).getVisitor().visit(m, ctx, getCursor().getParent()); + attr.value(), (String) null, null, null).getVisitor().visit(m, ctx, getCursor().getParent()); } } diff --git a/headless-services/commons/commons-rewrite/src/test/java/org/openrewrite/java/spring/AddSpringPropertyTest.java b/headless-services/commons/commons-rewrite/src/test/java/org/openrewrite/java/spring/AddSpringPropertyTest.java new file mode 100644 index 0000000000..b07cee8643 --- /dev/null +++ b/headless-services/commons/commons-rewrite/src/test/java/org/openrewrite/java/spring/AddSpringPropertyTest.java @@ -0,0 +1,199 @@ +/* + * Copyright 2021 the original author or authors. + *

+ * 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 + *

+ * https://www.apache.org/licenses/LICENSE-2.0 + *

+ * 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; + +import org.junit.jupiter.api.Test; +import org.openrewrite.DocumentExample; +import org.openrewrite.test.RewriteTest; + +import java.util.List; + +import static org.openrewrite.properties.Assertions.properties; +import static org.openrewrite.yaml.Assertions.yaml; + + +class AddSpringPropertyTest implements RewriteTest { + + @DocumentExample + @Test + void addNestedIntoExisting() { + rewriteRun( + spec -> spec.recipe(new AddSpringProperty("server.servlet.path", "/tmp/my-server-path", null, List.of("*"))), + //language=properties + properties( + """ + server.port=8080 + """, + """ + server.port=8080 + server.servlet.path=/tmp/my-server-path + """ + ), + //language=yaml + yaml( + """ + server: + port: 8080 + """, + """ + server: + port: 8080 + servlet: + path: /tmp/my-server-path + """ + ) + ); + } + + @Test + void addPropertyToRoot() { + rewriteRun( + spec -> spec.recipe(new AddSpringProperty("fred", "fred", null, List.of("*"))), + //language=properties + properties( + """ + servlet.session.cookie.path=/cookie-monster + """, + """ + fred=fred + servlet.session.cookie.path=/cookie-monster + """ + ), + //language=yaml + yaml( + """ + server: + port: 8888 + """, + """ + server: + port: 8888 + fred: fred + """ + ) + ); + } + + @Test + void propertyAlreadyExists() { + rewriteRun( + spec -> spec.recipe(new AddSpringProperty("fred", "fred", null, List.of("*"))), + //language=properties + properties( + """ + servlet.session.cookie.path=/cookie-monster + fred=doNotChangeThis + """ + ), + //language=yaml + yaml( + """ + server: + port: 8888 + fred: doNotChangeThis + """ + ) + ); + } + + @Test + void addPropertyWithComment() { + rewriteRun( + spec -> spec.recipe(new AddSpringProperty("server.servlet.path", "/tmp/my-server-path", "This property was added", List.of("*"))), + //language=properties + properties( + """ + server.port=8080 + """, + """ + server.port=8080 + # This property was added + server.servlet.path=/tmp/my-server-path + """ + ), + //language=yaml + yaml( + """ + server: + port: 8080 + """, + """ + server: + port: 8080 + servlet: + # This property was added + path: /tmp/my-server-path + """ + ) + ); + } + + @Test + void makeChangeToMatchingFiles() { + rewriteRun( + spec -> spec.recipe(new AddSpringProperty("server.servlet.path", "/tmp/my-server-path", null, List.of("**/application.properties", "**/application.yml"))), + properties( + //language=properties + """ + server.port=8080 + """, + //language=properties + """ + server.port=8080 + server.servlet.path=/tmp/my-server-path + """, + s -> s.path("src/main/resources/application.properties") + ), + yaml( + //language=yaml + """ + server: + port: 8080 + """, + //language=yaml + """ + server: + port: 8080 + servlet: + path: /tmp/my-server-path + """, + s -> s.path("src/main/resources/application.yml") + ) + ); + } + + @Test + void doNotChangeToFilesThatDoNotMatch() { + rewriteRun( + spec -> spec.recipe(new AddSpringProperty("server.servlet.path", "/tmp/my-server-path", null, List.of("**/application.properties", "**/application.yml"))), + properties( + //language=properties + """ + server.port=8080 + """, + s -> s.path("src/main/resources/application-test.properties") + ), + yaml( + //language=yaml + """ + server: + port: 8080 + """, + s -> s.path("src/main/resources/application-dev.yml") + ) + ); + } +} diff --git a/headless-services/commons/commons-rewrite/src/test/java/org/openrewrite/java/spring/ChangeSpringPropertyValueTest.java b/headless-services/commons/commons-rewrite/src/test/java/org/openrewrite/java/spring/ChangeSpringPropertyValueTest.java new file mode 100644 index 0000000000..299d6cc3db --- /dev/null +++ b/headless-services/commons/commons-rewrite/src/test/java/org/openrewrite/java/spring/ChangeSpringPropertyValueTest.java @@ -0,0 +1,59 @@ +/* + * Copyright 2023 the original author or authors. + *

+ * 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 + *

+ * https://www.apache.org/licenses/LICENSE-2.0 + *

+ * 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; + +import org.junit.jupiter.api.Test; +import org.openrewrite.DocumentExample; +import org.openrewrite.test.RewriteTest; + +import static org.openrewrite.properties.Assertions.properties; +import static org.openrewrite.yaml.Assertions.yaml; + +class ChangeSpringPropertyValueTest implements RewriteTest { + @DocumentExample + @Test + void propFile() { + rewriteRun( + spec -> spec.recipe(new ChangeSpringPropertyValue("server.port", "8081", null, null, null)), + properties("server.port=8080", "server.port=8081") + ); + } + + @Test + void yamlDotSeparated() { + rewriteRun( + spec -> spec.recipe(new ChangeSpringPropertyValue("server.port", "8081", null, null, null)), + yaml("server.port: 8080", "server.port: 8081") + ); + } + + @Test + void yamlIndented() { + rewriteRun( + spec -> spec.recipe(new ChangeSpringPropertyValue("server.port", "8081", null, null, null)), + yaml("server:\n port: 8080", "server:\n port: 8081") + ); + } + + @Test + void regex() { + rewriteRun( + spec -> spec.recipe(new ChangeSpringPropertyValue("server.port", "80$1", "^([0-9]{2})$", true, null)), + properties("server.port=53", "server.port=8053"), + yaml("server.port: 53", "server.port: 8053") + ); + } +} diff --git a/headless-services/commons/commons-rewrite/src/test/java/org/openrewrite/java/spring/ImplicitWebAnnotationNamesTest.java b/headless-services/commons/commons-rewrite/src/test/java/org/openrewrite/java/spring/ImplicitWebAnnotationNamesTest.java new file mode 100644 index 0000000000..2f538876ec --- /dev/null +++ b/headless-services/commons/commons-rewrite/src/test/java/org/openrewrite/java/spring/ImplicitWebAnnotationNamesTest.java @@ -0,0 +1,183 @@ +/* + * Copyright 2021 the original author or authors. + *

+ * 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 + *

+ * https://www.apache.org/licenses/LICENSE-2.0 + *

+ * 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; + +import org.junit.jupiter.api.Test; +import org.openrewrite.DocumentExample; +import org.openrewrite.Issue; +import org.openrewrite.java.JavaParser; +import org.openrewrite.test.RecipeSpec; +import org.openrewrite.test.RewriteTest; + +import static org.openrewrite.java.Assertions.java; + +class ImplicitWebAnnotationNamesTest implements RewriteTest { + + @Override + public void defaults(RecipeSpec spec) { + spec.recipe(new ImplicitWebAnnotationNames()) + .parser(JavaParser.fromJavaVersion().classpath("spring-web")); + } + + @DocumentExample + @Test + void removeUnnecessaryAnnotationArgument() { + //language=java + rewriteRun( + java( + """ + import org.springframework.http.ResponseEntity; + import org.springframework.web.bind.annotation.*; + + @RestController + @RequestMapping("/users") + public class UsersController { + @GetMapping("/{id}") + public ResponseEntity getUser(@PathVariable("id") Long id, + @PathVariable(required = false) Long p2, + @PathVariable(value = "p3") Long anotherName) { + System.out.println(anotherName); + } + } + """, + """ + import org.springframework.http.ResponseEntity; + import org.springframework.web.bind.annotation.*; + + @RestController + @RequestMapping("/users") + public class UsersController { + @GetMapping("/{id}") + public ResponseEntity getUser(@PathVariable Long id, + @PathVariable(required = false) Long p2, + @PathVariable(value = "p3") Long anotherName) { + System.out.println(anotherName); + } + } + """ + ) + ); + } + + @Issue("#4") + @Test + void removeUnnecessarySpacingInFollowingAnnotationArgument() { + //language=java + rewriteRun( + java( + """ + import org.springframework.http.ResponseEntity; + import org.springframework.web.bind.annotation.*; + + @RestController + @RequestMapping("/users") + public class UsersController { + @GetMapping("/{id}") + public ResponseEntity getUser( + @RequestParam(name = "count", defaultValue = 3) int count) { + } + } + """, + """ + import org.springframework.http.ResponseEntity; + import org.springframework.web.bind.annotation.*; + + @RestController + @RequestMapping("/users") + public class UsersController { + @GetMapping("/{id}") + public ResponseEntity getUser( + @RequestParam(defaultValue = 3) int count) { + } + } + """ + ) + ); + } + + @Issue("https://github.com/openrewrite/rewrite-spring/issues/340") + @Test + void autoFormatAfterRemovingArgument() { + //language=java + rewriteRun( + java( + """ + import org.springframework.http.ResponseEntity; + import org.springframework.web.bind.annotation.*; + + @RestController + @RequestMapping("/users") + public class UsersController { + @GetMapping("/{id}") + public ResponseEntity getUser(@PathVariable("id")Long id) { + } + } + """, + """ + import org.springframework.http.ResponseEntity; + import org.springframework.web.bind.annotation.*; + + @RestController + @RequestMapping("/users") + public class UsersController { + @GetMapping("/{id}") + public ResponseEntity getUser(@PathVariable Long id) { + } + } + """ + ) + ); + } + + @Test + void doNotRemoveModelAttributeOnMethods() { + //language=java + rewriteRun( + java( + """ + import org.springframework.web.bind.annotation.*; + import java.util.*; + + public class UsersController { + @ModelAttribute("types") + public Collection populateUserTypes() { + return Arrays.asList("free", "premium"); + } + } + """ + ) + ); + } + + @Test + void doesNotRenamePathVariable() { + //language=java + rewriteRun( + java( + """ + import org.springframework.http.ResponseEntity; + import org.springframework.web.bind.annotation.*; + + public class UsersController { + public ResponseEntity getUser(@PathVariable("uid") Long id, + @PathVariable(value = "another_name") Long anotherName) { + } + } + """ + ) + ); + } +} diff --git a/headless-services/commons/commons-rewrite/src/test/java/org/openrewrite/java/spring/Jars.java b/headless-services/commons/commons-rewrite/src/test/java/org/openrewrite/java/spring/Jars.java new file mode 100644 index 0000000000..7ab80bc832 --- /dev/null +++ b/headless-services/commons/commons-rewrite/src/test/java/org/openrewrite/java/spring/Jars.java @@ -0,0 +1,98 @@ +/******************************************************************************* + * Copyright (c) 2025 Broadcom, Inc. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * https://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Broadcom, Inc. - initial API and implementation + *******************************************************************************/ +package org.openrewrite.java.spring; + +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.List; +import java.util.Objects; +import java.util.stream.Collectors; + +import org.openrewrite.maven.MavenDownloadingExceptions; +import org.openrewrite.maven.MavenParser; +import org.openrewrite.maven.cache.LocalMavenArtifactCache; +import org.openrewrite.maven.tree.MavenResolutionResult; +import org.openrewrite.maven.tree.ResolvedDependency; +import org.openrewrite.maven.tree.Scope; +import org.openrewrite.maven.utilities.MavenArtifactDownloader; +import org.openrewrite.xml.tree.Xml; + +import com.google.common.base.Supplier; +import com.google.common.base.Suppliers; + +public class Jars { + + private static List getDependencyJarsForClasspath(String pom) throws MavenDownloadingExceptions { + Xml.Document doc = (Xml.Document) MavenParser.builder().build().parse(pom).collect(Collectors.toList()).get(0); + MavenResolutionResult resolutionResult = doc.getMarkers().findFirst(MavenResolutionResult.class).orElseThrow(() -> new IllegalStateException()); + // As of 8.52.x the resolution result has all dependency poms resolved +// resolutionResult = resolutionResult.resolveDependencies(new MavenPomDownloader(Collections.emptyMap(), new InMemoryExecutionContext(), null, null), new InMemoryExecutionContext()); + List deps = resolutionResult.getDependencies().get(Scope.Compile); + MavenArtifactDownloader downloader = new MavenArtifactDownloader(new LocalMavenArtifactCache(Paths.get(System.getProperty("user.home"), ".m2", "repository")), null, (t) -> {}); + return deps.stream().filter(d -> "jar".equals(d.getType())).map(downloader::downloadArtifact).filter(Objects::nonNull).collect(Collectors.toList()); + } + + + public static final Supplier> BOOT_2_7 = Suppliers.memoize(() -> { + try { + return getDependencyJarsForClasspath( + //language=xml + """ + + com.example + demo + 0.0.1-SNAPSHOT + + + + org.springframework.boot + spring-boot-dependencies + 2.7.18 + pom + import + + + + + + org.springframework.boot + spring-boot-starter + + + org.springframework.boot + spring-boot-starter-web + + + org.springframework.boot + spring-boot-starter-security + + + org.springframework.security + spring-security-ldap + + + org.springframework.boot + spring-boot-starter-test + + + org.springframework.batch + spring-batch-test + + + + """ + ); + } catch (MavenDownloadingExceptions e) { + throw new IllegalStateException(e); + } + }); + +} diff --git a/headless-services/commons/commons-rewrite/src/test/java/org/openrewrite/java/spring/NoAutowiredOnConstructorTest.java b/headless-services/commons/commons-rewrite/src/test/java/org/openrewrite/java/spring/NoAutowiredOnConstructorTest.java new file mode 100644 index 0000000000..aa29076768 --- /dev/null +++ b/headless-services/commons/commons-rewrite/src/test/java/org/openrewrite/java/spring/NoAutowiredOnConstructorTest.java @@ -0,0 +1,588 @@ +/* + * Copyright 2021 the original author or authors. + *

+ * 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 + *

+ * https://www.apache.org/licenses/LICENSE-2.0 + *

+ * 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; + +import org.junit.jupiter.api.Test; +import org.openrewrite.Issue; +import org.openrewrite.java.JavaParser; +import org.openrewrite.test.RecipeSpec; +import org.openrewrite.test.RewriteTest; + +import static org.openrewrite.java.Assertions.java; + +class NoAutowiredOnConstructorTest implements RewriteTest { + + @Override + public void defaults(RecipeSpec spec) { + spec.recipe(new NoAutowiredOnConstructor()) + .parser(JavaParser.fromJavaVersion().classpath("spring-beans", "spring-boot", "spring-context", "spring-core")); + } + + @Issue("https://github.com/openrewrite/rewrite-spring/issues/78") + @Test + void removeLeadingAutowiredAnnotation() { + //language=java + rewriteRun( + java("@org.springframework.stereotype.Component public class TestSourceA {}"), + java("@org.springframework.stereotype.Component public class TestSourceB {}"), + java("@org.springframework.stereotype.Component public class TestSourceC {}"), + java( + """ + import org.springframework.beans.factory.annotation.Autowired; + + @Autowired + public class TestConfiguration { + private final TestSourceA testSourceA; + private TestSourceB testSourceB; + + @Autowired + private TestSourceC testSourceC; + + @Autowired + public TestConfiguration(TestSourceA testSourceA) { + this.testSourceA = testSourceA; + } + + @Autowired + public void setTestSourceB(TestSourceB testSourceB) { + this.testSourceB = testSourceB; + } + } + """, + """ + import org.springframework.beans.factory.annotation.Autowired; + + @Autowired + public class TestConfiguration { + private final TestSourceA testSourceA; + private TestSourceB testSourceB; + + @Autowired + private TestSourceC testSourceC; + + public TestConfiguration(TestSourceA testSourceA) { + this.testSourceA = testSourceA; + } + + @Autowired + public void setTestSourceB(TestSourceB testSourceB) { + this.testSourceB = testSourceB; + } + } + """ + ) + ); + } + + @Issue("https://github.com/openrewrite/rewrite-spring/issues/78") + @Test + void removeLeadingAutowiredAnnotationNoModifiers() { + //language=java + rewriteRun( + java("@org.springframework.stereotype.Component public class TestSourceA {}"), + java("@org.springframework.stereotype.Component public class TestSourceB {}"), + java("@org.springframework.stereotype.Component public class TestSourceC {}"), + java( + """ + import org.springframework.beans.factory.annotation.Autowired; + + public class TestConfiguration { + private final TestSourceA testSourceA; + private TestSourceB testSourceB; + + @Autowired + private TestSourceC testSourceC; + + @Autowired + TestConfiguration(TestSourceA testSourceA) { + this.testSourceA = testSourceA; + } + + @Autowired + public void setTestSourceB(TestSourceB testSourceB) { + this.testSourceB = testSourceB; + } + } + """, + """ + import org.springframework.beans.factory.annotation.Autowired; + + public class TestConfiguration { + private final TestSourceA testSourceA; + private TestSourceB testSourceB; + + @Autowired + private TestSourceC testSourceC; + + TestConfiguration(TestSourceA testSourceA) { + this.testSourceA = testSourceA; + } + + @Autowired + public void setTestSourceB(TestSourceB testSourceB) { + this.testSourceB = testSourceB; + } + } + """ + ) + ); + } + + @Issue("https://github.com/openrewrite/rewrite-spring/issues/78") + @Test + void removeAutowiredWithMultipleAnnotation() { + //language=java + rewriteRun( + java("@org.springframework.stereotype.Component public class TestSourceA {}"), + java( + """ + import org.springframework.beans.factory.annotation.Autowired; + import org.springframework.beans.factory.annotation.Qualifier; + + public class AnnotationPos1 { + private final TestSourceA testSourceA; + + @Autowired + @Deprecated + @Qualifier + public AnnotationPos1(TestSourceA testSourceA) { + this.testSourceA = testSourceA; + } + } + """, + """ + import org.springframework.beans.factory.annotation.Qualifier; + + public class AnnotationPos1 { + private final TestSourceA testSourceA; + + @Deprecated + @Qualifier + public AnnotationPos1(TestSourceA testSourceA) { + this.testSourceA = testSourceA; + } + } + """ + ), + java( + """ + import org.springframework.beans.factory.annotation.Autowired; + import org.springframework.beans.factory.annotation.Qualifier; + + public class AnnotationPos2 { + private final TestSourceA testSourceA; + + @Deprecated + @Autowired + @Qualifier + public AnnotationPos2(TestSourceA testSourceA) { + this.testSourceA = testSourceA; + } + } + """, + """ + import org.springframework.beans.factory.annotation.Qualifier; + + public class AnnotationPos2 { + private final TestSourceA testSourceA; + + @Deprecated + @Qualifier + public AnnotationPos2(TestSourceA testSourceA) { + this.testSourceA = testSourceA; + } + } + """ + ), + java( + """ + import org.springframework.beans.factory.annotation.Autowired; + import org.springframework.beans.factory.annotation.Qualifier; + + public class AnnotationPos3 { + private final TestSourceA testSourceA; + + @Deprecated + @Qualifier + @Autowired + public AnnotationPos3(TestSourceA testSourceA) { + this.testSourceA = testSourceA; + } + } + """, + """ + import org.springframework.beans.factory.annotation.Qualifier; + + public class AnnotationPos3 { + private final TestSourceA testSourceA; + + @Deprecated + @Qualifier + public AnnotationPos3(TestSourceA testSourceA) { + this.testSourceA = testSourceA; + } + } + """ + ) + ); + } + + @Issue("https://github.com/openrewrite/rewrite-spring/issues/78") + @Test + void removeAutowiredWithMultipleInLineAnnotation() { + //language=java + rewriteRun( + java("@org.springframework.stereotype.Component public class TestSourceA {}"), + java( + """ + import org.springframework.beans.factory.annotation.Autowired; + import org.springframework.beans.factory.annotation.Qualifier; + + public class AnnotationPos1 { + private final TestSourceA testSourceA; + + @Autowired @Deprecated @Qualifier + public AnnotationPos1(TestSourceA testSourceA) { + this.testSourceA = testSourceA; + } + } + """, + """ + import org.springframework.beans.factory.annotation.Qualifier; + + public class AnnotationPos1 { + private final TestSourceA testSourceA; + + @Deprecated @Qualifier + public AnnotationPos1(TestSourceA testSourceA) { + this.testSourceA = testSourceA; + } + } + """ + ), + java( + """ + import org.springframework.beans.factory.annotation.Autowired; + import org.springframework.beans.factory.annotation.Qualifier; + + public class AnnotationPos2 { + private final TestSourceA testSourceA; + + @Deprecated @Autowired @Qualifier + public AnnotationPos2(TestSourceA testSourceA) { + this.testSourceA = testSourceA; + } + } + """, + """ + import org.springframework.beans.factory.annotation.Qualifier; + + public class AnnotationPos2 { + private final TestSourceA testSourceA; + + @Deprecated @Qualifier + public AnnotationPos2(TestSourceA testSourceA) { + this.testSourceA = testSourceA; + } + } + """ + ), + java( + """ + import org.springframework.beans.factory.annotation.Autowired; + import org.springframework.beans.factory.annotation.Qualifier; + + public class AnnotationPos3 { + private final TestSourceA testSourceA; + + @Deprecated @Qualifier @Autowired + public AnnotationPos3(TestSourceA testSourceA) { + this.testSourceA = testSourceA; + } + } + """, + """ + import org.springframework.beans.factory.annotation.Qualifier; + + public class AnnotationPos3 { + private final TestSourceA testSourceA; + + @Deprecated @Qualifier + public AnnotationPos3(TestSourceA testSourceA) { + this.testSourceA = testSourceA; + } + } + """ + ) + ); + } + + @Issue("https://github.com/openrewrite/rewrite-spring/issues/78") + @Test + void oneNamePrefixAnnotation() { + //language=java + rewriteRun( + java( + """ + import javax.sql.DataSource; + import org.springframework.beans.factory.annotation.Autowired; + + public class DatabaseConfiguration { + private final DataSource dataSource; + + public @Autowired DatabaseConfiguration(DataSource dataSource) { + } + } + """, + """ + import javax.sql.DataSource; + + public class DatabaseConfiguration { + private final DataSource dataSource; + + public DatabaseConfiguration(DataSource dataSource) { + } + } + """ + ) + ); + } + + @Issue("https://github.com/openrewrite/rewrite-spring/issues/78") + @Test + void multipleNamePrefixAnnotationsPos1() { + //language=java + rewriteRun( + java( + """ + import javax.sql.DataSource; + import org.springframework.beans.factory.annotation.Autowired; + + public class DatabaseConfiguration { + private final DataSource dataSource; + + public @Autowired @Deprecated DatabaseConfiguration(DataSource dataSource) { + } + } + """, + """ + import javax.sql.DataSource; + + public class DatabaseConfiguration { + private final DataSource dataSource; + + public @Deprecated DatabaseConfiguration(DataSource dataSource) { + } + } + """ + ) + ); + } + + @Issue("https://github.com/openrewrite/rewrite-spring/issues/78") + @Test + void multipleNamePrefixAnnotationsPos2() { + //language=java + rewriteRun( + java( + """ + import javax.sql.DataSource; + import org.springframework.beans.factory.annotation.Autowired; + + public class DatabaseConfiguration { + private final DataSource dataSource; + + public @SuppressWarnings("") @Autowired @Deprecated DatabaseConfiguration(DataSource dataSource) { + } + } + """, + """ + import javax.sql.DataSource; + + public class DatabaseConfiguration { + private final DataSource dataSource; + + public @SuppressWarnings("") @Deprecated DatabaseConfiguration(DataSource dataSource) { + } + } + """ + ) + ); + } + + @Issue("https://github.com/openrewrite/rewrite-spring/issues/78") + @Test + void multipleNamePrefixAnnotationsPos3() { + //language=java + rewriteRun( + java( + """ + import javax.sql.DataSource; + import org.springframework.beans.factory.annotation.Autowired; + + public class DatabaseConfiguration { + private final DataSource dataSource; + + public @SuppressWarnings("") @Deprecated @Autowired DatabaseConfiguration(DataSource dataSource) { + } + } + """, + """ + import javax.sql.DataSource; + + public class DatabaseConfiguration { + private final DataSource dataSource; + + public @SuppressWarnings("") @Deprecated DatabaseConfiguration(DataSource dataSource) { + } + } + """ + ) + ); + } + + @Issue("https://github.com/openrewrite/rewrite-spring/issues/78") + @Test + void keepAutowiredAnnotationsWhenMultipleConstructorsExist() { + //language=java + rewriteRun( + java( + """ + import org.springframework.beans.factory.annotation.Autowired; + import org.springframework.core.io.Resource; + import java.io.PrintStream; + + public class MyAppResourceService { + private final Resource someResource; + private final PrintStream printStream; + + public MyAppResourceService(Resource someResource) { + this.someResource = someResource; + this.printStream = System.out; + } + + @Autowired + public MyAppResourceService(Resource someResource, PrintStream printStream) { + this.someResource = someResource; + this.printStream = printStream; + } + } + """ + ) + ); + } + + @Test + void optionalAutowiredAnnotations() { + //language=java + rewriteRun( + java( + """ + import org.springframework.beans.factory.annotation.Autowired; + import javax.sql.DataSource; + + public class DatabaseConfiguration { + private final DataSource dataSource; + + public DatabaseConfiguration(@Autowired(required = false) DataSource dataSource) { + } + } + """ + ) + ); + } + + @Test + void noAutowiredAnnotations() { + //language=java + rewriteRun( + java( + """ + import org.springframework.context.annotation.Primary; + import javax.sql.DataSource; + + public class DatabaseConfiguration { + private final DataSource dataSource; + + @Primary + public DatabaseConfiguration(DataSource dataSource) { + } + } + """ + ) + ); + } + + @Test + void ignoreConfigurationProperties() { + //language=java + rewriteRun( + java( + """ + import org.springframework.beans.factory.annotation.Autowired; + import org.springframework.boot.context.properties.ConfigurationProperties; + import org.springframework.core.env.Environment; + @ConfigurationProperties + public class ArchivingWorkflowListenerProperties { + private final Environment environment; + @Autowired + public ArchivingWorkflowListenerProperties(Environment environment) { + this.environment = environment; + } + } + """ + ) + ); + } + + @Test + @Issue("https://github.com/openrewrite/rewrite-spring/issues/479") + void ignoreLombokConstructors() { + //language=java + rewriteRun( + java( + """ + package lombok; + + import java.lang.annotation.ElementType; + import java.lang.annotation.Retention; + import java.lang.annotation.RetentionPolicy; + import java.lang.annotation.Target; + + @Target({ElementType.TYPE}) + @Retention(RetentionPolicy.SOURCE) + public @interface NoArgsConstructor { + } + """ + ), + java( + """ + import lombok.NoArgsConstructor; + import org.springframework.beans.factory.annotation.Autowired; + import org.springframework.core.env.Environment; + @NoArgsConstructor + public class ArchivingWorkflowListenerProperties { + private final Environment environment; + @Autowired + public ArchivingWorkflowListenerProperties(Environment environment) { + this.environment = environment; + } + } + """ + ) + ); + } +} diff --git a/headless-services/commons/commons-rewrite/src/test/java/org/openrewrite/java/spring/NoRepoAnnotationOnRepoInterfaceTest.java b/headless-services/commons/commons-rewrite/src/test/java/org/openrewrite/java/spring/NoRepoAnnotationOnRepoInterfaceTest.java new file mode 100644 index 0000000000..0d4058aa64 --- /dev/null +++ b/headless-services/commons/commons-rewrite/src/test/java/org/openrewrite/java/spring/NoRepoAnnotationOnRepoInterfaceTest.java @@ -0,0 +1,255 @@ +/* + * Copyright 2022 the original author or authors. + *

+ * 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 + *

+ * https://www.apache.org/licenses/LICENSE-2.0 + *

+ * 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; + +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; + +class NoRepoAnnotationOnRepoInterfaceTest implements RewriteTest { + + @Override + public void defaults(RecipeSpec spec) { + spec.recipe(new NoRepoAnnotationOnRepoInterface()) + .parser(JavaParser.fromJavaVersion().classpath("spring-context", "spring-beans", "spring-data")); + } + + @DocumentExample + @Test + void simpleCase() { + //language=java + rewriteRun( + java( + """ + import org.springframework.stereotype.Repository; + + @Repository + public interface MyRepo extends org.springframework.data.repository.Repository { + } + """, + """ + + public interface MyRepo extends org.springframework.data.repository.Repository { + } + """ + ) + ); + } + + @Test + void simpleCaseWithNoParameters() { + //language=java + rewriteRun( + java( + """ + import org.springframework.stereotype.Repository; + + @Repository( ) + public interface MyRepo extends org.springframework.data.repository.Repository { + } + """, + """ + + public interface MyRepo extends org.springframework.data.repository.Repository { + } + """ + ) + ); + } + + @Test + void crudRepoClass() { + //language=java + rewriteRun( + java( + """ + import java.util.Optional; + + import org.springframework.data.repository.CrudRepository; + import org.springframework.stereotype.Repository; + + @Repository + public class MyRepo implements CrudRepository { + + @Override + public S save(S entity) { + return null; + } + + @Override + public Iterable saveAll(Iterable entities) { + return null; + } + + @Override + public Optional findById(String id) { + return Optional.empty(); + } + + @Override + public boolean existsById(String id) { + return false; + } + + @Override + public Iterable findAll() { + return null; + } + + @Override + public Iterable findAllById(Iterable ids) { + return null; + } + + @Override + public long count() { + return 0; + } + + @Override + public void deleteById(String id) { + } + + @Override + public void delete(String entity) { + } + + @Override + public void deleteAllById(Iterable ids) { + } + + @Override + public void deleteAll(Iterable entities) { + } + + @Override + public void deleteAll() { + } + + } + """ + ) + ); + } + + @Test + void crudRepoInterface() { + //language=java + rewriteRun( + java( + """ + import org.springframework.data.repository.CrudRepository; + import org.springframework.stereotype.Repository; + + @Repository + public interface MyRepo extends CrudRepository { + + } + """, + """ + import org.springframework.data.repository.CrudRepository; + + public interface MyRepo extends CrudRepository { + + } + """ + ) + ); + } + + @Test + void crudRepoInterfaceWithMultipleAnnotations() { + //language=java + rewriteRun( + java( + """ + import org.springframework.data.repository.CrudRepository; + import org.springframework.stereotype.Repository; + + @Repository + @Deprecated + public interface MyRepo extends CrudRepository { + + } + """, + """ + import org.springframework.data.repository.CrudRepository; + + @Deprecated + public interface MyRepo extends CrudRepository { + + } + """ + ) + ); + } + + @Test + void repoAnnotationWithParameters() { + //language=java + rewriteRun( + java( + """ + import org.springframework.data.repository.CrudRepository; + import org.springframework.stereotype.Repository; + + @Repository("myRepoBean") + public interface MyRepo extends CrudRepository { + + } + """ + ) + ); + } + + @Test + void noRepoSubclass() { + //language=java + rewriteRun( + java( + """ + import java.util.List; + + import org.springframework.stereotype.Repository; + + @Repository + public interface MyRepo extends List { + } + """ + ) + ); + } + + @Test + void noRepoAnnotation() { + //language=java + rewriteRun( + java( + """ + import org.springframework.data.repository.CrudRepository; + + public interface MyRepo extends CrudRepository { + } + """ + ) + ); + } + +} diff --git a/headless-services/commons/commons-rewrite/src/test/java/org/openrewrite/java/spring/boot2/AddConfigurationAnnotationIfBeansPresentTest.java b/headless-services/commons/commons-rewrite/src/test/java/org/openrewrite/java/spring/boot2/AddConfigurationAnnotationIfBeansPresentTest.java new file mode 100644 index 0000000000..d589445e8f --- /dev/null +++ b/headless-services/commons/commons-rewrite/src/test/java/org/openrewrite/java/spring/boot2/AddConfigurationAnnotationIfBeansPresentTest.java @@ -0,0 +1,134 @@ +/* + * Copyright 2023 the original author or authors. + *

+ * 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 + *

+ * https://www.apache.org/licenses/LICENSE-2.0 + *

+ * 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.boot2; + +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; + +class AddConfigurationAnnotationIfBeansPresentTest implements RewriteTest { + + @Override + public void defaults(RecipeSpec spec) { + spec.recipe(new AddConfigurationAnnotationIfBeansPresent()) + .parser(JavaParser.fromJavaVersion() + .classpath("spring-beans", "spring-context", "spring-boot", "spring-security", "spring-web", "spring-core")); + } + + @Test + void enableWebSecurityNoBeans() { + rewriteRun( + java( + """ + import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; + + @EnableWebSecurity + class A {} + """ + ) + ); + } + + @DocumentExample + @Test + void enableWebSecurityWithBeans() { + rewriteRun( + java( + """ + import org.springframework.context.annotation.Bean; + import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; + + @EnableWebSecurity + class A { + @Bean String hello() { return "hello"; } + } + """, + """ + import org.springframework.context.annotation.Bean; + import org.springframework.context.annotation.Configuration; + import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; + + @Configuration + @EnableWebSecurity + class A { + @Bean String hello() { return "hello"; } + } + """ + ) + ); + } + + @Test + void configurationMetaAnnotation() { + rewriteRun( + java( + """ + import org.springframework.boot.autoconfigure.SpringBootApplication; + import org.springframework.context.annotation.Bean; + + @SpringBootApplication + class A { + @Bean String hello() { return "hello"; } + } + """ + ) + ); + } + + @Test + void abstractClassWithBeans() { + rewriteRun( + java( + """ + import org.springframework.context.annotation.Bean; + + abstract class A { + @Bean String hello() { return "hello"; } + } + """ + ) + ); + } + + @Test + void noAnnotationsWithBean() { + rewriteRun( + java( + """ + import org.springframework.context.annotation.Bean; + + class A { + @Bean String hello() { return "hello"; } + } + """, + """ + import org.springframework.context.annotation.Bean; + import org.springframework.context.annotation.Configuration; + + @Configuration + class A { + @Bean String hello() { return "hello"; } + } + """ + ) + ); + } + +} diff --git a/headless-services/commons/commons-rewrite/src/test/java/org/openrewrite/java/spring/boot2/UnnecessarySpringExtensionTest.java b/headless-services/commons/commons-rewrite/src/test/java/org/openrewrite/java/spring/boot2/UnnecessarySpringExtensionTest.java new file mode 100644 index 0000000000..68519fa44e --- /dev/null +++ b/headless-services/commons/commons-rewrite/src/test/java/org/openrewrite/java/spring/boot2/UnnecessarySpringExtensionTest.java @@ -0,0 +1,116 @@ +/* + * Copyright 2021 the original author or authors. + *

+ * 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 + *

+ * https://www.apache.org/licenses/LICENSE-2.0 + *

+ * 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.boot2; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; +import org.openrewrite.Issue; +import org.openrewrite.java.JavaParser; +import org.openrewrite.java.spring.Jars; +import org.openrewrite.test.RecipeSpec; +import org.openrewrite.test.RewriteTest; + +import static org.openrewrite.java.Assertions.java; + +class UnnecessarySpringExtensionTest implements RewriteTest { +// override val parser: JavaParser +// get() = JavaParser.fromJavaVersion() +// .classpath("spring-context", "spring-test", "spring-boot-test", "junit-jupiter-api", "spring-boot-test-autoconfigure", "spring-batch-test") +// .build() +// +// override val recipe: Recipe +// get() = UnnecessarySpringExtension() + + + @Override + public void defaults(RecipeSpec spec) { + spec.recipe(new UnnecessarySpringExtension()) + .parser(JavaParser.fromJavaVersion() + .classpath(Jars.BOOT_2_7.get())); + } + + @Issue("https://github.com/openrewrite/rewrite-spring/issues/43") + @Test + void removeSpringExtensionIfSpringBootTestIsPresent() { + //language=java + rewriteRun( + java( + """ + import org.junit.jupiter.api.extension.ExtendWith; + import org.springframework.boot.test.context.SpringBootTest; + import org.springframework.test.context.junit.jupiter.SpringExtension; + + @SpringBootTest + @ExtendWith(SpringExtension.class) + class Test { + } + """, + """ + import org.springframework.boot.test.context.SpringBootTest; + + @SpringBootTest + class Test { + } + """ + ) + ); + } + + @Issue("https://github.com/openrewrite/rewrite-spring/issues/72") + @ParameterizedTest + @ValueSource(strings = { + "org.springframework.boot.test.autoconfigure.jdbc.JdbcTest", + "org.springframework.boot.test.autoconfigure.web.client.RestClientTest", + "org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest", + "org.springframework.boot.test.autoconfigure.web.reactive.WebFluxTest", + "org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest", + "org.springframework.boot.test.autoconfigure.webservices.client.WebServiceClientTest", + "org.springframework.boot.test.autoconfigure.jooq.JooqTest", + "org.springframework.boot.test.autoconfigure.json.JsonTest", + "org.springframework.boot.test.autoconfigure.data.cassandra.DataCassandraTest", + "org.springframework.boot.test.autoconfigure.data.jdbc.DataJdbcTest", + "org.springframework.boot.test.autoconfigure.data.ldap.DataLdapTest", + "org.springframework.boot.test.autoconfigure.data.mongo.DataMongoTest", + "org.springframework.boot.test.autoconfigure.data.neo4j.DataNeo4jTest", + "org.springframework.boot.test.autoconfigure.data.r2dbc.DataR2dbcTest", + "org.springframework.boot.test.autoconfigure.data.redis.DataRedisTest", + "org.springframework.batch.test.context.SpringBatchTest" + }) + void removeSpringExtensionForTestSliceAnnotations(String annotationName) { + rewriteRun( + java( + """ + import org.junit.jupiter.api.extension.ExtendWith; + import %s; + import org.springframework.test.context.junit.jupiter.SpringExtension; + + @%s + @ExtendWith(SpringExtension.class) + class Test { + } + """.formatted(annotationName, annotationName.substring(annotationName.lastIndexOf('.') + 1)), + """ + import %s; + + @%s + class Test { + } + """.formatted(annotationName, annotationName.substring(annotationName.lastIndexOf('.') + 1)) + ) + ); + } +} diff --git a/headless-services/commons/commons-rewrite/src/test/java/org/openrewrite/java/spring/boot3/PreciseBeanTypeTest.java b/headless-services/commons/commons-rewrite/src/test/java/org/openrewrite/java/spring/boot3/PreciseBeanTypeTest.java new file mode 100644 index 0000000000..e788fc5357 --- /dev/null +++ b/headless-services/commons/commons-rewrite/src/test/java/org/openrewrite/java/spring/boot3/PreciseBeanTypeTest.java @@ -0,0 +1,149 @@ +/* + * Copyright 2022 the original author or authors. + *

+ * 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 + *

+ * https://www.apache.org/licenses/LICENSE-2.0 + *

+ * 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.boot3; + +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; + +class PreciseBeanTypeTest implements RewriteTest { + + @Override + public void defaults(RecipeSpec spec) { + spec.recipe(new PreciseBeanType()) + .parser(JavaParser.fromJavaVersion() + .classpath("spring-context", "spring-boot")); + } + + @DocumentExample + @Test + void simplestCase() { + //language=java + rewriteRun( + java( + """ + import org.springframework.context.annotation.Bean; + import java.util.List; + import java.util.ArrayList; + + class A { + @Bean + List bean1() { + return new ArrayList(); + } + } + """, + """ + import org.springframework.context.annotation.Bean; + import java.util.ArrayList; + + class A { + @Bean + ArrayList bean1() { + return new ArrayList(); + } + } + """ + ) + ); + } + + @Test + void nestedCase() { + //language=java + rewriteRun( + java( + """ + import org.springframework.context.annotation.Bean; + import java.util.List; + import java.util.ArrayList; + import java.util.Stack; + import java.util.concurrent.Callable; + + class A { + @Bean + List bean1() { + Callable callable = () -> { + return new ArrayList(); + }; + return new Stack(); + } + } + """, + """ + import org.springframework.context.annotation.Bean; + import java.util.List; + import java.util.ArrayList; + import java.util.Stack; + import java.util.concurrent.Callable; + + class A { + @Bean + Stack bean1() { + Callable callable = () -> { + return new ArrayList(); + }; + return new Stack(); + } + } + """ + ) + ); + } + + @Test + void notApplicableCase() { + //language=java + rewriteRun( + java( + """ + import org.springframework.context.annotation.Bean; + import java.util.ArrayList; + + class A { + @Bean + ArrayList bean1() { + return new ArrayList(); + } + } + """ + ) + ); + } + + @Test + void notApplicablePrimitiveTypeCase() { + //language=java + rewriteRun( + java( + """ + import org.springframework.context.annotation.Bean; + + class A { + @Bean + String bean1() { + return "hello"; + } + } + """ + ) + ); + } +} diff --git a/headless-services/commons/commons-rewrite/src/test/java/org/openrewrite/java/spring/framework/BeanMethodsNotPublicTest.java b/headless-services/commons/commons-rewrite/src/test/java/org/openrewrite/java/spring/framework/BeanMethodsNotPublicTest.java new file mode 100644 index 0000000000..cc5c2e115d --- /dev/null +++ b/headless-services/commons/commons-rewrite/src/test/java/org/openrewrite/java/spring/framework/BeanMethodsNotPublicTest.java @@ -0,0 +1,133 @@ +/* + * Copyright 2021 the original author or authors. + *

+ * 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 + *

+ * https://www.apache.org/licenses/LICENSE-2.0 + *

+ * 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.framework; + +import org.junit.jupiter.api.Test; +import org.openrewrite.Issue; +import org.openrewrite.java.JavaParser; +import org.openrewrite.test.RecipeSpec; +import org.openrewrite.test.RewriteTest; + +import static org.openrewrite.java.Assertions.java; + +class BeanMethodsNotPublicTest implements RewriteTest { + + @Override + public void defaults(RecipeSpec spec) { + spec.recipe(new BeanMethodsNotPublic()) + .parser(JavaParser.fromJavaVersion().classpath("spring-context")); + } + + @Test + void removePublicModifierFromBeanMethods() { + //language=java + rewriteRun( + java( + """ + package a.b.c; + public class DataSource {} + """), + java( + """ + import a.b.c.DataSource; + import org.springframework.context.annotation.Bean; + import org.springframework.context.annotation.Primary; + + public class DatabaseConfiguration { + + // primary comments + @Primary + @Bean + public DataSource dataSource() { + return new DataSource(); + } + + @Bean // comments + public final DataSource dataSource2() { + return new DataSource(); + } + + @Bean + // comments + public static DataSource dataSource3() { + return new DataSource(); + } + } + """, + """ + import a.b.c.DataSource; + import org.springframework.context.annotation.Bean; + import org.springframework.context.annotation.Primary; + + public class DatabaseConfiguration { + + // primary comments + @Primary + @Bean + DataSource dataSource() { + return new DataSource(); + } + + @Bean // comments + final DataSource dataSource2() { + return new DataSource(); + } + + @Bean // comments + static DataSource dataSource3() { + return new DataSource(); + } + } + """ + ) + ); + } + + @Issue("https://github.com/openrewrite/rewrite-spring/issues/70") + @Test + void leaveOverridesUnchanged() { + //language=java + rewriteRun( + java( + """ + interface A { + void a(); + } + """ + ), + java( + """ + class B { + public void b() {} + } + """ + ), + java( + """ + import org.springframework.context.annotation.Bean; + + public class PublicBeans extends B implements A { + @Bean + public void a() {} + + @Bean + public void b() {} + } + """ + ) + ); + } +} diff --git a/headless-services/commons/commons-rewrite/src/test/java/org/openrewrite/java/spring/security5/AuthorizeHttpRequestsTest.java b/headless-services/commons/commons-rewrite/src/test/java/org/openrewrite/java/spring/security5/AuthorizeHttpRequestsTest.java new file mode 100644 index 0000000000..d0c397ce58 --- /dev/null +++ b/headless-services/commons/commons-rewrite/src/test/java/org/openrewrite/java/spring/security5/AuthorizeHttpRequestsTest.java @@ -0,0 +1,293 @@ +/* + * Copyright 2023 the original author or authors. + *

+ * 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 + *

+ * https://www.apache.org/licenses/LICENSE-2.0 + *

+ * 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.security5; + +import org.junit.jupiter.api.Disabled; +import org.junit.jupiter.api.Test; +import org.openrewrite.DocumentExample; +import org.openrewrite.java.JavaParser; +import org.openrewrite.java.spring.Jars; +import org.openrewrite.test.RecipeSpec; +import org.openrewrite.test.RewriteTest; + +import static org.openrewrite.java.Assertions.java; + +class AuthorizeHttpRequestsTest implements RewriteTest { + + @Override + public void defaults(RecipeSpec spec) { + spec.recipe(new AuthorizeHttpRequests()) + .parser(JavaParser.fromJavaVersion() + .classpath(Jars.BOOT_2_7.get())); + } + + @DocumentExample + @Test + void noArgAuthorizeRequests() { + //language=java + rewriteRun( + java( + """ + import org.springframework.security.config.annotation.web.builders.HttpSecurity; + import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; + import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; + + @EnableWebSecurity + public class SecurityConfig extends WebSecurityConfigurerAdapter { + + @Override + protected void configure(HttpSecurity http) throws Exception { + http + .authorizeRequests() + .antMatchers("/blog/**").permitAll() + .anyRequest().authenticated() + .and() + .formLogin() + .loginPage("/login") + .permitAll() + .and() + .rememberMe(); + } + } + """, + """ + import org.springframework.security.config.annotation.web.builders.HttpSecurity; + import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; + import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; + + @EnableWebSecurity + public class SecurityConfig extends WebSecurityConfigurerAdapter { + + @Override + protected void configure(HttpSecurity http) throws Exception { + http + .authorizeHttpRequests() + .antMatchers("/blog/**").permitAll() + .anyRequest().authenticated() + .and() + .formLogin() + .loginPage("/login") + .permitAll() + .and() + .rememberMe(); + } + } + """ + ) + ); + } + + @Disabled + @Test + void noArgAuthorizeRequestsWithVars() { + //language=java + rewriteRun( + java( + """ + import org.springframework.context.annotation.Bean; + import org.springframework.context.annotation.Configuration; + import org.springframework.security.config.annotation.web.builders.HttpSecurity; + import org.springframework.security.config.annotation.web.configurers.ExpressionUrlAuthorizationConfigurer; + import org.springframework.security.web.SecurityFilterChain; + + @Configuration + public class JdbcSecurityConfiguration { + @Bean + SecurityFilterChain web(HttpSecurity http) throws Exception { + ExpressionUrlAuthorizationConfigurer.ExpressionInterceptUrlRegistry reqs = http.authorizeRequests(); + reqs.antMatchers("/ll").authenticated(); + return http.build(); + } + } + """, + """ + import org.springframework.context.annotation.Bean; + import org.springframework.context.annotation.Configuration; + import org.springframework.security.config.annotation.web.builders.HttpSecurity; + import org.springframework.security.config.annotation.web.configurers.AuthorizeHttpRequestsConfigurer; + import org.springframework.security.web.SecurityFilterChain; + + @Configuration + public class JdbcSecurityConfiguration { + @Bean + SecurityFilterChain web(HttpSecurity http) throws Exception { + AuthorizeHttpRequestsConfigurer.AuthorizationManagerRequestMatcherRegistry reqs = http.authorizeHttpRequests(); + reqs.antMatchers("/ll").authenticated(); + return http.build(); + } + } + """ + ) + ); + } + + @Test + void lambdaAuthorizeRequests() { + //language=java + rewriteRun( + java( + """ + import org.springframework.security.config.annotation.web.builders.HttpSecurity; + import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; + import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; + + import static org.springframework.security.config.Customizer.withDefaults; + + @EnableWebSecurity + public class SecurityConfig extends WebSecurityConfigurerAdapter { + + @Override + protected void configure(HttpSecurity http) throws Exception { + http + .authorizeRequests(authorizeRequests -> + authorizeRequests + .antMatchers("/blog/**").permitAll() + .anyRequest().authenticated() + ) + .formLogin(formLogin -> + formLogin + .loginPage("/login") + .permitAll() + ) + .rememberMe(withDefaults()); + } + + } + """, + """ + import org.springframework.security.config.annotation.web.builders.HttpSecurity; + import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; + import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; + + import static org.springframework.security.config.Customizer.withDefaults; + + @EnableWebSecurity + public class SecurityConfig extends WebSecurityConfigurerAdapter { + + @Override + protected void configure(HttpSecurity http) throws Exception { + http + .authorizeHttpRequests(authorizeRequests -> + authorizeRequests + .antMatchers("/blog/**").permitAll() + .anyRequest().authenticated() + ) + .formLogin(formLogin -> + formLogin + .loginPage("/login") + .permitAll() + ) + .rememberMe(withDefaults()); + } + + } + """ + ) + ); + } + + @Test + void accessDecisionManagerTopInvocation() { + //language=java + rewriteRun( + java( + """ + import org.springframework.security.config.annotation.web.builders.HttpSecurity; + import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; + import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; + + @EnableWebSecurity + public class SecurityConfig extends WebSecurityConfigurerAdapter { + + @Override + protected void configure(HttpSecurity http) throws Exception { + http + .authorizeRequests() + .antMatchers("/blog/**").permitAll() + .anyRequest().authenticated() + // hello + .accessDecisionManager(null); + } + } + """, + """ + import org.springframework.security.config.annotation.web.builders.HttpSecurity; + import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; + import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; + + @EnableWebSecurity + public class SecurityConfig extends WebSecurityConfigurerAdapter { + + @Override + protected void configure(HttpSecurity http) throws Exception { + /*TODO: replace removed '.accessDecisionManager(null);' with appropriate call to 'access(AuthorizationManager)' after antMatcher(...) call etc.*/ + http + .authorizeHttpRequests() + .antMatchers("/blog/**").permitAll() + .anyRequest().authenticated(); + } + } + """ + ) + ); + } + + @Test + void accessDecisionManager() { + //language=java + rewriteRun( + java( + """ + import org.springframework.security.config.annotation.web.builders.HttpSecurity; + import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; + import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; + + @EnableWebSecurity + public class SecurityConfig extends WebSecurityConfigurerAdapter { + + @Override + protected void configure(HttpSecurity http) throws Exception { + http + .authorizeRequests() + .accessDecisionManager(null) + .antMatchers("/blog/**").permitAll() + .anyRequest().authenticated(); + } + } + """, + """ + import org.springframework.security.config.annotation.web.builders.HttpSecurity; + import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; + import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; + + @EnableWebSecurity + public class SecurityConfig extends WebSecurityConfigurerAdapter { + + @Override + protected void configure(HttpSecurity http) throws Exception { + http + .authorizeHttpRequests() + /*TODO: replace removed '.accessDecisionManager(null);' with appropriate call to 'access(AuthorizationManager)' after antMatcher(...) call etc.*/ + .antMatchers("/blog/**").permitAll() + .anyRequest().authenticated(); + } + } + """ + ) + ); + } + +} diff --git a/headless-services/commons/commons-rewrite/src/test/java/org/openrewrite/java/spring/security5/HttpSecurityLambdaDslTest.java b/headless-services/commons/commons-rewrite/src/test/java/org/openrewrite/java/spring/security5/HttpSecurityLambdaDslTest.java new file mode 100644 index 0000000000..adb129d38a --- /dev/null +++ b/headless-services/commons/commons-rewrite/src/test/java/org/openrewrite/java/spring/security5/HttpSecurityLambdaDslTest.java @@ -0,0 +1,341 @@ +/* + * Copyright 2023 the original author or authors. + *

+ * 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 + *

+ * https://www.apache.org/licenses/LICENSE-2.0 + *

+ * 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.security5; + +import org.junit.jupiter.api.Test; +import org.openrewrite.DocumentExample; +import org.openrewrite.java.JavaParser; +import org.openrewrite.java.spring.Jars; +import org.openrewrite.test.RecipeSpec; +import org.openrewrite.test.RewriteTest; + +import static org.openrewrite.java.Assertions.java; + +class HttpSecurityLambdaDslTest implements RewriteTest { + + @Override + public void defaults(RecipeSpec spec) { + spec.recipe(new HttpSecurityLambdaDsl()) + .parser(JavaParser.fromJavaVersion() + .classpath(Jars.BOOT_2_7.get())); + } + + @DocumentExample + @Test + void simple() { + //language=java + rewriteRun( + java( + """ + import org.springframework.security.config.annotation.web.builders.HttpSecurity; + import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; + import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; + + @EnableWebSecurity + public class ConventionalSecurityConfig extends WebSecurityConfigurerAdapter { + @Override + protected void configure(HttpSecurity http) throws Exception { + http + .authorizeRequests() + .antMatchers("/blog/**").permitAll() + .anyRequest().authenticated(); + } + } + """, + """ + import org.springframework.security.config.annotation.web.builders.HttpSecurity; + import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; + import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; + + @EnableWebSecurity + public class ConventionalSecurityConfig extends WebSecurityConfigurerAdapter { + @Override + protected void configure(HttpSecurity http) throws Exception { + http + .authorizeRequests(requests -> requests + .antMatchers("/blog/**").permitAll() + .anyRequest().authenticated()); + } + } + """ + ) + ); + } + + @Test + void advanced() { + //language=java + rewriteRun( + java( + """ + import org.springframework.security.config.annotation.web.builders.HttpSecurity; + import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; + import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; + + @EnableWebSecurity + public class ConventionalSecurityConfig extends WebSecurityConfigurerAdapter { + @Override + protected void configure(HttpSecurity http) throws Exception { + http + .authorizeRequests() + .antMatchers("/blog/**").permitAll() + .anyRequest().authenticated() + .and() + .formLogin() + .loginPage("/login") + .permitAll() + .and() + .rememberMe(); + } + } + """, + """ + import org.springframework.security.config.annotation.web.builders.HttpSecurity; + import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; + import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; + + import static org.springframework.security.config.Customizer.withDefaults; + + @EnableWebSecurity + public class ConventionalSecurityConfig extends WebSecurityConfigurerAdapter { + @Override + protected void configure(HttpSecurity http) throws Exception { + http + .authorizeRequests(requests -> requests + .antMatchers("/blog/**").permitAll() + .anyRequest().authenticated()) + .formLogin(login -> login + .loginPage("/login") + .permitAll()) + .rememberMe(withDefaults()); + } + } + """ + ) + ); + } + + @Test + void handleDisableChain() { + rewriteRun( + //language=java + java( + """ + import org.springframework.security.config.annotation.web.builders.HttpSecurity; + import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; + import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; + + @EnableWebSecurity + public class ConventionalSecurityConfig extends WebSecurityConfigurerAdapter { + + @Override + protected void configure(HttpSecurity http) throws Exception { + http.csrf().disable(); + } + } + """, + """ + import org.springframework.security.config.annotation.web.builders.HttpSecurity; + import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; + import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; + + @EnableWebSecurity + public class ConventionalSecurityConfig extends WebSecurityConfigurerAdapter { + + @Override + protected void configure(HttpSecurity http) throws Exception { + http.csrf(csrf -> csrf.disable()); + } + } + """ + ) + ); + } + + @Test + void retainComments() { + rewriteRun( + //language=java + java( + """ + import org.springframework.security.config.annotation.web.builders.HttpSecurity; + import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; + import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; + + @EnableWebSecurity + public class ConventionalSecurityConfig extends WebSecurityConfigurerAdapter { + + @Override + protected void configure(HttpSecurity http) throws Exception { + // matcher order matters + http.authorizeRequests() + .antMatchers("/blog/**").permitAll() + .anyRequest().authenticated(); + } + } + """, + """ + import org.springframework.security.config.annotation.web.builders.HttpSecurity; + import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; + import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; + + @EnableWebSecurity + public class ConventionalSecurityConfig extends WebSecurityConfigurerAdapter { + + @Override + protected void configure(HttpSecurity http) throws Exception { + // matcher order matters + http.authorizeRequests(requests -> requests + .antMatchers("/blog/**").permitAll() + .anyRequest().authenticated()); + } + } + """ + ) + ); + } + + @Test + void retainFormatting() { + rewriteRun( + //language=java + java( + """ + import org.springframework.security.config.annotation.web.builders.HttpSecurity; + import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; + import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; + + @EnableWebSecurity + public class ConventionalSecurityConfig extends WebSecurityConfigurerAdapter { + + @Override + protected void configure(HttpSecurity http) throws Exception { + http.authorizeRequests() + .antMatchers("/blog/**").permitAll() + .anyRequest().authenticated(); + + http.csrf().disable(); + } + } + """, + """ + import org.springframework.security.config.annotation.web.builders.HttpSecurity; + import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; + import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; + + @EnableWebSecurity + public class ConventionalSecurityConfig extends WebSecurityConfigurerAdapter { + + @Override + protected void configure(HttpSecurity http) throws Exception { + http.authorizeRequests(requests -> requests + .antMatchers("/blog/**").permitAll() + .anyRequest().authenticated()); + + http.csrf(csrf -> csrf.disable()); + } + } + """ + ) + ); + } + + @Test + void disableIsTerminal() { + rewriteRun( + //language=java + java( + """ + import org.springframework.security.config.annotation.web.builders.HttpSecurity; + import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; + import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; + + @EnableWebSecurity + public class ConventionalSecurityConfig extends WebSecurityConfigurerAdapter { + + @Override + protected void configure(HttpSecurity http) throws Exception { + http.csrf().disable() + .authorizeRequests() + .antMatchers("/blog/**").permitAll() + .anyRequest().authenticated(); + } + } + """, + """ + import org.springframework.security.config.annotation.web.builders.HttpSecurity; + import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; + import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; + + @EnableWebSecurity + public class ConventionalSecurityConfig extends WebSecurityConfigurerAdapter { + + @Override + protected void configure(HttpSecurity http) throws Exception { + http.csrf(csrf -> csrf.disable()) + .authorizeRequests(requests -> requests + .antMatchers("/blog/**").permitAll() + .anyRequest().authenticated()); + } + } + """ + ) + ); + } + + @Test + void disableAfterOptions() { + rewriteRun( + //language=java + java( + """ + import org.springframework.security.config.annotation.web.builders.HttpSecurity; + import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; + import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; + + @EnableWebSecurity + public class ConventionalSecurityConfig extends WebSecurityConfigurerAdapter { + + @Override + protected void configure(HttpSecurity http) throws Exception { + http.csrf().ignoringAntMatchers("").disable() + .authorizeRequests() + .antMatchers("/blog/**").permitAll() + .anyRequest().authenticated(); + } + } + """, + """ + import org.springframework.security.config.annotation.web.builders.HttpSecurity; + import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; + import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; + + @EnableWebSecurity + public class ConventionalSecurityConfig extends WebSecurityConfigurerAdapter { + + @Override + protected void configure(HttpSecurity http) throws Exception { + http.csrf(csrf -> csrf.ignoringAntMatchers("").disable()) + .authorizeRequests(requests -> requests + .antMatchers("/blog/**").permitAll() + .anyRequest().authenticated()); + } + } + """ + ) + ); + } +} diff --git a/headless-services/commons/commons-rewrite/src/test/java/org/openrewrite/java/spring/security5/ServerHttpSecurityLambdaDslTest.java b/headless-services/commons/commons-rewrite/src/test/java/org/openrewrite/java/spring/security5/ServerHttpSecurityLambdaDslTest.java new file mode 100644 index 0000000000..d816bed4c9 --- /dev/null +++ b/headless-services/commons/commons-rewrite/src/test/java/org/openrewrite/java/spring/security5/ServerHttpSecurityLambdaDslTest.java @@ -0,0 +1,134 @@ +/* + * Copyright 2023 the original author or authors. + *

+ * 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 + *

+ * https://www.apache.org/licenses/LICENSE-2.0 + *

+ * 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.security5; + +import org.junit.jupiter.api.Test; +import org.openrewrite.DocumentExample; +import org.openrewrite.java.JavaParser; +import org.openrewrite.java.spring.Jars; +import org.openrewrite.test.RecipeSpec; +import org.openrewrite.test.RewriteTest; + +import static org.openrewrite.java.Assertions.java; + +class ServerHttpSecurityLambdaDslTest implements RewriteTest { + @Override + public void defaults(RecipeSpec spec) { + spec.recipe(new ServerHttpSecurityLambdaDsl()) + .parser(JavaParser.fromJavaVersion() + .classpath(Jars.BOOT_2_7.get())); + } + + @DocumentExample + @Test + void simple() { + rewriteRun( + //language=java + java( + """ + import org.springframework.context.annotation.Bean; + import org.springframework.security.config.annotation.web.reactive.EnableWebFluxSecurity; + import org.springframework.security.config.web.server.ServerHttpSecurity; + import org.springframework.security.web.server.SecurityWebFilterChain; + + @EnableWebFluxSecurity + public class SecurityConfig { + @Bean + SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http) { + http.authorizeExchange() + .pathMatchers("/blog/**").permitAll() + .anyExchange().authenticated(); + return http.build(); + } + } + """, + """ + import org.springframework.context.annotation.Bean; + import org.springframework.security.config.annotation.web.reactive.EnableWebFluxSecurity; + import org.springframework.security.config.web.server.ServerHttpSecurity; + import org.springframework.security.web.server.SecurityWebFilterChain; + + @EnableWebFluxSecurity + public class SecurityConfig { + @Bean + SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http) { + http.authorizeExchange(exchange -> exchange + .pathMatchers("/blog/**").permitAll() + .anyExchange().authenticated()); + return http.build(); + } + } + """ + ) + ); + } + + @Test + void advanced() { + rewriteRun( + //language=java + java( + """ + import org.springframework.context.annotation.Bean; + import org.springframework.security.config.annotation.web.reactive.EnableWebFluxSecurity; + import org.springframework.security.config.web.server.ServerHttpSecurity; + import org.springframework.security.web.server.SecurityWebFilterChain; + + @EnableWebFluxSecurity + public class SecurityConfig { + @Bean + SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http) { + http + .authorizeExchange() + .pathMatchers("/blog/**").permitAll() + .anyExchange().authenticated() + .and() + .httpBasic() + .and() + .formLogin() + .loginPage("/login"); + return http.build(); + } + } + """, + """ + import org.springframework.context.annotation.Bean; + import org.springframework.security.config.annotation.web.reactive.EnableWebFluxSecurity; + import org.springframework.security.config.web.server.ServerHttpSecurity; + import org.springframework.security.web.server.SecurityWebFilterChain; + + import static org.springframework.security.config.Customizer.withDefaults; + + @EnableWebFluxSecurity + public class SecurityConfig { + @Bean + SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http) { + http + .authorizeExchange(exchange -> exchange + .pathMatchers("/blog/**").permitAll() + .anyExchange().authenticated()) + .httpBasic(withDefaults()) + .formLogin(login -> login + .loginPage("/login")); + return http.build(); + } + } + """ + ) + ); + } + +} diff --git a/headless-services/commons/commons-rewrite/src/test/java/org/openrewrite/java/spring/security5/WebSecurityConfigurerAdapterTest.java b/headless-services/commons/commons-rewrite/src/test/java/org/openrewrite/java/spring/security5/WebSecurityConfigurerAdapterTest.java new file mode 100644 index 0000000000..be0f31a3e9 --- /dev/null +++ b/headless-services/commons/commons-rewrite/src/test/java/org/openrewrite/java/spring/security5/WebSecurityConfigurerAdapterTest.java @@ -0,0 +1,739 @@ +/* + * Copyright 2022 the original author or authors. + *

+ * 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 + *

+ * https://www.apache.org/licenses/LICENSE-2.0 + *

+ * 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.security5; + +import static org.openrewrite.java.Assertions.java; + +import org.junit.jupiter.api.Test; +import org.openrewrite.DocumentExample; +import org.openrewrite.java.JavaParser; +import org.openrewrite.java.spring.Jars; +import org.openrewrite.test.RecipeSpec; +import org.openrewrite.test.RewriteTest; + +/** + * @author Alex Boyko + */ +class WebSecurityConfigurerAdapterTest implements RewriteTest { + + @Override + public void defaults(RecipeSpec spec) { + spec.recipe(new WebSecurityConfigurerAdapter()) + .parser(JavaParser.fromJavaVersion() + .classpath(Jars.BOOT_2_7.get())); + } + + @DocumentExample + @Test + void configureHttpSecurityMethod() { + //language=java + rewriteRun( + java( + """ + package com.example.websecuritydemo; + + import static org.springframework.security.config.Customizer.withDefaults; + import org.springframework.context.annotation.Configuration; + import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; + import org.springframework.security.config.annotation.web.builders.HttpSecurity; + + @Configuration + public class SecurityConfiguration extends WebSecurityConfigurerAdapter { + + @Override + protected void configure(HttpSecurity http) throws Exception { + http + .authorizeHttpRequests((authz) -> authz + .anyRequest().authenticated() + ) + .httpBasic(withDefaults()); + } + + void someMethod() {} + + } + """, + """ + package com.example.websecuritydemo; + + import static org.springframework.security.config.Customizer.withDefaults; + + import org.springframework.context.annotation.Bean; + import org.springframework.context.annotation.Configuration; + import org.springframework.security.config.annotation.web.builders.HttpSecurity; + import org.springframework.security.web.SecurityFilterChain; + + @Configuration + public class SecurityConfiguration { + + @Bean + SecurityFilterChain filterChain(HttpSecurity http) throws Exception { + http + .authorizeHttpRequests((authz) -> authz + .anyRequest().authenticated() + ) + .httpBasic(withDefaults()); + return http.build(); + } + + void someMethod() {} + + } + """ + ) + ); + } + + @Test + void noConfigurationAnnotation() { + //language=java + rewriteRun( + java( + """ + package com.example.websecuritydemo; + + import static org.springframework.security.config.Customizer.withDefaults; + import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; + import org.springframework.security.config.annotation.web.builders.HttpSecurity; + + public class SecurityConfiguration extends WebSecurityConfigurerAdapter { + + @Override + protected void configure(HttpSecurity http) throws Exception { + http + .authorizeHttpRequests((authz) -> authz + .anyRequest().authenticated() + ) + .httpBasic(withDefaults()); + } + + } + """ + ) + ); + } + + @Test + void configureWebSecurityMethod() { + //language=java + rewriteRun( + java( + """ + import org.springframework.context.annotation.Configuration; + import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; + import org.springframework.security.config.annotation.web.builders.WebSecurity; + + @Configuration + public class SecurityConfiguration extends WebSecurityConfigurerAdapter { + + @Override + public void configure(WebSecurity web) { + web.ignoring().antMatchers("/ignore1", "/ignore2"); + } + } + """, + """ + import org.springframework.context.annotation.Bean; + import org.springframework.context.annotation.Configuration; + import org.springframework.security.config.annotation.web.builders.WebSecurity; + import org.springframework.security.config.annotation.web.configuration.WebSecurityCustomizer; + + @Configuration + public class SecurityConfiguration { + + @Bean + WebSecurityCustomizer webSecurityCustomizer() { + return (web) -> { + web.ignoring().antMatchers("/ignore1", "/ignore2"); + }; + } + } + """ + ) + ); + } + + @Test + void configureAuthManagerMethod() { + //language=java + rewriteRun( + java( + """ + import org.springframework.context.annotation.Configuration; + import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; + import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder; + import org.springframework.security.ldap.userdetails.PersonContextMapper; + + @Configuration + public class SecurityConfiguration extends WebSecurityConfigurerAdapter { + + @Override + protected void configure(AuthenticationManagerBuilder auth) { + auth + .ldapAuthentication() + .userDetailsContextMapper(new PersonContextMapper()) + .userDnPatterns("uid={0},ou=people") + .contextSource() + .port(0); + } + } + """, + """ + import org.springframework.context.annotation.Configuration; + import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; + import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder; + import org.springframework.security.ldap.userdetails.PersonContextMapper; + + @Configuration + public class SecurityConfiguration extends WebSecurityConfigurerAdapter { + + /*~~(Migrate manually based on https://spring.io/blog/2022/02/21/spring-security-without-the-websecurityconfigureradapter)~~>*/@Override + protected void configure(AuthenticationManagerBuilder auth) { + auth + .ldapAuthentication() + .userDetailsContextMapper(new PersonContextMapper()) + .userDnPatterns("uid={0},ou=people") + .contextSource() + .port(0); + } + } + """ + ) + ); + } + + @Test + void overrideUnapplicableMethod() { + //language=java + rewriteRun( + java( + """ + import static org.springframework.security.config.Customizer.withDefaults; + + import org.springframework.context.annotation.Configuration; + import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; + import org.springframework.security.config.annotation.web.builders.HttpSecurity; + import org.springframework.security.authentication.AuthenticationManager; + import org.springframework.security.core.userdetails.UserDetailsService; + + @Configuration + public class SecurityConfiguration extends WebSecurityConfigurerAdapter { + + @Override + protected void configure(HttpSecurity http) throws Exception { + http + .authorizeHttpRequests((authz) -> authz + .anyRequest().authenticated() + ) + .httpBasic(withDefaults()); + } + + @Override + public UserDetailsService userDetailsServiceBean() throws Exception { + return null; + } + + @Override + public AuthenticationManager authenticationManagerBean() throws Exception { + return null; + } + } + """, + """ + import static org.springframework.security.config.Customizer.withDefaults; + + import org.springframework.context.annotation.Configuration; + import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; + import org.springframework.security.config.annotation.web.builders.HttpSecurity; + import org.springframework.security.authentication.AuthenticationManager; + import org.springframework.security.core.userdetails.UserDetailsService; + + @Configuration + public class SecurityConfiguration extends WebSecurityConfigurerAdapter { + + @Override + protected void configure(HttpSecurity http) throws Exception { + http + .authorizeHttpRequests((authz) -> authz + .anyRequest().authenticated() + ) + .httpBasic(withDefaults()); + } + + /*~~(Migrate manually based on https://spring.io/blog/2022/02/21/spring-security-without-the-websecurityconfigureradapter)~~>*/@Override + public UserDetailsService userDetailsServiceBean() throws Exception { + return null; + } + + /*~~(Migrate manually based on https://spring.io/blog/2022/02/21/spring-security-without-the-websecurityconfigureradapter)~~>*/@Override + public AuthenticationManager authenticationManagerBean() throws Exception { + return null; + } + } + """ + ) + ); + } + + @Test + void unapplicableMethodInvocation() { + //language=java + rewriteRun( + java( + """ + import static org.springframework.security.config.Customizer.withDefaults; + import org.springframework.context.annotation.Configuration; + import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; + import org.springframework.security.config.annotation.web.builders.HttpSecurity; + + @Configuration + public class SecurityConfiguration extends WebSecurityConfigurerAdapter { + + @Override + protected void configure(HttpSecurity http) { + System.out.println(getApplicationContext()); + http + .authorizeHttpRequests((authz) -> authz + .anyRequest().authenticated() + ) + .httpBasic(withDefaults()); + } + + public void someMethod() {} + } + """, + """ + import static org.springframework.security.config.Customizer.withDefaults; + + import org.springframework.context.annotation.Bean; + import org.springframework.context.annotation.Configuration; + import org.springframework.security.config.annotation.web.builders.HttpSecurity; + import org.springframework.security.web.SecurityFilterChain; + + @Configuration + public class SecurityConfiguration { + + @Bean + SecurityFilterChain filterChain(HttpSecurity http) { + System.out.println(getApplicationContext()); + http + .authorizeHttpRequests((authz) -> authz + .anyRequest().authenticated() + ) + .httpBasic(withDefaults()); + return http.build(); + } + + public void someMethod() {} + } + """ + ) + ); + } + + @Test + void configureHttpSecurityMethodWithNotApplicableMethodInNonStaticInnerClass() { + //language=java + rewriteRun( + java( + """ + import static org.springframework.security.config.Customizer.withDefaults; + import org.springframework.context.annotation.Configuration; + import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; + import org.springframework.security.config.annotation.web.builders.HttpSecurity; + + @Configuration + public class SecurityConfiguration extends WebSecurityConfigurerAdapter { + + @Override + protected void configure(HttpSecurity http) throws Exception { + http + .authorizeHttpRequests((authz) -> authz + .anyRequest().authenticated() + ) + .httpBasic(withDefaults()); + } + + @Configuration + public class InnerSecurityConfiguration { + protected void configure() throws Exception { + System.out.println(getApplicationContext()); + } + } + } + """, + """ + import static org.springframework.security.config.Customizer.withDefaults; + + import org.springframework.context.annotation.Bean; + import org.springframework.context.annotation.Configuration; + import org.springframework.security.config.annotation.web.builders.HttpSecurity; + import org.springframework.security.web.SecurityFilterChain; + + @Configuration + public class SecurityConfiguration { + + @Bean + SecurityFilterChain filterChain(HttpSecurity http) throws Exception { + http + .authorizeHttpRequests((authz) -> authz + .anyRequest().authenticated() + ) + .httpBasic(withDefaults()); + return http.build(); + } + + @Configuration + public class InnerSecurityConfiguration { + protected void configure() throws Exception { + System.out.println(getApplicationContext()); + } + } + } + """ + ) + ); + } + + @Test + void multipleClasses() { + rewriteRun( + //language=java + java( + """ + import org.springframework.context.annotation.Bean; + import org.springframework.context.annotation.Configuration; + import org.springframework.core.annotation.Order; + import org.springframework.security.config.annotation.web.builders.HttpSecurity; + import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; + import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; + import org.springframework.security.core.userdetails.UserDetailsService; + import org.springframework.security.provisioning.InMemoryUserDetailsManager; + + @EnableWebSecurity + public class MultiHttpSecurityConfig { + @Bean + public UserDetailsService userDetailsService() throws Exception { + InMemoryUserDetailsManager manager = new InMemoryUserDetailsManager(); + return manager; + } + + @Configuration + @Order(1) + public static class ApiWebSecurityConfigurationAdapter extends WebSecurityConfigurerAdapter { + protected void configure(HttpSecurity http) throws Exception { + http + .antMatcher("/api/**") + .authorizeRequests() + .anyRequest().hasRole("ADMIN") + .and() + .httpBasic(); + } + } + + @Configuration + public static class FormLoginWebSecurityConfigurerAdapter extends WebSecurityConfigurerAdapter { + + @Override + protected void configure(HttpSecurity http) throws Exception { + http + .authorizeRequests() + .anyRequest().authenticated() + .and() + .formLogin(); + } + } + } + """, + """ + import org.springframework.context.annotation.Bean; + import org.springframework.security.config.annotation.web.builders.HttpSecurity; + import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; + import org.springframework.security.core.userdetails.UserDetailsService; + import org.springframework.security.provisioning.InMemoryUserDetailsManager; + import org.springframework.security.web.SecurityFilterChain; + + @EnableWebSecurity + public class MultiHttpSecurityConfig { + @Bean + public UserDetailsService userDetailsService() throws Exception { + InMemoryUserDetailsManager manager = new InMemoryUserDetailsManager(); + return manager; + } + + @Bean + SecurityFilterChain apiWebSecurityConfigurationSecurityFilterChain(HttpSecurity http) throws Exception { + http + .antMatcher("/api/**") + .authorizeRequests() + .anyRequest().hasRole("ADMIN") + .and() + .httpBasic(); + return http.build(); + } + + @Bean + SecurityFilterChain formLoginSecurityFilterChain(HttpSecurity http) throws Exception { + http + .authorizeRequests() + .anyRequest().authenticated() + .and() + .formLogin(); + return http.build(); + } + } + """ + ) + ); + } + + @Test + void multipleClassesNoFlattening() { + rewriteRun( + //language=java + java( + """ + import org.springframework.context.annotation.Bean; + import org.springframework.context.annotation.Configuration; + import org.springframework.core.annotation.Order; + import org.springframework.security.config.annotation.web.builders.HttpSecurity; + import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; + import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; + import org.springframework.security.core.userdetails.UserDetailsService; + import org.springframework.security.provisioning.InMemoryUserDetailsManager; + + @EnableWebSecurity + public class MultiHttpSecurityConfig { + private int a; + + @Bean + public UserDetailsService userDetailsService() throws Exception { + InMemoryUserDetailsManager manager = new InMemoryUserDetailsManager(); + return manager; + } + + @Configuration + @Order(1) + public static class ApiWebSecurityConfigurationAdapter extends WebSecurityConfigurerAdapter { + private String a; + protected void configure(HttpSecurity http) throws Exception { + http + .antMatcher("/api/**") + .authorizeRequests() + .anyRequest().hasRole("ADMIN") + .and() + .httpBasic(); + } + } + + @Configuration + public static class FormLoginWebSecurityConfigurerAdapter extends WebSecurityConfigurerAdapter { + + @Override + protected void configure(HttpSecurity http) throws Exception { + http + .authorizeRequests() + .anyRequest().authenticated() + .and() + .formLogin(); + } + } + } + """, + """ + import org.springframework.context.annotation.Bean; + import org.springframework.context.annotation.Configuration; + import org.springframework.core.annotation.Order; + import org.springframework.security.config.annotation.web.builders.HttpSecurity; + import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; + import org.springframework.security.core.userdetails.UserDetailsService; + import org.springframework.security.provisioning.InMemoryUserDetailsManager; + import org.springframework.security.web.SecurityFilterChain; + + @EnableWebSecurity + public class MultiHttpSecurityConfig { + private int a; + + @Bean + public UserDetailsService userDetailsService() throws Exception { + InMemoryUserDetailsManager manager = new InMemoryUserDetailsManager(); + return manager; + } + + @Configuration + @Order(1) + public static class ApiWebSecurityConfigurationAdapter { + private String a; + + @Bean + SecurityFilterChain filterChain(HttpSecurity http) throws Exception { + http + .antMatcher("/api/**") + .authorizeRequests() + .anyRequest().hasRole("ADMIN") + .and() + .httpBasic(); + return http.build(); + } + } + + @Bean + SecurityFilterChain formLoginSecurityFilterChain(HttpSecurity http) throws Exception { + http + .authorizeRequests() + .anyRequest().authenticated() + .and() + .formLogin(); + return http.build(); + } + } + """ + ) + ); + } + + @Test + void inMemoryConfig() { + rewriteRun( + //language=java + java( + """ + package com.example.websecuritydemo; + + import org.springframework.context.annotation.Configuration; + import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder; + import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; + import org.springframework.security.core.userdetails.User; + import org.springframework.security.core.userdetails.UserDetails; + + @Configuration + public class SecurityConfiguration extends WebSecurityConfigurerAdapter { + @Override + protected void configure(AuthenticationManagerBuilder auth) throws Exception { + UserDetails user = User.withDefaultPasswordEncoder().username("user").password("password").roles("USER") + .build(); + auth.inMemoryAuthentication().withUser(user); + } + } + """, + """ + package com.example.websecuritydemo; + + import org.springframework.context.annotation.Bean; + import org.springframework.context.annotation.Configuration; + import org.springframework.security.core.userdetails.User; + import org.springframework.security.core.userdetails.UserDetails; + import org.springframework.security.provisioning.InMemoryUserDetailsManager; + + @Configuration + public class SecurityConfiguration { + @Bean + InMemoryUserDetailsManager inMemoryAuthManager() throws Exception { + UserDetails user = User.withDefaultPasswordEncoder().username("user").password("password").roles("USER") + .build(); + return new InMemoryUserDetailsManager(user); + } + } + """ + ) + ); + } + + @Test + void inMemoryConfigWithUserBuilder() { + rewriteRun( + //language=java + java( + """ + package com.example.websecuritydemo; + + import org.springframework.context.annotation.Configuration; + import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder; + import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; + import org.springframework.security.core.userdetails.User; + import org.springframework.security.core.userdetails.User.UserBuilder; + + @Configuration + public class SecurityConfiguration extends WebSecurityConfigurerAdapter { + @Override + protected void configure(AuthenticationManagerBuilder auth) throws Exception { + UserBuilder builder = User.withDefaultPasswordEncoder().username("user").password("password").roles("USER"); + auth.inMemoryAuthentication().withUser(builder); + } + } + """, + """ + package com.example.websecuritydemo; + + import org.springframework.context.annotation.Bean; + import org.springframework.context.annotation.Configuration; + import org.springframework.security.core.userdetails.User; + import org.springframework.security.core.userdetails.User.UserBuilder; + import org.springframework.security.provisioning.InMemoryUserDetailsManager; + + @Configuration + public class SecurityConfiguration { + @Bean + InMemoryUserDetailsManager inMemoryAuthManager() throws Exception { + UserBuilder builder = User.withDefaultPasswordEncoder().username("user").password("password").roles("USER"); + return new InMemoryUserDetailsManager(builder.build()); + } + } + """ + ) + ); + } + + @Test + void inMemoryConfigWithUserString() { + rewriteRun( + //language=java + java( + """ + package com.example.websecuritydemo; + + import org.springframework.context.annotation.Configuration; + import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder; + import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; + + @Configuration + public class SecurityConfiguration extends WebSecurityConfigurerAdapter { + @Override + protected void configure(AuthenticationManagerBuilder auth) throws Exception { + auth.inMemoryAuthentication().withUser("user"); + } + } + """, + """ + package com.example.websecuritydemo; + + import org.springframework.context.annotation.Bean; + import org.springframework.context.annotation.Configuration; + import org.springframework.security.core.userdetails.User; + import org.springframework.security.provisioning.InMemoryUserDetailsManager; + + @Configuration + public class SecurityConfiguration { + @Bean + InMemoryUserDetailsManager inMemoryAuthManager() throws Exception { + return new InMemoryUserDetailsManager(User.builder().username("user").build()); + } + } + """ + ) + ); + } +} diff --git a/headless-services/commons/commons-rewrite/src/test/java/org/springframework/ide/vscode/commons/rewrite/LoadUtilsTest.java b/headless-services/commons/commons-rewrite/src/test/java/org/springframework/ide/vscode/commons/rewrite/LoadUtilsTest.java index 71ff3e8ec2..e14bfb68ad 100644 --- a/headless-services/commons/commons-rewrite/src/test/java/org/springframework/ide/vscode/commons/rewrite/LoadUtilsTest.java +++ b/headless-services/commons/commons-rewrite/src/test/java/org/springframework/ide/vscode/commons/rewrite/LoadUtilsTest.java @@ -17,6 +17,7 @@ import java.time.Duration; import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; import org.openrewrite.Recipe; import org.openrewrite.config.DeclarativeRecipe; @@ -28,6 +29,7 @@ import com.google.gson.Gson; import com.google.gson.GsonBuilder; +@Disabled public class LoadUtilsTest { private static Environment env; diff --git a/headless-services/commons/commons-rewrite/src/test/java/org/springframework/ide/vscode/commons/rewrite/java/AddAnnotationOverMethodTest.java b/headless-services/commons/commons-rewrite/src/test/java/org/springframework/ide/vscode/commons/rewrite/java/AddAnnotationOverMethodTest.java index f40c94b409..9f2c208427 100644 --- a/headless-services/commons/commons-rewrite/src/test/java/org/springframework/ide/vscode/commons/rewrite/java/AddAnnotationOverMethodTest.java +++ b/headless-services/commons/commons-rewrite/src/test/java/org/springframework/ide/vscode/commons/rewrite/java/AddAnnotationOverMethodTest.java @@ -21,7 +21,7 @@ public class AddAnnotationOverMethodTest implements RewriteTest { @Test void addAnnotationToMethod() { rewriteRun( - spec -> spec.recipe(new AddAnnotationOverMethod("demo.A foo()", "java.lang.Deprecated", List.of(new AddAnnotationOverMethod.Attribute("value", "\"Expected Text\"")))), + spec -> spec.recipe(new AddAnnotationOverMethod("demo.A foo()", "java.lang.Deprecated", List.of(new AddAnnotationOverMethod.Attribute("value", "\"Expected-Text\"")))), Assertions.java( """ package demo; @@ -32,7 +32,7 @@ interface A { """ package demo; interface A { - @Deprecated("Expected Text") + @Deprecated("Expected-Text") void foo(); } """ @@ -43,7 +43,7 @@ interface A { @Test void addAnnotationToMethodWithAnnotation() { rewriteRun( - spec -> spec.recipe(new AddAnnotationOverMethod("demo.A foo()", "java.lang.Deprecated", List.of(new AddAnnotationOverMethod.Attribute("value", "\"Expected Text\"")))), + spec -> spec.recipe(new AddAnnotationOverMethod("demo.A foo()", "java.lang.Deprecated", List.of(new AddAnnotationOverMethod.Attribute("value", "\"Expected-Text\"")))), Assertions.java( """ package demo; @@ -58,7 +58,7 @@ void foo() { package demo; class A { @SuppressWarnings("null") - @Deprecated("Expected Text") + @Deprecated("Expected-Text") void foo() { System.out.println("foo"); } diff --git a/headless-services/commons/commons-rewrite/src/test/java/org/springframework/ide/vscode/commons/rewrite/maven/MavenIJavaProjectParserTest.java b/headless-services/commons/commons-rewrite/src/test/java/org/springframework/ide/vscode/commons/rewrite/maven/MavenIJavaProjectParserTest.java index 6b9aba4ec4..96ebf2e8a2 100644 --- a/headless-services/commons/commons-rewrite/src/test/java/org/springframework/ide/vscode/commons/rewrite/maven/MavenIJavaProjectParserTest.java +++ b/headless-services/commons/commons-rewrite/src/test/java/org/springframework/ide/vscode/commons/rewrite/maven/MavenIJavaProjectParserTest.java @@ -51,7 +51,7 @@ void parseResources() throws Exception { Path testProjectPath = Paths.get(jp.getLocationUri()); MavenParser.Builder mavenParserBuilder = MavenParser.builder() - .mavenConfig(testProjectPath.resolve(".mvn/maven.config")); + /*.mavenConfig(testProjectPath.resolve(".mvn/maven.config"))*/; MavenIJavaProjectParser parser = new MavenIJavaProjectParser(jp, JavaParser.fromJavaVersion(), null, mavenParserBuilder); diff --git a/headless-services/commons/pom.xml b/headless-services/commons/pom.xml index 40c643b91a..41e8366a31 100644 --- a/headless-services/commons/pom.xml +++ b/headless-services/commons/pom.xml @@ -130,17 +130,10 @@ org.openrewrite rewrite-bom - 8.42.5 + 8.64.0 pom import - - org.openrewrite.recipe - rewrite-recipe-bom - 2.23.1 - pom - import - org.gradle gradle-tooling-api diff --git a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/reconcilers/HttpSecurityLambdaDslReconciler.java b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/reconcilers/HttpSecurityLambdaDslReconciler.java index fee17bf766..3a2fd3c921 100644 --- a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/reconcilers/HttpSecurityLambdaDslReconciler.java +++ b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/reconcilers/HttpSecurityLambdaDslReconciler.java @@ -13,7 +13,7 @@ import java.util.Arrays; import java.util.Collection; -import org.openrewrite.java.spring.boot2.HttpSecurityLambdaDsl; +import org.openrewrite.java.spring.security5.HttpSecurityLambdaDsl; import org.springframework.ide.vscode.boot.java.Boot2JavaProblemType; import org.springframework.ide.vscode.commons.Version; import org.springframework.ide.vscode.commons.java.IJavaProject; diff --git a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/reconcilers/ServerHttpSecurityLambdaDslReconciler.java b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/reconcilers/ServerHttpSecurityLambdaDslReconciler.java index 269d69df2e..9e54b4c010 100644 --- a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/reconcilers/ServerHttpSecurityLambdaDslReconciler.java +++ b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/reconcilers/ServerHttpSecurityLambdaDslReconciler.java @@ -13,7 +13,7 @@ import java.util.Arrays; import java.util.Collection; -import org.openrewrite.java.spring.boot2.ServerHttpSecurityLambdaDsl; +import org.openrewrite.java.spring.security5.ServerHttpSecurityLambdaDsl; import org.springframework.ide.vscode.boot.java.Boot2JavaProblemType; import org.springframework.ide.vscode.commons.Version; import org.springframework.ide.vscode.commons.java.IJavaProject; diff --git a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/rewrite/RewriteRecipeRepository.java b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/rewrite/RewriteRecipeRepository.java index c003a9fd9e..077c4413d9 100644 --- a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/rewrite/RewriteRecipeRepository.java +++ b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/rewrite/RewriteRecipeRepository.java @@ -502,9 +502,7 @@ private void reportParseErrors(List parseErrors) { private static ProjectParser createRewriteProjectParser(IJavaProject jp, Function inputProvider) { switch (jp.getProjectBuild().getType()) { case ProjectBuild.MAVEN_PROJECT_TYPE: - Path absoluteProjectDir = Paths.get(jp.getLocationUri()).toAbsolutePath(); - MavenParser.Builder mavenParserBuilder = MavenParser.builder() - .mavenConfig(absoluteProjectDir.resolve(".mvn/maven.config")); + MavenParser.Builder mavenParserBuilder = MavenParser.builder(); return new MavenIJavaProjectParser(jp, JavaParser.fromJavaVersion(), inputProvider, mavenParserBuilder); case ProjectBuild.GRADLE_PROJECT_TYPE: return new GradleIJavaProjectParser(jp, JavaParser.fromJavaVersion(), inputProvider); diff --git a/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/app/RestTemplateFactoryTest.java b/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/app/RestTemplateFactoryTest.java index fb5791bfbc..07fbfc804d 100644 --- a/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/app/RestTemplateFactoryTest.java +++ b/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/app/RestTemplateFactoryTest.java @@ -10,9 +10,9 @@ *******************************************************************************/ package org.springframework.ide.vscode.boot.app; -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertTrue; import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; import java.util.List; import java.util.Set; diff --git a/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/index/test/SpringIndexStructureTest.java b/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/index/test/SpringIndexStructureTest.java index 5320c7a9c5..3907937eb5 100644 --- a/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/index/test/SpringIndexStructureTest.java +++ b/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/index/test/SpringIndexStructureTest.java @@ -10,9 +10,9 @@ *******************************************************************************/ package org.springframework.ide.vscode.boot.index.test; -import static org.junit.Assert.assertTrue; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertSame; +import static org.junit.jupiter.api.Assertions.assertTrue; import java.io.File; import java.util.List; diff --git a/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/index/test/SpringIndexerBeanRegistrarTest.java b/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/index/test/SpringIndexerBeanRegistrarTest.java index aa8c104fc4..3542a0eb24 100644 --- a/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/index/test/SpringIndexerBeanRegistrarTest.java +++ b/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/index/test/SpringIndexerBeanRegistrarTest.java @@ -10,9 +10,9 @@ *******************************************************************************/ package org.springframework.ide.vscode.boot.index.test; -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertTrue; import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; import java.io.File; import java.util.Arrays; @@ -33,7 +33,6 @@ import org.springframework.ide.vscode.boot.java.Annotations; import org.springframework.ide.vscode.commons.languageserver.java.JavaProjectFinder; import org.springframework.ide.vscode.commons.protocol.spring.Bean; -import org.springframework.ide.vscode.commons.protocol.spring.BeanMethodContainerElement; import org.springframework.ide.vscode.commons.protocol.spring.BeanRegistrarElement; import org.springframework.ide.vscode.commons.protocol.spring.DocumentElement; import org.springframework.ide.vscode.commons.protocol.spring.SpringIndexElement; diff --git a/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/java/beans/test/BeanCompletionProviderTest.java b/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/java/beans/test/BeanCompletionProviderTest.java index e8f79e3d57..477766eec2 100644 --- a/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/java/beans/test/BeanCompletionProviderTest.java +++ b/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/java/beans/test/BeanCompletionProviderTest.java @@ -10,9 +10,9 @@ *******************************************************************************/ package org.springframework.ide.vscode.boot.java.beans.test; -import static org.junit.Assert.assertArrayEquals; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertTrue; +import static org.junit.jupiter.api.Assertions.assertArrayEquals; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; import java.io.File; import java.util.List; diff --git a/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/java/beans/test/DependsOnCompletionProviderTest.java b/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/java/beans/test/DependsOnCompletionProviderTest.java index d260c8e94a..9d21a14e44 100644 --- a/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/java/beans/test/DependsOnCompletionProviderTest.java +++ b/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/java/beans/test/DependsOnCompletionProviderTest.java @@ -10,7 +10,7 @@ *******************************************************************************/ package org.springframework.ide.vscode.boot.java.beans.test; -import static org.junit.Assert.assertEquals; +import static org.junit.jupiter.api.Assertions.assertEquals; import java.io.File; import java.util.List; diff --git a/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/java/beans/test/DependsOnDefinitionProviderTest.java b/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/java/beans/test/DependsOnDefinitionProviderTest.java index 61a30a1105..001df9a4f0 100644 --- a/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/java/beans/test/DependsOnDefinitionProviderTest.java +++ b/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/java/beans/test/DependsOnDefinitionProviderTest.java @@ -10,10 +10,9 @@ *******************************************************************************/ package org.springframework.ide.vscode.boot.java.beans.test; -import static org.junit.Assert.assertEquals; +import static org.junit.jupiter.api.Assertions.assertEquals; import java.io.File; -import java.util.ArrayList; import java.util.List; import java.util.concurrent.CompletableFuture; import java.util.concurrent.TimeUnit; diff --git a/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/java/beans/test/FeignSymbolProviderTest.java b/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/java/beans/test/FeignSymbolProviderTest.java index cdf39937ce..ea71bcab73 100644 --- a/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/java/beans/test/FeignSymbolProviderTest.java +++ b/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/java/beans/test/FeignSymbolProviderTest.java @@ -10,7 +10,7 @@ *******************************************************************************/ package org.springframework.ide.vscode.boot.java.beans.test; -import static org.junit.Assert.assertArrayEquals; +import static org.junit.jupiter.api.Assertions.assertArrayEquals; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertTrue; diff --git a/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/java/beans/test/NamedCompletionProviderTest.java b/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/java/beans/test/NamedCompletionProviderTest.java index 2fcabdd91b..a02df7c0a3 100644 --- a/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/java/beans/test/NamedCompletionProviderTest.java +++ b/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/java/beans/test/NamedCompletionProviderTest.java @@ -10,8 +10,8 @@ *******************************************************************************/ package org.springframework.ide.vscode.boot.java.beans.test; -import static org.junit.Assert.assertArrayEquals; -import static org.junit.Assert.assertEquals; +import static org.junit.jupiter.api.Assertions.assertArrayEquals; +import static org.junit.jupiter.api.Assertions.assertEquals; import java.io.File; import java.util.List; diff --git a/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/java/beans/test/NamedReferencesProviderTest.java b/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/java/beans/test/NamedReferencesProviderTest.java index cd68fcd68b..9b9610209d 100644 --- a/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/java/beans/test/NamedReferencesProviderTest.java +++ b/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/java/beans/test/NamedReferencesProviderTest.java @@ -10,8 +10,8 @@ *******************************************************************************/ package org.springframework.ide.vscode.boot.java.beans.test; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertNull; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNull; import java.io.File; import java.util.List; diff --git a/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/java/beans/test/ProfileCompletionProviderTest.java b/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/java/beans/test/ProfileCompletionProviderTest.java index 4876f97284..b770d6c6d5 100644 --- a/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/java/beans/test/ProfileCompletionProviderTest.java +++ b/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/java/beans/test/ProfileCompletionProviderTest.java @@ -10,8 +10,8 @@ *******************************************************************************/ package org.springframework.ide.vscode.boot.java.beans.test; -import static org.junit.Assert.assertArrayEquals; -import static org.junit.Assert.assertEquals; +import static org.junit.jupiter.api.Assertions.assertArrayEquals; +import static org.junit.jupiter.api.Assertions.assertEquals; import java.io.File; import java.util.List; diff --git a/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/java/beans/test/ProfileReferencesProviderTest.java b/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/java/beans/test/ProfileReferencesProviderTest.java index a2f5a116c1..31a2d8e140 100644 --- a/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/java/beans/test/ProfileReferencesProviderTest.java +++ b/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/java/beans/test/ProfileReferencesProviderTest.java @@ -10,8 +10,8 @@ *******************************************************************************/ package org.springframework.ide.vscode.boot.java.beans.test; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertTrue; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; import java.io.File; import java.util.List; diff --git a/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/java/beans/test/QualifierCompletionProviderTest.java b/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/java/beans/test/QualifierCompletionProviderTest.java index 8c9c7e8c26..7718199491 100644 --- a/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/java/beans/test/QualifierCompletionProviderTest.java +++ b/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/java/beans/test/QualifierCompletionProviderTest.java @@ -10,8 +10,8 @@ *******************************************************************************/ package org.springframework.ide.vscode.boot.java.beans.test; -import static org.junit.Assert.assertArrayEquals; -import static org.junit.Assert.assertEquals; +import static org.junit.jupiter.api.Assertions.assertArrayEquals; +import static org.junit.jupiter.api.Assertions.assertEquals; import java.io.File; import java.util.List; diff --git a/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/java/beans/test/QualifierDefinitionProviderTest.java b/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/java/beans/test/QualifierDefinitionProviderTest.java index 72c9bc0f61..38b59df5f4 100644 --- a/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/java/beans/test/QualifierDefinitionProviderTest.java +++ b/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/java/beans/test/QualifierDefinitionProviderTest.java @@ -10,7 +10,7 @@ *******************************************************************************/ package org.springframework.ide.vscode.boot.java.beans.test; -import static org.junit.Assert.assertEquals; +import static org.junit.jupiter.api.Assertions.assertEquals; import java.io.File; import java.util.List; diff --git a/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/java/beans/test/QualifierReferencesProviderTest.java b/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/java/beans/test/QualifierReferencesProviderTest.java index 865a6dfef2..4242f35680 100644 --- a/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/java/beans/test/QualifierReferencesProviderTest.java +++ b/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/java/beans/test/QualifierReferencesProviderTest.java @@ -10,8 +10,8 @@ *******************************************************************************/ package org.springframework.ide.vscode.boot.java.beans.test; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertNull; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNull; import java.io.File; import java.util.List; diff --git a/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/java/beans/test/ResourceCompletionProviderTest.java b/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/java/beans/test/ResourceCompletionProviderTest.java index 0b1151810e..bfe6fa0313 100644 --- a/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/java/beans/test/ResourceCompletionProviderTest.java +++ b/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/java/beans/test/ResourceCompletionProviderTest.java @@ -10,8 +10,8 @@ *******************************************************************************/ package org.springframework.ide.vscode.boot.java.beans.test; -import static org.junit.Assert.assertArrayEquals; -import static org.junit.Assert.assertEquals; +import static org.junit.jupiter.api.Assertions.assertArrayEquals; +import static org.junit.jupiter.api.Assertions.assertEquals; import java.io.File; import java.util.List; diff --git a/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/java/beans/test/ResourceDefinitionProviderTest.java b/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/java/beans/test/ResourceDefinitionProviderTest.java index 7cd99392aa..f266f1e3eb 100644 --- a/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/java/beans/test/ResourceDefinitionProviderTest.java +++ b/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/java/beans/test/ResourceDefinitionProviderTest.java @@ -10,7 +10,7 @@ *******************************************************************************/ package org.springframework.ide.vscode.boot.java.beans.test; -import static org.junit.Assert.assertEquals; +import static org.junit.jupiter.api.Assertions.assertEquals; import java.io.File; import java.util.List; diff --git a/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/java/beans/test/SpringIndexerBeansTest.java b/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/java/beans/test/SpringIndexerBeansTest.java index 19960ac818..e170a4df6c 100644 --- a/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/java/beans/test/SpringIndexerBeansTest.java +++ b/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/java/beans/test/SpringIndexerBeansTest.java @@ -10,8 +10,8 @@ *******************************************************************************/ package org.springframework.ide.vscode.boot.java.beans.test; -import static org.junit.Assert.assertSame; -import static org.junit.Assert.assertTrue; +import static org.junit.jupiter.api.Assertions.assertSame; +import static org.junit.jupiter.api.Assertions.assertTrue; import static org.junit.jupiter.api.Assertions.assertEquals; import java.io.File; diff --git a/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/java/beans/test/SpringIndexerJakartaJavaxAnnotationsTest.java b/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/java/beans/test/SpringIndexerJakartaJavaxAnnotationsTest.java index 43f8d55d7d..56f1453499 100644 --- a/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/java/beans/test/SpringIndexerJakartaJavaxAnnotationsTest.java +++ b/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/java/beans/test/SpringIndexerJakartaJavaxAnnotationsTest.java @@ -10,7 +10,7 @@ *******************************************************************************/ package org.springframework.ide.vscode.boot.java.beans.test; -import static org.junit.Assert.assertEquals; +import static org.junit.jupiter.api.Assertions.assertEquals; import java.io.File; import java.util.concurrent.CompletableFuture; diff --git a/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/java/conditionals/test/ConditionalOnBeanCompletionTest.java b/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/java/conditionals/test/ConditionalOnBeanCompletionTest.java index b1d3172c02..32cb49b2b3 100644 --- a/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/java/conditionals/test/ConditionalOnBeanCompletionTest.java +++ b/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/java/conditionals/test/ConditionalOnBeanCompletionTest.java @@ -10,7 +10,7 @@ *******************************************************************************/ package org.springframework.ide.vscode.boot.java.conditionals.test; -import static org.junit.Assert.assertEquals; +import static org.junit.jupiter.api.Assertions.assertEquals; import java.io.File; import java.util.List; diff --git a/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/java/conditionals/test/ConditionalOnBeanDefinitionProviderTest.java b/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/java/conditionals/test/ConditionalOnBeanDefinitionProviderTest.java index 6246cac2c0..b21a843128 100644 --- a/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/java/conditionals/test/ConditionalOnBeanDefinitionProviderTest.java +++ b/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/java/conditionals/test/ConditionalOnBeanDefinitionProviderTest.java @@ -10,7 +10,7 @@ *******************************************************************************/ package org.springframework.ide.vscode.boot.java.conditionals.test; -import static org.junit.Assert.assertEquals; +import static org.junit.jupiter.api.Assertions.assertEquals; import java.io.File; import java.util.List; diff --git a/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/java/cron/CronExpressionCompletionProviderTest.java b/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/java/cron/CronExpressionCompletionProviderTest.java index bdc85f8dfe..cb08bba7f5 100644 --- a/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/java/cron/CronExpressionCompletionProviderTest.java +++ b/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/java/cron/CronExpressionCompletionProviderTest.java @@ -10,8 +10,8 @@ *******************************************************************************/ package org.springframework.ide.vscode.boot.java.cron; -import static org.junit.Assert.assertArrayEquals; -import static org.junit.Assert.assertEquals; +import static org.junit.jupiter.api.Assertions.assertArrayEquals; +import static org.junit.jupiter.api.Assertions.assertEquals; import java.io.File; import java.util.List; @@ -19,7 +19,6 @@ import java.util.concurrent.TimeUnit; import org.eclipse.lsp4j.CompletionItem; -import org.eclipse.lsp4j.TextDocumentIdentifier; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; @@ -28,8 +27,6 @@ import org.springframework.ide.vscode.boot.app.SpringSymbolIndex; import org.springframework.ide.vscode.boot.bootiful.BootLanguageServerTest; import org.springframework.ide.vscode.boot.bootiful.SymbolProviderTestConf; -import org.springframework.ide.vscode.commons.java.IJavaProject; -import org.springframework.ide.vscode.commons.languageserver.java.JavaProjectFinder; import org.springframework.ide.vscode.commons.util.text.LanguageId; import org.springframework.ide.vscode.languageserver.testharness.Editor; import org.springframework.ide.vscode.project.harness.BootLanguageServerHarness; @@ -44,11 +41,9 @@ @Import(SymbolProviderTestConf.class) public class CronExpressionCompletionProviderTest { @Autowired private BootLanguageServerHarness harness; - @Autowired private JavaProjectFinder projectFinder; @Autowired private SpringSymbolIndex indexer; private File directory; - private IJavaProject project; private String tempJavaDocUri; @BeforeEach @@ -57,9 +52,6 @@ public void setup() throws Exception { directory = new File(ProjectsHarness.class.getResource("/test-projects/test-annotations/").toURI()); - String projectDir = directory.toURI().toString(); - project = projectFinder.find(new TextDocumentIdentifier(projectDir)).get(); - CompletableFuture initProject = indexer.waitOperation(); initProject.get(5, TimeUnit.SECONDS); diff --git a/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/java/cron/CronReconcilerTest.java b/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/java/cron/CronReconcilerTest.java index 25091ae526..98fab9cef0 100644 --- a/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/java/cron/CronReconcilerTest.java +++ b/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/java/cron/CronReconcilerTest.java @@ -10,7 +10,7 @@ *******************************************************************************/ package org.springframework.ide.vscode.boot.java.cron; -import static org.junit.Assert.assertEquals; +import static org.junit.jupiter.api.Assertions.assertEquals; import java.util.ArrayList; import java.util.List; diff --git a/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/java/data/test/DataRepositoryAotMetadataCodeLensProviderJpaTest.java b/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/java/data/test/DataRepositoryAotMetadataCodeLensProviderJpaTest.java index c66cdc4b7b..e83104fc74 100644 --- a/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/java/data/test/DataRepositoryAotMetadataCodeLensProviderJpaTest.java +++ b/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/java/data/test/DataRepositoryAotMetadataCodeLensProviderJpaTest.java @@ -10,7 +10,7 @@ *******************************************************************************/ package org.springframework.ide.vscode.boot.java.data.test; -import static org.junit.Assert.assertEquals; +import static org.junit.jupiter.api.Assertions.assertEquals; import java.nio.charset.StandardCharsets; import java.nio.file.Files; diff --git a/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/java/data/test/DataRepositoryAotMetadataCodeLensProviderMongoDbTest.java b/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/java/data/test/DataRepositoryAotMetadataCodeLensProviderMongoDbTest.java index e1ffa628ce..5292733d04 100644 --- a/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/java/data/test/DataRepositoryAotMetadataCodeLensProviderMongoDbTest.java +++ b/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/java/data/test/DataRepositoryAotMetadataCodeLensProviderMongoDbTest.java @@ -10,7 +10,7 @@ *******************************************************************************/ package org.springframework.ide.vscode.boot.java.data.test; -import static org.junit.Assert.assertEquals; +import static org.junit.jupiter.api.Assertions.assertEquals; import java.nio.charset.StandardCharsets; import java.nio.file.Files; diff --git a/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/java/data/test/DataRepositoryAotMetadataServiceTest.java b/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/java/data/test/DataRepositoryAotMetadataServiceTest.java index 470d4bbce0..92ed93f4d4 100644 --- a/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/java/data/test/DataRepositoryAotMetadataServiceTest.java +++ b/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/java/data/test/DataRepositoryAotMetadataServiceTest.java @@ -10,8 +10,8 @@ *******************************************************************************/ package org.springframework.ide.vscode.boot.java.data.test; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; import java.net.URI; import java.util.Arrays; diff --git a/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/java/data/test/DataRepositoryIndexElementsTest.java b/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/java/data/test/DataRepositoryIndexElementsTest.java index 4075cf7be6..6d03783ff3 100644 --- a/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/java/data/test/DataRepositoryIndexElementsTest.java +++ b/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/java/data/test/DataRepositoryIndexElementsTest.java @@ -10,7 +10,7 @@ *******************************************************************************/ package org.springframework.ide.vscode.boot.java.data.test; -import static org.junit.Assert.assertTrue; +import static org.junit.jupiter.api.Assertions.assertTrue; import static org.junit.jupiter.api.Assertions.assertEquals; import java.io.File; diff --git a/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/java/events/test/EventsReferencesProviderTest.java b/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/java/events/test/EventsReferencesProviderTest.java index 4ed9a7cc7b..2901e96ff3 100644 --- a/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/java/events/test/EventsReferencesProviderTest.java +++ b/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/java/events/test/EventsReferencesProviderTest.java @@ -10,8 +10,8 @@ *******************************************************************************/ package org.springframework.ide.vscode.boot.java.events.test; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertTrue; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; import java.io.File; import java.util.List; diff --git a/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/java/events/test/SpringIndexerEventsTest.java b/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/java/events/test/SpringIndexerEventsTest.java index 9cac222a79..67d89410ef 100644 --- a/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/java/events/test/SpringIndexerEventsTest.java +++ b/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/java/events/test/SpringIndexerEventsTest.java @@ -10,11 +10,11 @@ *******************************************************************************/ package org.springframework.ide.vscode.boot.java.events.test; -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertNotNull; -import static org.junit.Assert.assertSame; -import static org.junit.Assert.assertTrue; import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertSame; +import static org.junit.jupiter.api.Assertions.assertTrue; import java.io.File; import java.util.Arrays; @@ -43,7 +43,6 @@ import org.springframework.ide.vscode.boot.java.events.EventPublisherIndexElement; import org.springframework.ide.vscode.commons.languageserver.java.JavaProjectFinder; import org.springframework.ide.vscode.commons.protocol.spring.Bean; -import org.springframework.ide.vscode.commons.protocol.spring.BeanMethodContainerElement; import org.springframework.ide.vscode.commons.protocol.spring.DocumentElement; import org.springframework.ide.vscode.commons.protocol.spring.SpringIndexElement; import org.springframework.ide.vscode.project.harness.BootLanguageServerHarness; diff --git a/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/java/requestmapping/test/HttpExchangeIndexElementsTest.java b/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/java/requestmapping/test/HttpExchangeIndexElementsTest.java index 385f9e21b9..b9082de96d 100644 --- a/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/java/requestmapping/test/HttpExchangeIndexElementsTest.java +++ b/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/java/requestmapping/test/HttpExchangeIndexElementsTest.java @@ -10,7 +10,7 @@ *******************************************************************************/ package org.springframework.ide.vscode.boot.java.requestmapping.test; -import static org.junit.Assert.assertArrayEquals; +import static org.junit.jupiter.api.Assertions.assertArrayEquals; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertTrue; diff --git a/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/java/requestmapping/test/WebConfigCodeLensProviderTest.java b/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/java/requestmapping/test/WebConfigCodeLensProviderTest.java index 2e846f393c..11748f124a 100644 --- a/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/java/requestmapping/test/WebConfigCodeLensProviderTest.java +++ b/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/java/requestmapping/test/WebConfigCodeLensProviderTest.java @@ -10,7 +10,7 @@ *******************************************************************************/ package org.springframework.ide.vscode.boot.java.requestmapping.test; -import static org.junit.Assert.assertTrue; +import static org.junit.jupiter.api.Assertions.assertTrue; import java.nio.charset.StandardCharsets; import java.nio.file.Files; diff --git a/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/java/requestmapping/test/WebVersionSupportTest.java b/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/java/requestmapping/test/WebVersionSupportTest.java index ba9606d846..b90cf19f3d 100644 --- a/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/java/requestmapping/test/WebVersionSupportTest.java +++ b/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/java/requestmapping/test/WebVersionSupportTest.java @@ -10,7 +10,7 @@ *******************************************************************************/ package org.springframework.ide.vscode.boot.java.requestmapping.test; -import static org.junit.Assert.assertSame; +import static org.junit.jupiter.api.Assertions.assertSame; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertTrue; diff --git a/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/java/rewrite/RewriteRecipeRepositoryTest.java b/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/java/rewrite/RewriteRecipeRepositoryTest.java index 1cee486080..a780696739 100644 --- a/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/java/rewrite/RewriteRecipeRepositoryTest.java +++ b/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/java/rewrite/RewriteRecipeRepositoryTest.java @@ -15,6 +15,7 @@ import java.util.List; +import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.openrewrite.Recipe; @@ -28,6 +29,7 @@ @ExtendWith(SpringExtension.class) @BootLanguageServerTest @Import(SymbolProviderTestConf.class) +@Disabled public class RewriteRecipeRepositoryTest { @Autowired RewriteRecipeRepository recipeRepo; diff --git a/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/java/spel/SpelDefinitionProviderTest.java b/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/java/spel/SpelDefinitionProviderTest.java index 33d97f0cd1..508970eac3 100644 --- a/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/java/spel/SpelDefinitionProviderTest.java +++ b/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/java/spel/SpelDefinitionProviderTest.java @@ -10,7 +10,7 @@ *******************************************************************************/ package org.springframework.ide.vscode.boot.java.spel; -import static org.junit.Assert.assertEquals; +import static org.junit.jupiter.api.Assertions.assertEquals; import java.io.File; import java.util.List; diff --git a/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/java/stereotypes/ProjectBasedCatalogSourceTest.java b/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/java/stereotypes/ProjectBasedCatalogSourceTest.java index fd0da49e40..57f247f7d7 100644 --- a/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/java/stereotypes/ProjectBasedCatalogSourceTest.java +++ b/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/java/stereotypes/ProjectBasedCatalogSourceTest.java @@ -10,8 +10,8 @@ *******************************************************************************/ package org.springframework.ide.vscode.boot.java.stereotypes; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertTrue; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; import java.io.File; import java.net.URL; diff --git a/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/java/stereotypes/StereotypesIndexerTest.java b/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/java/stereotypes/StereotypesIndexerTest.java index 666fe853ac..b97dac62bd 100644 --- a/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/java/stereotypes/StereotypesIndexerTest.java +++ b/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/java/stereotypes/StereotypesIndexerTest.java @@ -10,9 +10,9 @@ *******************************************************************************/ package org.springframework.ide.vscode.boot.java.stereotypes; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertTrue; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; import static org.junit.jupiter.api.Assertions.assertNotNull; import java.io.File; diff --git a/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/java/utils/test/ASTUtilsTest.java b/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/java/utils/test/ASTUtilsTest.java index ada92c546e..802e515d49 100644 --- a/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/java/utils/test/ASTUtilsTest.java +++ b/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/java/utils/test/ASTUtilsTest.java @@ -10,10 +10,10 @@ *******************************************************************************/ package org.springframework.ide.vscode.boot.java.utils.test; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertNotNull; -import static org.junit.Assert.assertTrue; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertTrue; import java.io.IOException; import java.nio.charset.StandardCharsets; diff --git a/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/maven/PomInlayHintHandlerTest.java b/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/maven/PomInlayHintHandlerTest.java index f52a8fc1b6..d632011597 100644 --- a/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/maven/PomInlayHintHandlerTest.java +++ b/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/maven/PomInlayHintHandlerTest.java @@ -10,8 +10,8 @@ *******************************************************************************/ package org.springframework.ide.vscode.boot.maven; -import static org.junit.Assert.assertNotNull; -import static org.junit.Assert.assertTrue; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertTrue; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyString; diff --git a/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/xml/test/XMLBeanRefContentAssistTest.java b/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/xml/test/XMLBeanRefContentAssistTest.java index 79cd027fd6..f897603362 100644 --- a/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/xml/test/XMLBeanRefContentAssistTest.java +++ b/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/xml/test/XMLBeanRefContentAssistTest.java @@ -10,8 +10,8 @@ *******************************************************************************/ package org.springframework.ide.vscode.boot.xml.test; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertTrue; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; import java.io.File; import java.util.HashMap; diff --git a/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/xml/test/XMLContentAssistTest.java b/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/xml/test/XMLContentAssistTest.java index ed53c4c904..33cc06d5cf 100644 --- a/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/xml/test/XMLContentAssistTest.java +++ b/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/xml/test/XMLContentAssistTest.java @@ -10,8 +10,8 @@ *******************************************************************************/ package org.springframework.ide.vscode.boot.xml.test; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; import java.util.HashMap; import java.util.List; From f8707696f33c802a82c4b376632fbcc0a4227aa8 Mon Sep 17 00:00:00 2001 From: BoykoAlex Date: Tue, 28 Oct 2025 18:23:18 -0700 Subject: [PATCH 2/6] UI tweaks to not show Rewrite upgrades Signed-off-by: BoykoAlex --- .../plugin.xml | 2 + .../boot/ls/prefs/PrefsInitializer.java | 2 +- .../ide/vscode/commons/rewrite/LoadUtils.java | 2 +- .../rewrite/UpgradeSpringBoot_3_4.yml | 50 -------- .../rewrite/UpgradeSpringBoot_3_5.yml | 46 ------- .../rewrite/spring-boot-34-properties.yml | 120 ------------------ .../rewrite/spring-boot-35-properties.yml | 94 -------------- .../META-INF/rewrite/upgrade-boot3.yml | 33 ----- .../src/main/resources/init.gradle | 4 +- headless-services/commons/pom.xml | 2 +- .../boot/java/rewrite/SpringBootUpgrade.java | 12 ++ .../generations/UpdateBootVersion.java | 4 +- 12 files changed, 21 insertions(+), 350 deletions(-) delete mode 100644 headless-services/commons/commons-rewrite/src/main/resources/META-INF/rewrite/UpgradeSpringBoot_3_4.yml delete mode 100644 headless-services/commons/commons-rewrite/src/main/resources/META-INF/rewrite/UpgradeSpringBoot_3_5.yml delete mode 100644 headless-services/commons/commons-rewrite/src/main/resources/META-INF/rewrite/spring-boot-34-properties.yml delete mode 100644 headless-services/commons/commons-rewrite/src/main/resources/META-INF/rewrite/spring-boot-35-properties.yml delete mode 100644 headless-services/commons/commons-rewrite/src/main/resources/META-INF/rewrite/upgrade-boot3.yml diff --git a/eclipse-language-servers/org.springframework.tooling.boot.ls/plugin.xml b/eclipse-language-servers/org.springframework.tooling.boot.ls/plugin.xml index ef018ee22c..b7c9aa0ff0 100644 --- a/eclipse-language-servers/org.springframework.tooling.boot.ls/plugin.xml +++ b/eclipse-language-servers/org.springframework.tooling.boot.ls/plugin.xml @@ -232,12 +232,14 @@ id="org.springsource.ide.eclipse.commons.preferencePage" name="Spring"> + recipeClazz = getRecipeClass == null ? null : getRecipeClass.apply(d.getName()); if (recipeClazz == null || DeclarativeRecipe.class.getName().equals(recipeClazz.getName())) { DeclarativeRecipe recipe = new DeclarativeRecipe(d.getName(), d.getDisplayName(), d.getDescription(), - d.getTags(), d.getEstimatedEffortPerOccurrence(), d.getSource(), false, d.getMaintainers()); + d.getTags(), d.getEstimatedEffortPerOccurrence(), null, false, d.getMaintainers()); if (!shallow) { for (RecipeDescriptor subDescriptor : d.getRecipeList()) { recipe.getRecipeList().add(createRecipe(subDescriptor, getRecipeClass)); diff --git a/headless-services/commons/commons-rewrite/src/main/resources/META-INF/rewrite/UpgradeSpringBoot_3_4.yml b/headless-services/commons/commons-rewrite/src/main/resources/META-INF/rewrite/UpgradeSpringBoot_3_4.yml deleted file mode 100644 index 969ec5a543..0000000000 --- a/headless-services/commons/commons-rewrite/src/main/resources/META-INF/rewrite/UpgradeSpringBoot_3_4.yml +++ /dev/null @@ -1,50 +0,0 @@ -# -# Copyright 2024 the original author or authors. -#

-# 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 -#

-# https://www.apache.org/licenses/LICENSE-2.0 -#

-# 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. -# ---- -type: specs.openrewrite.org/v1beta/recipe -name: org.springframework.ide.vscode.rewrite.boot3.UpgradeSpringBoot_3_4 -displayName: Migrate to Spring Boot 3.4 -description: >- - Migrate applications to the latest Spring Boot 3.4 release. This recipe will modify an - application's build files and migrate configuration settings that have - changes between versions. -tags: - - spring - - boot -recipeList: - - org.openrewrite.java.spring.boot3.UpgradeSpringBoot_3_3 - - org.openrewrite.java.spring.boot3.SpringBootProperties_3_4 - - org.openrewrite.java.dependencies.UpgradeDependencyVersion: - groupId: org.springframework.boot - artifactId: "*" - newVersion: 3.4.x - overrideManagedVersion: false - - org.openrewrite.maven.UpgradePluginVersion: - groupId: org.springframework.boot - artifactId: spring-boot-maven-plugin - newVersion: 3.4.x - - org.openrewrite.java.dependencies.UpgradeDependencyVersion: - groupId: org.springframework - artifactId: "*" - newVersion: 6.2.x - - org.openrewrite.maven.UpgradeParentVersion: - groupId: org.springframework.boot - artifactId: spring-boot-starter-parent - newVersion: 3.4.x - - org.openrewrite.gradle.plugins.UpgradePluginVersion: - pluginIdPattern: org.springframework.boot - newVersion: 3.4.x - diff --git a/headless-services/commons/commons-rewrite/src/main/resources/META-INF/rewrite/UpgradeSpringBoot_3_5.yml b/headless-services/commons/commons-rewrite/src/main/resources/META-INF/rewrite/UpgradeSpringBoot_3_5.yml deleted file mode 100644 index 858cdedac3..0000000000 --- a/headless-services/commons/commons-rewrite/src/main/resources/META-INF/rewrite/UpgradeSpringBoot_3_5.yml +++ /dev/null @@ -1,46 +0,0 @@ -# -# Copyright 2024 the original author or authors. -#

-# 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 -#

-# https://www.apache.org/licenses/LICENSE-2.0 -#

-# 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. -# ---- -type: specs.openrewrite.org/v1beta/recipe -name: org.springframework.ide.vscode.rewrite.boot3.UpgradeSpringBoot_3_5 -displayName: Migrate to Spring Boot 3.5 -description: >- - Migrate applications to the latest Spring Boot 3.5 release. This recipe will modify an - application's build files and migrate configuration settings that have - changes between versions. -tags: - - spring - - boot -recipeList: - - org.springframework.ide.vscode.rewrite.boot3.UpgradeSpringBoot_3_4 - - org.openrewrite.java.spring.boot3.SpringBootProperties_3_5 - - org.openrewrite.java.dependencies.UpgradeDependencyVersion: - groupId: org.springframework.boot - artifactId: "*" - newVersion: 3.5.x - overrideManagedVersion: false - - org.openrewrite.maven.UpgradePluginVersion: - groupId: org.springframework.boot - artifactId: spring-boot-maven-plugin - newVersion: 3.5.x - - org.openrewrite.maven.UpgradeParentVersion: - groupId: org.springframework.boot - artifactId: spring-boot-starter-parent - newVersion: 3.5.x - - org.openrewrite.gradle.plugins.UpgradePluginVersion: - pluginIdPattern: org.springframework.boot - newVersion: 3.5.x - diff --git a/headless-services/commons/commons-rewrite/src/main/resources/META-INF/rewrite/spring-boot-34-properties.yml b/headless-services/commons/commons-rewrite/src/main/resources/META-INF/rewrite/spring-boot-34-properties.yml deleted file mode 100644 index 5af7958f03..0000000000 --- a/headless-services/commons/commons-rewrite/src/main/resources/META-INF/rewrite/spring-boot-34-properties.yml +++ /dev/null @@ -1,120 +0,0 @@ -# -# Copyright 2024 the original author or authors. -#

-# 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 -#

-# https://www.apache.org/licenses/LICENSE-2.0 -#

-# 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. -# -# This file is automatically generated by the GeneratePropertiesMigratorConfiguration class. -# Do not edit this file manually. Update the Spring Boot property metadata upstream instead. ---- -type: specs.openrewrite.org/v1beta/recipe -name: org.openrewrite.java.spring.boot3.SpringBootProperties_3_4 -displayName: Migrate Spring Boot properties to 3.4 -description: Migrate properties found in `application.properties` and `application.yml`. -tags: - - spring - - boot -recipeList: - - org.openrewrite.java.spring.ChangeSpringPropertyKey: - oldPropertyKey: management.endpoint.auditevents.enabled - newPropertyKey: management.endpoint.auditevents.access - - org.openrewrite.java.spring.ChangeSpringPropertyKey: - oldPropertyKey: management.endpoint.beans.enabled - newPropertyKey: management.endpoint.beans.access - - org.openrewrite.java.spring.ChangeSpringPropertyKey: - oldPropertyKey: management.endpoint.caches.enabled - newPropertyKey: management.endpoint.caches.access - - org.openrewrite.java.spring.ChangeSpringPropertyKey: - oldPropertyKey: management.endpoint.conditions.enabled - newPropertyKey: management.endpoint.conditions.access - - org.openrewrite.java.spring.ChangeSpringPropertyKey: - oldPropertyKey: management.endpoint.configprops.enabled - newPropertyKey: management.endpoint.configprops.access - - org.openrewrite.java.spring.ChangeSpringPropertyKey: - oldPropertyKey: management.endpoint.env.enabled - newPropertyKey: management.endpoint.env.access - - org.openrewrite.java.spring.ChangeSpringPropertyKey: - oldPropertyKey: management.endpoint.flyway.enabled - newPropertyKey: management.endpoint.flyway.access - - org.openrewrite.java.spring.ChangeSpringPropertyKey: - oldPropertyKey: management.endpoint.health.enabled - newPropertyKey: management.endpoint.health.access - - org.openrewrite.java.spring.ChangeSpringPropertyKey: - oldPropertyKey: management.endpoint.heapdump.enabled - newPropertyKey: management.endpoint.heapdump.access - - org.openrewrite.java.spring.ChangeSpringPropertyKey: - oldPropertyKey: management.endpoint.httpexchanges.enabled - newPropertyKey: management.endpoint.httpexchanges.access - - org.openrewrite.java.spring.ChangeSpringPropertyKey: - oldPropertyKey: management.endpoint.info.enabled - newPropertyKey: management.endpoint.info.access - - org.openrewrite.java.spring.ChangeSpringPropertyKey: - oldPropertyKey: management.endpoint.integrationgraph.enabled - newPropertyKey: management.endpoint.integrationgraph.access - - org.openrewrite.java.spring.ChangeSpringPropertyKey: - oldPropertyKey: management.endpoint.liquibase.enabled - newPropertyKey: management.endpoint.liquibase.access - - org.openrewrite.java.spring.ChangeSpringPropertyKey: - oldPropertyKey: management.endpoint.logfile.enabled - newPropertyKey: management.endpoint.logfile.access - - org.openrewrite.java.spring.ChangeSpringPropertyKey: - oldPropertyKey: management.endpoint.loggers.enabled - newPropertyKey: management.endpoint.loggers.access - - org.openrewrite.java.spring.ChangeSpringPropertyKey: - oldPropertyKey: management.endpoint.mappings.enabled - newPropertyKey: management.endpoint.mappings.access - - org.openrewrite.java.spring.ChangeSpringPropertyKey: - oldPropertyKey: management.endpoint.metrics.enabled - newPropertyKey: management.endpoint.metrics.access - - org.openrewrite.java.spring.ChangeSpringPropertyKey: - oldPropertyKey: management.endpoint.prometheus.enabled - newPropertyKey: management.endpoint.prometheus.access - - org.openrewrite.java.spring.ChangeSpringPropertyKey: - oldPropertyKey: management.endpoint.quartz.enabled - newPropertyKey: management.endpoint.quartz.access - - org.openrewrite.java.spring.ChangeSpringPropertyKey: - oldPropertyKey: management.endpoint.sbom.enabled - newPropertyKey: management.endpoint.sbom.access - - org.openrewrite.java.spring.ChangeSpringPropertyKey: - oldPropertyKey: management.endpoint.scheduledtasks.enabled - newPropertyKey: management.endpoint.scheduledtasks.access - - org.openrewrite.java.spring.ChangeSpringPropertyKey: - oldPropertyKey: management.endpoint.sessions.enabled - newPropertyKey: management.endpoint.sessions.access - - org.openrewrite.java.spring.ChangeSpringPropertyKey: - oldPropertyKey: management.endpoint.shutdown.enabled - newPropertyKey: management.endpoint.shutdown.access - - org.openrewrite.java.spring.ChangeSpringPropertyKey: - oldPropertyKey: management.endpoint.startup.enabled - newPropertyKey: management.endpoint.startup.access - - org.openrewrite.java.spring.ChangeSpringPropertyKey: - oldPropertyKey: management.endpoint.threaddump.enabled - newPropertyKey: management.endpoint.threaddump.access - - org.openrewrite.java.spring.ChangeSpringPropertyKey: - oldPropertyKey: management.endpoints.enabled-by-default - newPropertyKey: management.endpoints.access.default - - org.openrewrite.java.spring.ChangeSpringPropertyKey: - oldPropertyKey: spring.gson.lenient - newPropertyKey: spring.gson.strictness - - org.openrewrite.java.spring.ChangeSpringPropertyKey: - oldPropertyKey: spring.kafka.retry.topic.delay - newPropertyKey: spring.kafka.retry.topic.backoff.delay - - org.openrewrite.java.spring.ChangeSpringPropertyKey: - oldPropertyKey: spring.kafka.retry.topic.max-delay - newPropertyKey: spring.kafka.retry.topic.backoff.maxDelay - - org.openrewrite.java.spring.ChangeSpringPropertyKey: - oldPropertyKey: spring.kafka.retry.topic.multiplier - newPropertyKey: spring.kafka.retry.topic.backoff.multiplier - - org.openrewrite.java.spring.ChangeSpringPropertyKey: - oldPropertyKey: spring.kafka.retry.topic.random-back-off - newPropertyKey: spring.kafka.retry.topic.backoff.random - diff --git a/headless-services/commons/commons-rewrite/src/main/resources/META-INF/rewrite/spring-boot-35-properties.yml b/headless-services/commons/commons-rewrite/src/main/resources/META-INF/rewrite/spring-boot-35-properties.yml deleted file mode 100644 index d95c8d77ba..0000000000 --- a/headless-services/commons/commons-rewrite/src/main/resources/META-INF/rewrite/spring-boot-35-properties.yml +++ /dev/null @@ -1,94 +0,0 @@ -# This file is automatically generated by the GeneratePropertiesMigratorConfiguration class. -# Do not edit this file manually. Update the Spring Boot property metadata upstream instead. ---- -type: specs.openrewrite.org/v1beta/recipe -name: org.openrewrite.java.spring.boot3.SpringBootProperties_3_5 -displayName: Migrate Spring Boot properties to 3.5 -description: Migrate properties found in `application.properties` and `application.yml`. -tags: - - spring - - boot -recipeList: - - org.openrewrite.java.spring.ChangeSpringPropertyKey: - oldPropertyKey: management.promethus.metrics.export.pushgateway.base-url - newPropertyKey: management.prometheus.metrics.export.pushgateway.address - - org.openrewrite.java.spring.ChangeSpringPropertyKey: - oldPropertyKey: spring.codec.log-request-details - newPropertyKey: spring.http.codec.log-request-details - - org.openrewrite.java.spring.ChangeSpringPropertyKey: - oldPropertyKey: spring.codec.max-in-memory-size - newPropertyKey: spring.http.codec.max-in-memory-size - - org.openrewrite.java.spring.ChangeSpringPropertyKey: - oldPropertyKey: spring.graphql.path - newPropertyKey: spring.graphql.http.path - - org.openrewrite.java.spring.ChangeSpringPropertyKey: - oldPropertyKey: spring.graphql.sse.timeout - newPropertyKey: spring.graphql.http.sse.timeout - - org.openrewrite.java.spring.ChangeSpringPropertyKey: - oldPropertyKey: spring.groovy.template.configuration.auto-escape - newPropertyKey: spring.groovy.template.auto-escape - - org.openrewrite.java.spring.ChangeSpringPropertyKey: - oldPropertyKey: spring.groovy.template.configuration.auto-indent - newPropertyKey: spring.groovy.template.auto-indent - - org.openrewrite.java.spring.ChangeSpringPropertyKey: - oldPropertyKey: spring.groovy.template.configuration.auto-indent-string - newPropertyKey: spring.groovy.template.auto-indent-string - - org.openrewrite.java.spring.ChangeSpringPropertyKey: - oldPropertyKey: spring.groovy.template.configuration.auto-new-line - newPropertyKey: spring.groovy.template.auto-new-line - - org.openrewrite.java.spring.ChangeSpringPropertyKey: - oldPropertyKey: spring.groovy.template.configuration.base-template-class - newPropertyKey: spring.groovy.template.base-template-class - - org.openrewrite.java.spring.ChangeSpringPropertyKey: - oldPropertyKey: spring.groovy.template.configuration.cache-templates - newPropertyKey: spring.groovy.template.cache - - org.openrewrite.java.spring.ChangeSpringPropertyKey: - oldPropertyKey: spring.groovy.template.configuration.declaration-encoding - newPropertyKey: spring.groovy.template.declaration-encoding - - org.openrewrite.java.spring.ChangeSpringPropertyKey: - oldPropertyKey: spring.groovy.template.configuration.expand-empty-elements - newPropertyKey: spring.groovy.template.expand-empty-elements - - org.openrewrite.java.spring.ChangeSpringPropertyKey: - oldPropertyKey: spring.groovy.template.configuration.locale - newPropertyKey: spring.groovy.template.locale - - org.openrewrite.java.spring.ChangeSpringPropertyKey: - oldPropertyKey: spring.groovy.template.configuration.new-line-string - newPropertyKey: spring.groovy.template.new-line-string - - org.openrewrite.java.spring.ChangeSpringPropertyKey: - oldPropertyKey: spring.groovy.template.configuration.resource-loader-path - newPropertyKey: spring.groovy.template.resource-loader-path - - org.openrewrite.java.spring.ChangeSpringPropertyKey: - oldPropertyKey: spring.groovy.template.configuration.use-double-quotes - newPropertyKey: spring.groovy.template.use-double-quotes - - org.openrewrite.java.spring.ChangeSpringPropertyKey: - oldPropertyKey: spring.mvc.converters.preferred-json-mapper - newPropertyKey: spring.http.converters.preferred-json-mapper - - - org.openrewrite.java.spring.CommentOutSpringPropertyKey: - propertyKey: management.signalfx.metrics.export.access-token - comment: "This property is deprecated: Deprecated in Micrometer 1.15.0" - - org.openrewrite.java.spring.CommentOutSpringPropertyKey: - propertyKey: management.signalfx.metrics.export.batch-size - comment: "This property is deprecated: Deprecated in Micrometer 1.15.0" - - org.openrewrite.java.spring.CommentOutSpringPropertyKey: - propertyKey: management.signalfx.metrics.export.connect-timeout - comment: "This property is deprecated: Deprecated in Micrometer 1.15.0" - - org.openrewrite.java.spring.CommentOutSpringPropertyKey: - propertyKey: management.signalfx.metrics.export.enabled - comment: "This property is deprecated: Deprecated in Micrometer 1.15.0" - - org.openrewrite.java.spring.CommentOutSpringPropertyKey: - propertyKey: management.signalfx.metrics.export.published-histogram-type - comment: "This property is deprecated: Deprecated in Micrometer 1.15.0" - - org.openrewrite.java.spring.CommentOutSpringPropertyKey: - propertyKey: management.signalfx.metrics.export.read-timeout - comment: "This property is deprecated: Deprecated in Micrometer 1.15.0" - - org.openrewrite.java.spring.CommentOutSpringPropertyKey: - propertyKey: management.signalfx.metrics.export.source - comment: "This property is deprecated: Deprecated in Micrometer 1.15.0" - - org.openrewrite.java.spring.CommentOutSpringPropertyKey: - propertyKey: management.signalfx.metrics.export.step - comment: "This property is deprecated: Deprecated in Micrometer 1.15.0" - - org.openrewrite.java.spring.CommentOutSpringPropertyKey: - propertyKey: management.signalfx.metrics.export.uri - comment: "This property is deprecated: Deprecated in Micrometer 1.15.0" - diff --git a/headless-services/commons/commons-rewrite/src/main/resources/META-INF/rewrite/upgrade-boot3.yml b/headless-services/commons/commons-rewrite/src/main/resources/META-INF/rewrite/upgrade-boot3.yml deleted file mode 100644 index 517ff400ac..0000000000 --- a/headless-services/commons/commons-rewrite/src/main/resources/META-INF/rewrite/upgrade-boot3.yml +++ /dev/null @@ -1,33 +0,0 @@ -# -# Copyright 2022 the original author or authors. -#

-# 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 -#

-# https://www.apache.org/licenses/LICENSE-2.0 -#

-# 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. -# ---- -######################################################################################################################## -# SpringBoot 3_0 -#type: specs.openrewrite.org/v1beta/recipe -#name: org.springframework.sts.java.spring.boot3.UpgradeSpringBoot_3_0 -#displayName: Upgrade to Spring Boot 3.0 from 2.7 -#description: 'Upgrade to Spring Boot 3.0 from prior 2.x version.' -#recipeList: -# - org.openrewrite.maven.AddRepository: -# id: spring-snapshots -# url: https://repo.spring.io/snapshot -# snapshotsEnabled: true -# - org.openrewrite.maven.AddRepository: -# id: spring-milestones -# url: https://repo.spring.io/milestone -# snapshotsEnabled: false -# - org.openrewrite.java.spring.boot3.UpgradeSpringBoot_3_0 -#--- diff --git a/headless-services/commons/commons-rewrite/src/main/resources/init.gradle b/headless-services/commons/commons-rewrite/src/main/resources/init.gradle index 8e02bfb7a8..432ca385d4 100644 --- a/headless-services/commons/commons-rewrite/src/main/resources/init.gradle +++ b/headless-services/commons/commons-rewrite/src/main/resources/init.gradle @@ -28,8 +28,8 @@ initscript { } dependencies { - classpath 'org.openrewrite.gradle.tooling:plugin:2.8.0' - classpath 'org.openrewrite:rewrite-maven:8.42.5' + classpath 'org.openrewrite.gradle.tooling:plugin:8.65.0' + classpath 'org.openrewrite:rewrite-maven:8.65.0' } } diff --git a/headless-services/commons/pom.xml b/headless-services/commons/pom.xml index 41e8366a31..f16955eb32 100644 --- a/headless-services/commons/pom.xml +++ b/headless-services/commons/pom.xml @@ -130,7 +130,7 @@ org.openrewrite rewrite-bom - 8.64.0 + 8.65.0 pom import diff --git a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/rewrite/SpringBootUpgrade.java b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/rewrite/SpringBootUpgrade.java index 895f2c6482..25af8c89d7 100644 --- a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/rewrite/SpringBootUpgrade.java +++ b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/rewrite/SpringBootUpgrade.java @@ -100,6 +100,18 @@ static List createRecipeIdsChain(int major, int minor, int targetMajor, return ids; } + public boolean canUpgrade(Version version, Version targetVersion) { + if (version.compareTo(targetVersion) <= 0) { + return false; + } + if (version.getMajor() == targetVersion.getMajor() && version.getMinor() == targetVersion.getMinor()) { + return true; + } else { + //TODO: Major or minor version upgrades are NOT available for the time being via open rewrite + return false; + } + } + private Recipe createUpgradeRecipe(Map recipes, Version version, Version targetVersion) { Recipe recipe = new DeclarativeRecipe("upgrade-spring-boot", "Upgrade Spring Boot from " + version + " to " + targetVersion, "", Collections.emptySet(), null, null, false, Collections.emptyList()); diff --git a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/validation/generations/UpdateBootVersion.java b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/validation/generations/UpdateBootVersion.java index c3d1050d16..26af6762a7 100644 --- a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/validation/generations/UpdateBootVersion.java +++ b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/validation/generations/UpdateBootVersion.java @@ -69,7 +69,7 @@ private Optional validateMajorVersion(IJavaProject javaProject, Vers bootUpgradeOpt.flatMap(bu -> bu.getNearestAvailableMinorVersion(latest)).map(targetVersion -> { Version upgradeVersion = Version.parse(targetVersion); - if (javaProjectVersion.compareTo(upgradeVersion) >= 0) { + if (!bootUpgradeOpt.get().canUpgrade(javaProjectVersion, upgradeVersion) || javaProjectVersion.compareTo(upgradeVersion) >= 0) { return null; } CodeAction c = new CodeAction(); @@ -102,7 +102,7 @@ private Optional validateMinorVersion(IJavaProject javaProject, Vers bootUpgradeOpt.flatMap(bu -> bu.getNearestAvailableMinorVersion(latest)).map(targetVersion -> { Version upgradeVersion = Version.parse(targetVersion); - if (javaProjectVersion.compareTo(upgradeVersion) >= 0) { + if (!bootUpgradeOpt.get().canUpgrade(javaProjectVersion, upgradeVersion) || javaProjectVersion.compareTo(upgradeVersion) >= 0) { return null; } CodeAction c = new CodeAction(); From e451883374020b7c914ceac276e61f97556140eb Mon Sep 17 00:00:00 2001 From: BoykoAlex Date: Wed, 29 Oct 2025 17:28:45 -0700 Subject: [PATCH 3/6] Remove Eclipse UI for Rewrite Signed-off-by: BoykoAlex --- .../plugin.xml | 194 ------------ .../boot/ls/PlugRecipesPreferencePage.java | 41 --- .../boot/ls/RecipeFiltersPreferencePage.java | 35 --- .../boot/ls/RewritePreferencePage.java | 36 --- .../boot/ls/commands/RecipeDescriptor.java | 57 ---- .../boot/ls/commands/RecipeTreeModel.java | 175 ----------- .../commands/RewriteRefactoringsHandler.java | 150 --------- .../boot/ls/commands/SelectRecipesDialog.java | 286 ------------------ .../boot/ls/prefs/PrefsInitializer.java | 14 - .../commons/commons-rewrite/pom.xml | 26 -- .../vscode/commons/rewrite/LoadUtilsTest.java | 204 ++++++------- headless-services/commons/pom.xml | 1 - .../boot/java/rewrite/SpringBootUpgrade.java | 34 ++- 13 files changed, 121 insertions(+), 1132 deletions(-) delete mode 100644 eclipse-language-servers/org.springframework.tooling.boot.ls/src/org/springframework/tooling/boot/ls/PlugRecipesPreferencePage.java delete mode 100644 eclipse-language-servers/org.springframework.tooling.boot.ls/src/org/springframework/tooling/boot/ls/RecipeFiltersPreferencePage.java delete mode 100644 eclipse-language-servers/org.springframework.tooling.boot.ls/src/org/springframework/tooling/boot/ls/RewritePreferencePage.java delete mode 100644 eclipse-language-servers/org.springframework.tooling.boot.ls/src/org/springframework/tooling/boot/ls/commands/RecipeDescriptor.java delete mode 100644 eclipse-language-servers/org.springframework.tooling.boot.ls/src/org/springframework/tooling/boot/ls/commands/RecipeTreeModel.java delete mode 100644 eclipse-language-servers/org.springframework.tooling.boot.ls/src/org/springframework/tooling/boot/ls/commands/RewriteRefactoringsHandler.java delete mode 100644 eclipse-language-servers/org.springframework.tooling.boot.ls/src/org/springframework/tooling/boot/ls/commands/SelectRecipesDialog.java diff --git a/eclipse-language-servers/org.springframework.tooling.boot.ls/plugin.xml b/eclipse-language-servers/org.springframework.tooling.boot.ls/plugin.xml index b7c9aa0ff0..31a0f6d88d 100644 --- a/eclipse-language-servers/org.springframework.tooling.boot.ls/plugin.xml +++ b/eclipse-language-servers/org.springframework.tooling.boot.ls/plugin.xml @@ -232,26 +232,6 @@ id="org.springsource.ide.eclipse.commons.preferencePage" name="Spring"> - - - - - - - - -

"); - sb.append(r.description); - sb.append("

"); - sb.append("
    "); - for (OptionDescriptor option : r.options) { - if (option.value() != null) { - sb.append("
  • "); - sb.append("
    ");
    -				sb.append(option.value());
    -				sb.append("
    "); - sb.append(option.description()); - sb.append("
  • "); - } - } - sb.append("
"); - return sb.toString(); - } - - private static String wrapHtml(String html) { - /* - * No JDT content. Means no JDT CSS part either. Therefore add JDT CSS chunk to it. - */ - ColorRegistry registry = JFaceResources.getColorRegistry(); - RGB fgRGB = registry.getRGB("org.eclipse.jdt.ui.Javadoc.foregroundColor"); //$NON-NLS-1$ - RGB bgRGB= registry.getRGB("org.eclipse.jdt.ui.Javadoc.backgroundColor"); //$NON-NLS-1$ - - StringBuilder buffer = new StringBuilder(html); - HTMLPrinter.insertPageProlog(buffer, 0, fgRGB, bgRGB, getStyleSheet()); - HTMLPrinter.addPageEpilog(buffer); - return buffer.toString(); - } - - protected IDialogSettings getDialogBoundsSettings() { - String sectionName= getClass().getName() + "_dialogBounds"; //$NON-NLS-1$ - IDialogSettings settings = PlatformUI - .getDialogSettingsProvider(FrameworkUtil.getBundle(getClass())).getDialogSettings(); - IDialogSettings section= settings.getSection(sectionName); - if (section == null) - section= settings.addNewSection(sectionName); - return section; - } - - /** - * Taken from {@link JavadocHover}. It's private. See {@link JavadocHover#getStyleSheet()}. - * @return CSS as string - */ - private static String getStyleSheet() { - if (fgStyleSheet == null) { - fgStyleSheet= JavadocHover.loadStyleSheet("/JavadocHoverStyleSheet.css"); //$NON-NLS-1$ - } - String css= fgStyleSheet; - if (css != null) { - FontData fontData= JFaceResources.getFontRegistry().getFontData(PreferenceConstants.APPEARANCE_JAVADOC_FONT)[0]; - css= HTMLPrinter.convertTopLevelFont(css, fontData); - } - return css; - } - -} diff --git a/eclipse-language-servers/org.springframework.tooling.boot.ls/src/org/springframework/tooling/boot/ls/prefs/PrefsInitializer.java b/eclipse-language-servers/org.springframework.tooling.boot.ls/src/org/springframework/tooling/boot/ls/prefs/PrefsInitializer.java index 5c84895485..07972ef302 100644 --- a/eclipse-language-servers/org.springframework.tooling.boot.ls/src/org/springframework/tooling/boot/ls/prefs/PrefsInitializer.java +++ b/eclipse-language-servers/org.springframework.tooling.boot.ls/src/org/springframework/tooling/boot/ls/prefs/PrefsInitializer.java @@ -43,20 +43,6 @@ public void initializeDefaultPreferences() { preferenceStore.setDefault(Constants.PREF_SCAN_JAVA_TEST_SOURCES, false); preferenceStore.setDefault(Constants.PREF_JAVA_RECONCILE, true); - preferenceStore.setDefault(Constants.PREF_REWRITE_PROJECT_REFACTORINGS, false); - - preferenceStore.setDefault(Constants.PREF_REWRITE_RECIPE_FILTERS, StringListEditor.encode(new String[] { - "org.openrewrite.java.spring.boot2.SpringBoot2JUnit4to5Migration", - "org.openrewrite.java.spring.boot3.SpringBoot3BestPractices", - "org.openrewrite.java.testing.junit5.JUnit5BestPractices", - "org.openrewrite.java.testing.junit5.JUnit4to5Migration", - "org.openrewrite.java.spring.boot2.UpgradeSpringBoot_2_7", - "org.springframework.ide.vscode.rewrite.boot3.UpgradeSpringBoot_3_4", - "org.springframework.ide.vscode.rewrite.boot3.UpgradeSpringBoot_3_5", - "org.rewrite.java.security.*", - "org.springframework.rewrite.test.*", - "rewrite.test.*" - })); preferenceStore.setDefault(Constants.PREF_MODULITH, true); preferenceStore.setDefault(Constants.PREF_LIVE_INFORMATION_ALL_JVM_PROCESSES, false); diff --git a/headless-services/commons/commons-rewrite/pom.xml b/headless-services/commons/commons-rewrite/pom.xml index 1f362f9d6a..4493e37ae9 100644 --- a/headless-services/commons/commons-rewrite/pom.xml +++ b/headless-services/commons/commons-rewrite/pom.xml @@ -28,16 +28,6 @@ ${project.version} - - io.github.classgraph - classgraph - 4.8.149 - - - jakarta.annotation - jakarta.annotation-api - - org.openrewrite rewrite-properties @@ -95,16 +85,6 @@ org.eclipse.jgit org.eclipse.jgit - - javax.xml.bind - jaxb-api - 2.3.1 - - - org.opentest4j - opentest4j - 1.3.0 - org.springframework.ide.vscode @@ -134,12 +114,6 @@ spring-boot-starter-web test - - org.openrewrite.recipe - rewrite-java-dependencies - 1.43.0 - test - org.springframework.boot spring-boot-starter-security diff --git a/headless-services/commons/commons-rewrite/src/test/java/org/springframework/ide/vscode/commons/rewrite/LoadUtilsTest.java b/headless-services/commons/commons-rewrite/src/test/java/org/springframework/ide/vscode/commons/rewrite/LoadUtilsTest.java index e14bfb68ad..2ba47fd8bb 100644 --- a/headless-services/commons/commons-rewrite/src/test/java/org/springframework/ide/vscode/commons/rewrite/LoadUtilsTest.java +++ b/headless-services/commons/commons-rewrite/src/test/java/org/springframework/ide/vscode/commons/rewrite/LoadUtilsTest.java @@ -10,115 +10,115 @@ *******************************************************************************/ package org.springframework.ide.vscode.commons.rewrite; -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertNotNull; -import static org.junit.jupiter.api.Assertions.assertTrue; - -import java.time.Duration; - -import org.junit.jupiter.api.BeforeAll; +//import static org.junit.jupiter.api.Assertions.assertEquals; +//import static org.junit.jupiter.api.Assertions.assertNotNull; +//import static org.junit.jupiter.api.Assertions.assertTrue; +// +//import java.time.Duration; +// +//import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Disabled; -import org.junit.jupiter.api.Test; -import org.openrewrite.Recipe; -import org.openrewrite.config.DeclarativeRecipe; -import org.openrewrite.config.Environment; -import org.openrewrite.config.RecipeDescriptor; -import org.openrewrite.java.dependencies.UpgradeDependencyVersion; -import org.springframework.ide.vscode.commons.rewrite.LoadUtils.DurationTypeConverter; - -import com.google.gson.Gson; -import com.google.gson.GsonBuilder; +//import org.junit.jupiter.api.Test; +//import org.openrewrite.Recipe; +//import org.openrewrite.config.DeclarativeRecipe; +//import org.openrewrite.config.Environment; +//import org.openrewrite.config.RecipeDescriptor; +//import org.openrewrite.java.dependencies.UpgradeDependencyVersion; +//import org.springframework.ide.vscode.commons.rewrite.LoadUtils.DurationTypeConverter; +// +//import com.google.gson.Gson; +//import com.google.gson.GsonBuilder; @Disabled public class LoadUtilsTest { - private static Environment env; - - private static Gson serializationGson = new GsonBuilder() - .registerTypeAdapter(Duration.class, new DurationTypeConverter()) - .create(); - - @BeforeAll - public static void setupAll() { - env = Environment.builder().scanRuntimeClasspath().build(); - } - - @SuppressWarnings("unchecked") - @Test - public void createRecipeTest() throws Exception { - RecipeDescriptor recipeDescriptor = env.listRecipeDescriptors().stream().filter(d -> "org.openrewrite.java.spring.boot3.UpgradeSpringBoot_3_0".equals(d.getName())).findFirst().orElse(null); - assertNotNull(recipeDescriptor); - - Recipe r = LoadUtils.createRecipe(recipeDescriptor, id -> { - try { - return (Class) Class.forName(id); - } catch (ClassNotFoundException e) { - return null; - } - }); - - assertTrue(r instanceof DeclarativeRecipe); - assertEquals("org.openrewrite.java.spring.boot3.UpgradeSpringBoot_3_0", r.getName()); - assertTrue(r.getDescription().startsWith("Migrate applications to the latest Spring Boot 3.0 release.")); - assertEquals("Migrate to Spring Boot 3.0", r.getDisplayName()); - assertTrue(r.getRecipeList().size() >= 12); - -// Recipe pomRecipe = r.getRecipeList().get(2); +// private static Environment env; +// +// private static Gson serializationGson = new GsonBuilder() +// .registerTypeAdapter(Duration.class, new DurationTypeConverter()) +// .create(); +// +// @BeforeAll +// public static void setupAll() { +// env = Environment.builder().scanRuntimeClasspath().build(); +// } +// +// @SuppressWarnings("unchecked") +// @Test +// public void createRecipeTest() throws Exception { +// RecipeDescriptor recipeDescriptor = env.listRecipeDescriptors().stream().filter(d -> "org.openrewrite.java.spring.boot3.UpgradeSpringBoot_3_0".equals(d.getName())).findFirst().orElse(null); +// assertNotNull(recipeDescriptor); +// +// Recipe r = LoadUtils.createRecipe(recipeDescriptor, id -> { +// try { +// return (Class) Class.forName(id); +// } catch (ClassNotFoundException e) { +// return null; +// } +// }); +// +// assertTrue(r instanceof DeclarativeRecipe); +// assertEquals("org.openrewrite.java.spring.boot3.UpgradeSpringBoot_3_0", r.getName()); +// assertTrue(r.getDescription().startsWith("Migrate applications to the latest Spring Boot 3.0 release.")); +// assertEquals("Migrate to Spring Boot 3.0", r.getDisplayName()); +// assertTrue(r.getRecipeList().size() >= 12); +// +//// Recipe pomRecipe = r.getRecipeList().get(2); +//// assertTrue(pomRecipe instanceof DeclarativeRecipe); +//// assertEquals("org.openrewrite.java.spring.boot3.MavenPomUpgrade", pomRecipe.getName()); +//// assertEquals("Upgrade Maven POM to Spring Boot 3.0 from prior 2.x version.", pomRecipe.getDescription()); +//// assertEquals("Upgrade Maven POM to Spring Boot 3.0 from 2.x", pomRecipe.getDisplayName()); +//// assertTrue(pomRecipe.getRecipeList().size() >= 3); +// +// UpgradeDependencyVersion upgradeDependencyRecipe = r.getRecipeList().stream().filter(UpgradeDependencyVersion.class::isInstance).map(UpgradeDependencyVersion.class::cast).findFirst().get(); +// assertEquals("org.openrewrite.java.dependencies.UpgradeDependencyVersion", upgradeDependencyRecipe.getName()); +// assertEquals("Upgrade Gradle or Maven dependency versions", upgradeDependencyRecipe.getDisplayName()); +// assertTrue(upgradeDependencyRecipe.getNewVersion().startsWith("3.0.")); +// assertEquals("org.springframework.boot", upgradeDependencyRecipe.getGroupId()); +// assertEquals("*", upgradeDependencyRecipe.getArtifactId()); +// } +// +// @SuppressWarnings("unchecked") +// public void deserializeFromJson() throws Exception { +// Recipe r = env.listRecipes().stream().filter(d -> "org.openrewrite.java.spring.boot3.UpgradeSpringBoot_3_0".equals(d.getName())).findFirst().orElse(null); +// RecipeDescriptor recipeDescriptor = r.getDescriptor(); +// assertNotNull(recipeDescriptor); +// +// String json = serializationGson.toJson(recipeDescriptor); +// RecipeDescriptor deserialized = serializationGson.fromJson(json, RecipeDescriptor.class); +// assertNotNull(deserialized); +// +// r = LoadUtils.createRecipe(deserialized, id -> { +// try { +// return (Class) Class.forName(id); +// } catch (ClassNotFoundException e) { +// return null; +// } +// }); +// +// assertTrue(r instanceof DeclarativeRecipe); +// assertEquals("org.openrewrite.java.spring.boot3.UpgradeSpringBoot_3_0", r.getName()); +// assertEquals( +// "Migrate applications to the latest Spring Boot 3.0 release. This recipe will modify an application's build files, make changes to deprecated/preferred APIs, and migrate configuration settings that have changes between versions. This recipe will also chain additional framework migrations (Spring Framework, Spring Data, etc) that are required as part of the migration to Spring Boot 2.7.\n" +// + "" +// + "", +// r.getDescription()); +// assertEquals("Migrate to Spring Boot 3.0", r.getDisplayName()); +// assertEquals(9, r.getRecipeList().size()); +// +// Recipe pomRecipe = r.getRecipeList().get(0); // assertTrue(pomRecipe instanceof DeclarativeRecipe); -// assertEquals("org.openrewrite.java.spring.boot3.MavenPomUpgrade", pomRecipe.getName()); +// assertEquals("org.openrewrite.java.dependencies.UpgradeDependencyVersion", pomRecipe.getName()); // assertEquals("Upgrade Maven POM to Spring Boot 3.0 from prior 2.x version.", pomRecipe.getDescription()); // assertEquals("Upgrade Maven POM to Spring Boot 3.0 from 2.x", pomRecipe.getDisplayName()); // assertTrue(pomRecipe.getRecipeList().size() >= 3); - - UpgradeDependencyVersion upgradeDependencyRecipe = r.getRecipeList().stream().filter(UpgradeDependencyVersion.class::isInstance).map(UpgradeDependencyVersion.class::cast).findFirst().get(); - assertEquals("org.openrewrite.java.dependencies.UpgradeDependencyVersion", upgradeDependencyRecipe.getName()); - assertEquals("Upgrade Gradle or Maven dependency versions", upgradeDependencyRecipe.getDisplayName()); - assertTrue(upgradeDependencyRecipe.getNewVersion().startsWith("3.0.")); - assertEquals("org.springframework.boot", upgradeDependencyRecipe.getGroupId()); - assertEquals("*", upgradeDependencyRecipe.getArtifactId()); - } - - @SuppressWarnings("unchecked") - public void deserializeFromJson() throws Exception { - Recipe r = env.listRecipes().stream().filter(d -> "org.openrewrite.java.spring.boot3.UpgradeSpringBoot_3_0".equals(d.getName())).findFirst().orElse(null); - RecipeDescriptor recipeDescriptor = r.getDescriptor(); - assertNotNull(recipeDescriptor); - - String json = serializationGson.toJson(recipeDescriptor); - RecipeDescriptor deserialized = serializationGson.fromJson(json, RecipeDescriptor.class); - assertNotNull(deserialized); - - r = LoadUtils.createRecipe(deserialized, id -> { - try { - return (Class) Class.forName(id); - } catch (ClassNotFoundException e) { - return null; - } - }); - - assertTrue(r instanceof DeclarativeRecipe); - assertEquals("org.openrewrite.java.spring.boot3.UpgradeSpringBoot_3_0", r.getName()); - assertEquals( - "Migrate applications to the latest Spring Boot 3.0 release. This recipe will modify an application's build files, make changes to deprecated/preferred APIs, and migrate configuration settings that have changes between versions. This recipe will also chain additional framework migrations (Spring Framework, Spring Data, etc) that are required as part of the migration to Spring Boot 2.7.\n" - + "" - + "", - r.getDescription()); - assertEquals("Migrate to Spring Boot 3.0", r.getDisplayName()); - assertEquals(9, r.getRecipeList().size()); - - Recipe pomRecipe = r.getRecipeList().get(0); - assertTrue(pomRecipe instanceof DeclarativeRecipe); - assertEquals("org.openrewrite.java.dependencies.UpgradeDependencyVersion", pomRecipe.getName()); - assertEquals("Upgrade Maven POM to Spring Boot 3.0 from prior 2.x version.", pomRecipe.getDescription()); - assertEquals("Upgrade Maven POM to Spring Boot 3.0 from 2.x", pomRecipe.getDisplayName()); - assertTrue(pomRecipe.getRecipeList().size() >= 3); - - UpgradeDependencyVersion upgradeDependencyRecipe = pomRecipe.getRecipeList().stream().filter(UpgradeDependencyVersion.class::isInstance).map(UpgradeDependencyVersion.class::cast).findFirst().get(); - assertEquals("org.openrewrite.maven.UpgradeDependencyVersion", upgradeDependencyRecipe.getName()); - assertEquals("Upgrade Maven dependency version", upgradeDependencyRecipe.getDisplayName()); - assertEquals(0, upgradeDependencyRecipe.getRecipeList().size()); - assertTrue(upgradeDependencyRecipe.getNewVersion().startsWith("3.0.")); - assertEquals("org.springframework.boot", upgradeDependencyRecipe.getGroupId()); - assertEquals("*", upgradeDependencyRecipe.getArtifactId()); - } +// +// UpgradeDependencyVersion upgradeDependencyRecipe = pomRecipe.getRecipeList().stream().filter(UpgradeDependencyVersion.class::isInstance).map(UpgradeDependencyVersion.class::cast).findFirst().get(); +// assertEquals("org.openrewrite.maven.UpgradeDependencyVersion", upgradeDependencyRecipe.getName()); +// assertEquals("Upgrade Maven dependency version", upgradeDependencyRecipe.getDisplayName()); +// assertEquals(0, upgradeDependencyRecipe.getRecipeList().size()); +// assertTrue(upgradeDependencyRecipe.getNewVersion().startsWith("3.0.")); +// assertEquals("org.springframework.boot", upgradeDependencyRecipe.getGroupId()); +// assertEquals("*", upgradeDependencyRecipe.getArtifactId()); +// } } diff --git a/headless-services/commons/pom.xml b/headless-services/commons/pom.xml index f16955eb32..e6e1ffa428 100644 --- a/headless-services/commons/pom.xml +++ b/headless-services/commons/pom.xml @@ -126,7 +126,6 @@ - org.openrewrite rewrite-bom diff --git a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/rewrite/SpringBootUpgrade.java b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/rewrite/SpringBootUpgrade.java index 25af8c89d7..312e572640 100644 --- a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/rewrite/SpringBootUpgrade.java +++ b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/rewrite/SpringBootUpgrade.java @@ -22,7 +22,7 @@ import org.eclipse.lsp4j.TextDocumentIdentifier; import org.openrewrite.Recipe; import org.openrewrite.config.DeclarativeRecipe; -import org.openrewrite.maven.UpgradeDependencyVersion; +import org.openrewrite.gradle.plugins.UpgradePluginVersion; import org.openrewrite.maven.UpgradeParentVersion; import org.springframework.ide.vscode.commons.Version; import org.springframework.ide.vscode.commons.java.IJavaProject; @@ -76,12 +76,14 @@ public SpringBootUpgrade(SimpleLanguageServer server, RewriteRecipeRepository re + version.toMajorMinorVersionStr() + "' is newer or same as the target version '" + targetVersion.toMajorMinorVersionStr() + "'"); - return recipeRepo.recipes().thenComposeAsync(recipes -> recipeRepo.apply( - createUpgradeRecipe(recipes, version, targetVersion), - uri, - UUID.randomUUID().toString(), - askForPreview - )); +// return recipeRepo.recipes().thenComposeAsync(recipes -> recipeRepo.apply( +// createUpgradeRecipe(recipes, version, targetVersion), +// uri, +// UUID.randomUUID().toString(), +// askForPreview +// )); + + return recipeRepo.apply(createUpgradeRecipe(version, targetVersion), uri, UUID.randomUUID().toString(), askForPreview); }); } @@ -112,20 +114,22 @@ public boolean canUpgrade(Version version, Version targetVersion) { } } - private Recipe createUpgradeRecipe(Map recipes, Version version, Version targetVersion) { + private Recipe createUpgradeRecipe(/*Map recipes, */Version version, Version targetVersion) { Recipe recipe = new DeclarativeRecipe("upgrade-spring-boot", "Upgrade Spring Boot from " + version + " to " + targetVersion, "", Collections.emptySet(), null, null, false, Collections.emptyList()); if (version.getMajor() == targetVersion.getMajor() && version.getMinor() == targetVersion.getMinor()) { // patch version upgrade - treat as pom versions only upgrade - recipe.getRecipeList().add(new UpgradeDependencyVersion("org.springframework.boot", "*", version.getMajor() + "." + version.getMinor() + ".x", null, null, null)); + recipe.getRecipeList().add(new org.openrewrite.maven.UpgradeDependencyVersion("org.springframework.boot", "*", version.getMajor() + "." + version.getMinor() + ".x", null, null, null)); recipe.getRecipeList().add(new UpgradeParentVersion("org.springframework.boot", "spring-boot-starter-parent", version.getMajor() + "." + version.getMinor() + ".x", null, null)); - } else /*if (version.getMajor() == targetVersion.getMajor())*/ { - List recipedIds = createRecipeIdsChain(version.getMajor(), version.getMinor() + 1, targetVersion.getMajor(), targetVersion.getMinor(), versionsToRecipeId); - if (!recipedIds.isEmpty()) { - String recipeId = recipedIds.get(recipedIds.size() - 1); - Optional.ofNullable(recipes.get(recipeId)).ifPresent(r -> recipe.getRecipeList().add(r)); - } + recipe.getRecipeList().add(new org.openrewrite.gradle.UpgradeDependencyVersion("org.springframework.boot", "*", version.getMajor() + "." + version.getMinor() + ".x", null)); + recipe.getRecipeList().add(new UpgradePluginVersion("org.springframework.boot", version.getMajor() + "." + version.getMinor() + ".x", null)); +// } else /*if (version.getMajor() == targetVersion.getMajor())*/ { +// List recipedIds = createRecipeIdsChain(version.getMajor(), version.getMinor() + 1, targetVersion.getMajor(), targetVersion.getMinor(), versionsToRecipeId); +// if (!recipedIds.isEmpty()) { +// String recipeId = recipedIds.get(recipedIds.size() - 1); +// Optional.ofNullable(recipes.get(recipeId)).ifPresent(r -> recipe.getRecipeList().add(r)); +// } } if (recipe.getRecipeList().isEmpty()) { From 4542a4262934fc6af20082d9ad35ce43a2a870dd Mon Sep 17 00:00:00 2001 From: BoykoAlex Date: Thu, 30 Oct 2025 14:53:35 -0700 Subject: [PATCH 4/6] Remove from VSCode Signed-off-by: BoykoAlex --- .../vscode-spring-boot/lib/Main.ts | 2 - .../vscode-spring-boot/lib/rewrite.ts | 246 ------------------ .../vscode-spring-boot/package.json | 76 ------ 3 files changed, 324 deletions(-) delete mode 100644 vscode-extensions/vscode-spring-boot/lib/rewrite.ts diff --git a/vscode-extensions/vscode-spring-boot/lib/Main.ts b/vscode-extensions/vscode-spring-boot/lib/Main.ts index 0725799f12..e3c5f04c29 100644 --- a/vscode-extensions/vscode-spring-boot/lib/Main.ts +++ b/vscode-extensions/vscode-spring-boot/lib/Main.ts @@ -14,7 +14,6 @@ import { import * as commons from '@pivotal-tools/commons-vscode'; import * as liveHoverUi from './live-hover-connect-ui'; -import * as rewrite from './rewrite'; import { startDebugSupport } from './debug-config-provider'; import { ApiManager } from "./apiManager"; @@ -182,7 +181,6 @@ export function activate(context: ExtensionContext): Thenable { }))); context.subscriptions.push(commands.registerCommand('vscode-spring-boot.ls.stop', () => client.stop())); liveHoverUi.activate(client, options, context); - rewrite.activate(client, options, context); setLogLevelUi.activate(client, options, context); startPropertiesConversionSupport(context); if(isLlmApiReady) diff --git a/vscode-extensions/vscode-spring-boot/lib/rewrite.ts b/vscode-extensions/vscode-spring-boot/lib/rewrite.ts deleted file mode 100644 index 5c77628f3e..0000000000 --- a/vscode-extensions/vscode-spring-boot/lib/rewrite.ts +++ /dev/null @@ -1,246 +0,0 @@ -import { ActivatorOptions } from "@pivotal-tools/commons-vscode"; -import { LanguageClient } from "vscode-languageclient/node"; -import * as VSCode from 'vscode'; -import * as path from "path"; - -const BOOT_UPGRADE = 'BOOT_UPGRADE'; -const OTHER_REFACTORINGS = 'NON_BOOT_UPGRADE'; - -interface RecipeDescriptor { - name: string; - displayName: string; - description: string; - tags: string[]; - options: OptionDescriptor[]; - hasSubRecipes: boolean; -} - -interface OptionDescriptor { - name: string; - type: string; - displayName: string; - description: string; - example: string; - valid: string[] | undefined; - required: boolean; - value: any; -} - -interface RecipeSelectionDescriptor { - selected: boolean; - id: string; - subselection: RecipeSelectionDescriptor[]; -} - -interface RecipeQuickPickItem extends VSCode.QuickPickItem{ - readonly id: string; - selected: boolean; - children: RecipeQuickPickItem[] | undefined, - readonly recipeDescriptor: RecipeDescriptor; -} - -function getWorkspaceFolderName(file: VSCode.Uri): string { - if (file) { - const wf: VSCode.WorkspaceFolder = VSCode.workspace.getWorkspaceFolder(file); - if (wf) { - return wf.name; - } - } - return ''; -} - -function getRelativePathToWorkspaceFolder(file: VSCode.Uri): string { - if (file) { - const wf: VSCode.WorkspaceFolder = VSCode.workspace.getWorkspaceFolder(file); - if (wf) { - return path.relative(wf.uri.fsPath, file.fsPath); - } - } - return ''; -} - -async function getTargetPomXml(): Promise { - if (VSCode.window.activeTextEditor) { - const activeUri = VSCode.window.activeTextEditor.document.uri; - if ("pom.xml" === path.basename(activeUri.path).toLowerCase()) { - return activeUri; - } - } - - const candidates: VSCode.Uri[] = await VSCode.workspace.findFiles("**/pom.xml"); - if (candidates.length > 0) { - if (candidates.length === 1) { - return candidates[0]; - } else { - return await VSCode.window.showQuickPick( - candidates.map((c: VSCode.Uri) => ({ value: c, label: getRelativePathToWorkspaceFolder(c), description: getWorkspaceFolderName(c) })), - { placeHolder: "Select the target project." }, - ).then(res => res && res.value); - } - } - return undefined; -} - -const ROOT_RECIPES_BUTTON: VSCode.QuickInputButton = { - iconPath: new VSCode.ThemeIcon('home'), - tooltip: 'Root Recipes' -} -const PARENT_RECIPE_BUTTON: VSCode.QuickInputButton = { - iconPath: new VSCode.ThemeIcon('arrow-up'), - tooltip: 'Parent Recipe' -} -const SUB_RECIPES_BUTTON: VSCode.QuickInputButton = { - iconPath: new VSCode.ThemeIcon('type-hierarchy'), - tooltip: 'Sub-Recipes' -} - -async function showRefactorings(uri: VSCode.Uri, filter: string) { - if (!uri) { - uri = await getTargetPomXml(); - } - const choices = await showCurrentPathQuickPick(VSCode.commands.executeCommand('sts/rewrite/list', filter).then((cmds: RecipeDescriptor[]) => cmds.map(d => convertToQuickPickItem(d, false))), []); - const recipeDescriptors = choices.filter(i => i.selected).map(convertToRecipeSelectionDescriptor); - if (recipeDescriptors.length) { - VSCode.commands.executeCommand('sts/rewrite/execute', uri.toString(true), recipeDescriptors, true); - } else { - VSCode.window.showErrorMessage('No Recipes were selected!'); - } -} - -function convertToRecipeSelectionDescriptor(i: RecipeQuickPickItem): RecipeSelectionDescriptor { - return { - selected: i.selected, - id: i.id, - subselection: i.children ? i.children.map(convertToRecipeSelectionDescriptor) : undefined - }; -} - -function convertToQuickPickItem(i: RecipeDescriptor, selected?: boolean): RecipeQuickPickItem { - return { - id: i.name, - label: i.displayName, - detail: i.options.filter(o => !!o.value).map(o => `${o.name}: ${JSON.stringify(o.value)}`).join('\n\n'), - description: i.description, - selected: !!selected, - children: undefined, - buttons: i.hasSubRecipes ? [ SUB_RECIPES_BUTTON ] : undefined, - recipeDescriptor: i - }; -} - -function showCurrentPathQuickPick(itemsPromise: Thenable, itemsPath: RecipeQuickPickItem[]): Thenable { - const quickPick = VSCode.window.createQuickPick(); - quickPick.title = 'Loading Recipes...'; - quickPick.canSelectMany = true; - quickPick.busy = true; - quickPick.show(); - return itemsPromise.then(items => { - return new Promise((resolve, reject) => { - let currentItems = items; - let parent: RecipeQuickPickItem | undefined; - itemsPath.forEach(p => { - parent = currentItems.find(i => i === p); - currentItems = parent.children; - }); - quickPick.items = currentItems; - if (itemsPath.length) { - quickPick.buttons = [ PARENT_RECIPE_BUTTON, ROOT_RECIPES_BUTTON ]; - } - quickPick.selectedItems = currentItems.filter(i => i.selected); - quickPick.onDidTriggerItemButton(e => { - if (e.button === SUB_RECIPES_BUTTON) { - currentItems.forEach(i => i.selected = quickPick.selectedItems.includes(i)); - itemsPath.push(e.item); - showCurrentPathQuickPick(navigateToSubRecipes(e.item, itemsPath).then(() => items), itemsPath).then(resolve, reject); - } - }); - quickPick.onDidTriggerButton(b => { - if (b === ROOT_RECIPES_BUTTON) { - currentItems.forEach(i => i.selected = quickPick.selectedItems.includes(i)); - itemsPath.splice(0, itemsPath.length); - showCurrentPathQuickPick(Promise.resolve(items), itemsPath).then(resolve, reject); - } else if (b === PARENT_RECIPE_BUTTON) { - currentItems.forEach(i => i.selected = quickPick.selectedItems.includes(i)); - itemsPath.pop(); - showCurrentPathQuickPick(Promise.resolve(items), itemsPath).then(resolve, reject); - } - }); - quickPick.onDidAccept(() => { - currentItems.forEach(i => i.selected = quickPick.selectedItems.includes(i)); - quickPick.hide(); - resolve(items); - }); - quickPick.onDidChangeSelection(selected => { - currentItems.forEach(i => { - const isSelectedItem = selected.includes(i); - if (i.selected !== isSelectedItem) { - selectItemRecursively(i, isSelectedItem); - } - }); - updateParentSelection(itemsPath.slice()); - }); - quickPick.title = 'Select Recipes'; - quickPick.busy = false; - }); - }); -} - -async function navigateToSubRecipes(item: RecipeQuickPickItem, itemsPath: RecipeQuickPickItem[]) { - if (!item.children) { - const indexPath = []; - for (let i = 1; i < itemsPath.length; i++) { - indexPath.push(itemsPath[i - 1].children.indexOf(itemsPath[i])); - } - const recipeDescriptors: RecipeDescriptor[] = await VSCode.commands.executeCommand('sts/rewrite/sublist', itemsPath[0].id, indexPath); - item.children = recipeDescriptors.map(d => convertToQuickPickItem(d, item.selected)); - } -} - -function updateParentSelection(hierarchy: RecipeQuickPickItem[]): void { - if (hierarchy.length) { - const parent = hierarchy.pop(); - const isSelected = !!parent.children.find(i => i.selected); - if (parent.selected !== isSelected) { - parent.selected = isSelected; - updateParentSelection(hierarchy) - } - } -} - -function selectItemRecursively(i: RecipeQuickPickItem, isSelectedItem: boolean) { - i.selected = isSelectedItem; - if (i.children) { - i.children.forEach(c => selectItemRecursively(c, isSelectedItem)); - } -} - -/** Called when extension is activated */ -export function activate( - client: LanguageClient, - options: ActivatorOptions, - context: VSCode.ExtensionContext -) { - context.subscriptions.push( - VSCode.commands.registerCommand('vscode-spring-boot.rewrite.list.boot-upgrades', param => { - if (client.isRunning()) { - return showRefactorings(param, BOOT_UPGRADE); - } else { - VSCode.window.showErrorMessage("No Spring Boot project found. Action is only available for Spring Boot Projects"); - } - }), - VSCode.commands.registerCommand('vscode-spring-boot.rewrite.list.refactorings', param => { - if (client.isRunning()) { - return showRefactorings(param, OTHER_REFACTORINGS); - } else { - VSCode.window.showErrorMessage("No Spring Boot project found. Action is only available for Spring Boot Projects"); - } - }), - VSCode.commands.registerCommand('vscode-spring-boot.rewrite.reload', () => { - if (client.isRunning()) { - return VSCode.commands.executeCommand('sts/rewrite/reload'); - } else { - VSCode.window.showErrorMessage("No Spring Boot project found. Action is only available for Spring Boot Projects"); - } - }) - ); -} \ No newline at end of file diff --git a/vscode-extensions/vscode-spring-boot/package.json b/vscode-extensions/vscode-spring-boot/package.json index da37e76a1f..9d0a59ab48 100644 --- a/vscode-extensions/vscode-spring-boot/package.json +++ b/vscode-extensions/vscode-spring-boot/package.json @@ -118,16 +118,6 @@ } ], "editor/context": [ - { - "when": "resourceFilename == pom.xml || resourceFilename == build.gradle", - "command": "vscode-spring-boot.rewrite.list.refactorings", - "group": "SpringBoot" - }, - { - "when": "resourceFilename == pom.xml || resourceFilename == build.gradle", - "command": "vscode-spring-boot.rewrite.list.boot-upgrades", - "group": "SpringBoot" - }, { "when": "editorLangId == spring-boot-properties", "command": "vscode-spring-boot.props-to-yaml", @@ -140,16 +130,6 @@ } ], "explorer/context": [ - { - "when": "(resourceFilename == pom.xml || resourceFilename == build.gradle) && config.boot-java.rewrite.refactorings.on == true", - "command": "vscode-spring-boot.rewrite.list.refactorings", - "group": "SpringBoot" - }, - { - "when": "(resourceFilename == pom.xml || resourceFilename == build.gradle) && config.boot-java.rewrite.refactorings.on == true", - "command": "vscode-spring-boot.rewrite.list.boot-upgrades", - "group": "SpringBoot" - }, { "when": "resourceExtname == .properties", "command": "vscode-spring-boot.props-to-yaml", @@ -229,23 +209,6 @@ "title": "Show/Refresh/Hide Live Data from Spring Boot Processes", "category": "Spring Boot" }, - { - "enablement": "config.boot-java.rewrite.refactorings.on == true", - "command": "vscode-spring-boot.rewrite.list.boot-upgrades", - "category": "Spring Boot", - "title": "Upgrade Spring Boot Version..." - }, - { - "enablement": "config.boot-java.rewrite.refactorings.on == true", - "command": "vscode-spring-boot.rewrite.list.refactorings", - "category": "Spring Boot", - "title": "Refactor Spring Boot Project..." - }, - { - "command": "vscode-spring-boot.rewrite.reload", - "title": "Reload OpenRewrite Recipes", - "category": "Spring Boot" - }, { "command": "sts/common-properties/reload", "title": "Reload Shared Properties Metadata", @@ -496,45 +459,6 @@ } } }, - { - "id": "rewrite", - "title": "OpenRewrite", - "order": 400, - "properties": { - "boot-java.rewrite.refactorings.on": { - "type": "boolean", - "default": true, - "description": "Recipes refactoring entire Maven project via commands" - }, - "boot-java.rewrite.recipe-filters": { - "type": "array", - "default": [ - "org.openrewrite.java.spring.boot2.SpringBoot2JUnit4to5Migration", - "org.openrewrite.java.spring.boot3.SpringBoot3BestPractices", - "org.openrewrite.java.testing.junit5.JUnit5BestPractices", - "org.openrewrite.java.testing.junit5.JUnit4to5Migration", - "org.openrewrite.java.spring.boot2.UpgradeSpringBoot_2_7", - "org.springframework.ide.vscode.rewrite.boot3.UpgradeSpringBoot_3_4", - "org.springframework.ide.vscode.rewrite.boot3.UpgradeSpringBoot_3_5", - "org.rewrite.java.security.*", - "org.springframework.rewrite.test.*", - "rewrite.test.*" - ], - "items": { - "type": "string" - }, - "description": "Recipe ID filter patterns. Either exact ids or patterns with '*' as the wild-card" - }, - "boot-java.rewrite.scan-files": { - "type": "array", - "default": [], - "items": { - "type": "string" - }, - "description": "JAR and YAML files to scan for recipes" - } - } - }, { "id": "ls", "title": "Language Server", From ab9f84eed6b6fe095aba67e60fd3d418c8ce655d Mon Sep 17 00:00:00 2001 From: BoykoAlex Date: Thu, 30 Oct 2025 17:18:51 -0700 Subject: [PATCH 5/6] Remove remaining major/minor version upgrade code Signed-off-by: BoykoAlex --- ...AddSerialAnnotationToSerialVersionUID.java | 93 -- .../AddSerialVersionUidToSerializable.java | 149 -- .../ChainStringBuilderAppendCalls.java | 199 --- .../InstanceOfPatternMatch.java | 527 ------- .../staticanalysis/RemoveUnneededBlock.java | 100 -- .../ReplaceDeprecatedRuntimeExecMethods.java | 138 -- .../groovy/GroovyFileChecker.java | 35 - .../kotlin/KotlinFileChecker.java | 40 - .../ide/vscode/commons/rewrite/LoadUtils.java | 176 --- .../gradle/GradleIJavaProjectParser.java | 2 +- .../commons/rewrite/java/ProjectParser.java | 2 +- .../maven/MavenIJavaProjectParser.java | 2 +- ...erialAnnotationToSerialVersionUIDTest.java | 192 --- ...AddSerialVersionUidToSerializableTest.java | 275 ---- .../ChainStringBuilderAppendCallsTest.java | 313 ---- .../InstanceOfPatternMatchTest.java | 1296 ----------------- .../RemoveUnneededBlockTest.java | 568 -------- ...placeDeprecatedRuntimeExecMethodsTest.java | 297 ---- .../vscode/commons/rewrite/LoadUtilsTest.java | 124 -- .../java/rewrite/RewriteRecipeRepository.java | 322 +--- .../boot/java/rewrite/SpringBootUpgrade.java | 91 +- .../generations/UpdateBootVersion.java | 4 +- .../rewrite/RewriteRecipeRepositoryTest.java | 137 -- .../java/rewrite/SpringBootUpgradeTest.java | 98 -- 24 files changed, 39 insertions(+), 5141 deletions(-) delete mode 100644 headless-services/commons/commons-rewrite/src/main/java/org/openrewrite/staticanalysis/AddSerialAnnotationToSerialVersionUID.java delete mode 100644 headless-services/commons/commons-rewrite/src/main/java/org/openrewrite/staticanalysis/AddSerialVersionUidToSerializable.java delete mode 100644 headless-services/commons/commons-rewrite/src/main/java/org/openrewrite/staticanalysis/ChainStringBuilderAppendCalls.java delete mode 100644 headless-services/commons/commons-rewrite/src/main/java/org/openrewrite/staticanalysis/InstanceOfPatternMatch.java delete mode 100644 headless-services/commons/commons-rewrite/src/main/java/org/openrewrite/staticanalysis/RemoveUnneededBlock.java delete mode 100644 headless-services/commons/commons-rewrite/src/main/java/org/openrewrite/staticanalysis/ReplaceDeprecatedRuntimeExecMethods.java delete mode 100644 headless-services/commons/commons-rewrite/src/main/java/org/openrewrite/staticanalysis/groovy/GroovyFileChecker.java delete mode 100644 headless-services/commons/commons-rewrite/src/main/java/org/openrewrite/staticanalysis/kotlin/KotlinFileChecker.java delete mode 100644 headless-services/commons/commons-rewrite/src/main/java/org/springframework/ide/vscode/commons/rewrite/LoadUtils.java delete mode 100644 headless-services/commons/commons-rewrite/src/test/java/org/openrewrite/staticanalysis/AddSerialAnnotationToSerialVersionUIDTest.java delete mode 100644 headless-services/commons/commons-rewrite/src/test/java/org/openrewrite/staticanalysis/AddSerialVersionUidToSerializableTest.java delete mode 100644 headless-services/commons/commons-rewrite/src/test/java/org/openrewrite/staticanalysis/ChainStringBuilderAppendCallsTest.java delete mode 100644 headless-services/commons/commons-rewrite/src/test/java/org/openrewrite/staticanalysis/InstanceOfPatternMatchTest.java delete mode 100644 headless-services/commons/commons-rewrite/src/test/java/org/openrewrite/staticanalysis/RemoveUnneededBlockTest.java delete mode 100644 headless-services/commons/commons-rewrite/src/test/java/org/openrewrite/staticanalysis/ReplaceDeprecatedRuntimeExecMethodsTest.java delete mode 100644 headless-services/commons/commons-rewrite/src/test/java/org/springframework/ide/vscode/commons/rewrite/LoadUtilsTest.java delete mode 100644 headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/java/rewrite/RewriteRecipeRepositoryTest.java delete mode 100644 headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/java/rewrite/SpringBootUpgradeTest.java diff --git a/headless-services/commons/commons-rewrite/src/main/java/org/openrewrite/staticanalysis/AddSerialAnnotationToSerialVersionUID.java b/headless-services/commons/commons-rewrite/src/main/java/org/openrewrite/staticanalysis/AddSerialAnnotationToSerialVersionUID.java deleted file mode 100644 index 746399b097..0000000000 --- a/headless-services/commons/commons-rewrite/src/main/java/org/openrewrite/staticanalysis/AddSerialAnnotationToSerialVersionUID.java +++ /dev/null @@ -1,93 +0,0 @@ -/* - * Copyright 2024 the original author or authors. - *

- * 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 - *

- * https://www.apache.org/licenses/LICENSE-2.0 - *

- * 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.staticanalysis; - -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.JavaTemplate; -import org.openrewrite.java.search.FindAnnotations; -import org.openrewrite.java.search.UsesJavaVersion; -import org.openrewrite.java.search.UsesType; -import org.openrewrite.java.tree.J; -import org.openrewrite.java.tree.JavaType; -import org.openrewrite.java.tree.TypeUtils; - -import java.time.Duration; -import java.util.Comparator; - -public class AddSerialAnnotationToSerialVersionUID extends Recipe { - @Override - public String getDisplayName() { - return "Add `@Serial` annotation to `serialVersionUID`"; - } - - @Override - public String getDescription() { - return "Annotation any `serialVersionUID` fields with `@Serial` to indicate it's part of the serialization mechanism."; - } - - @Override - public Duration getEstimatedEffortPerOccurrence() { - return Duration.ofMinutes(1); - } - - @Override - @NonNull - public TreeVisitor getVisitor() { - return Preconditions.check( - Preconditions.and( - new UsesJavaVersion<>(14), - new UsesType<>("java.io.Serializable", true) - ), - new JavaIsoVisitor() { - @Override - public J.ClassDeclaration visitClassDeclaration(J.ClassDeclaration classDecl, ExecutionContext ctx) { - if (TypeUtils.isAssignableTo("java.io.Serializable", classDecl.getType())) { - return super.visitClassDeclaration(classDecl, ctx); - } - return classDecl; - } - - @Override - public J.VariableDeclarations visitVariableDeclarations(J.VariableDeclarations multiVariable, ExecutionContext ctx) { - J.VariableDeclarations vd = super.visitVariableDeclarations(multiVariable, ctx); - if (isPrivateStaticFinalLongSerialVersionUID(vd) && - FindAnnotations.find(vd, "@java.io.Serial").isEmpty()) { - maybeAddImport("java.io.Serial"); - return JavaTemplate.builder("@Serial") - .imports("java.io.Serial") - .build() - .apply(getCursor(), vd.getCoordinates().addAnnotation(Comparator.comparing(J.Annotation::getSimpleName))); - } - return vd; - } - - private boolean isPrivateStaticFinalLongSerialVersionUID(J.VariableDeclarations vd) { - return vd.hasModifier(J.Modifier.Type.Private) && - vd.hasModifier(J.Modifier.Type.Static) && - vd.hasModifier(J.Modifier.Type.Final) && - TypeUtils.asPrimitive(vd.getType()) == JavaType.Primitive.Long && - vd.getVariables().size() == 1 && - "serialVersionUID".equals(vd.getVariables().get(0).getSimpleName()); - } - } - ); - } -} diff --git a/headless-services/commons/commons-rewrite/src/main/java/org/openrewrite/staticanalysis/AddSerialVersionUidToSerializable.java b/headless-services/commons/commons-rewrite/src/main/java/org/openrewrite/staticanalysis/AddSerialVersionUidToSerializable.java deleted file mode 100644 index 67e25a0395..0000000000 --- a/headless-services/commons/commons-rewrite/src/main/java/org/openrewrite/staticanalysis/AddSerialVersionUidToSerializable.java +++ /dev/null @@ -1,149 +0,0 @@ -/* - * Copyright 2021 the original author or authors. - *

- * 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 - *

- * https://www.apache.org/licenses/LICENSE-2.0 - *

- * 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.staticanalysis; - -import org.jspecify.annotations.Nullable; -import org.openrewrite.*; -import org.openrewrite.internal.ListUtils; -import org.openrewrite.java.JavaIsoVisitor; -import org.openrewrite.java.JavaTemplate; -import org.openrewrite.java.tree.J; -import org.openrewrite.java.tree.JavaType; -import org.openrewrite.java.tree.Space; -import org.openrewrite.java.tree.TypeUtils; -import org.openrewrite.marker.Markers; - -import java.util.Arrays; -import java.util.Collections; -import java.util.List; -import java.util.Set; -import java.util.concurrent.atomic.AtomicBoolean; - -public class AddSerialVersionUidToSerializable extends Recipe { - - @Override - public String getDisplayName() { - return "Add `serialVersionUID` to a `Serializable` class when missing"; - } - - @Override - public String getDescription() { - return "A `serialVersionUID` field is strongly recommended in all `Serializable` classes. If this is not " + - "defined on a `Serializable` class, the compiler will generate this value. If a change is later made " + - "to the class, the generated value will change and attempts to deserialize the class will fail."; - } - - @Override - public Set getTags() { - return Collections.singleton("RSPEC-S2057"); - } - - @Override - public TreeVisitor getVisitor() { - return new JavaIsoVisitor() { - final JavaTemplate template = JavaTemplate.builder("private static final long serialVersionUID = 1;").build(); - - @Override - public J.MethodDeclaration visitMethodDeclaration(J.MethodDeclaration method, ExecutionContext ctx) { - // Anonymous classes are not of interest - return method; - } - - @Override - public J.VariableDeclarations visitVariableDeclarations(J.VariableDeclarations multiVariable, ExecutionContext ctx) { - // Anonymous classes are not of interest - return multiVariable; - } - - @Override - public J.ClassDeclaration visitClassDeclaration(J.ClassDeclaration classDecl, ExecutionContext ctx) { - J.ClassDeclaration c = super.visitClassDeclaration(classDecl, ctx); - if (c.getKind() != J.ClassDeclaration.Kind.Type.Class || !requiresSerialVersionField(classDecl.getType())) { - return c; - } - AtomicBoolean needsSerialVersionId = new AtomicBoolean(true); - J.Block body = c.getBody(); - c = c.withBody(c.getBody().withStatements(ListUtils.map(c.getBody().getStatements(), s -> { - if (!(s instanceof J.VariableDeclarations)) { - return s; - } - J.VariableDeclarations varDecls = (J.VariableDeclarations) s; - for (J.VariableDeclarations.NamedVariable v : varDecls.getVariables()) { - if ("serialVersionUID".equals(v.getSimpleName())) { - needsSerialVersionId.set(false); - return maybeAutoFormat(varDecls, maybeFixVariableDeclarations(varDecls), ctx, new Cursor(getCursor(), body)); - } - } - return s; - }))); - if (needsSerialVersionId.get()) { - c = template.apply(updateCursor(c), c.getBody().getCoordinates().firstStatement()); - } - return c; - } - - private J.VariableDeclarations maybeFixVariableDeclarations(J.VariableDeclarations varDecls) { - List modifiers = varDecls.getModifiers(); - if (!J.Modifier.hasModifier(modifiers, J.Modifier.Type.Private) || - !J.Modifier.hasModifier(modifiers, J.Modifier.Type.Static) || - !J.Modifier.hasModifier(modifiers, J.Modifier.Type.Final)) { - varDecls = varDecls.withModifiers(Arrays.asList( - new J.Modifier(Tree.randomId(), Space.EMPTY, Markers.EMPTY, null, J.Modifier.Type.Private, Collections.emptyList()), - new J.Modifier(Tree.randomId(), Space.SINGLE_SPACE, Markers.EMPTY, null, J.Modifier.Type.Static, Collections.emptyList()), - new J.Modifier(Tree.randomId(), Space.SINGLE_SPACE, Markers.EMPTY, null, J.Modifier.Type.Final, Collections.emptyList()) - )); - } - if (TypeUtils.asPrimitive(varDecls.getType()) != JavaType.Primitive.Long) { - varDecls = varDecls.withTypeExpression(new J.Primitive(Tree.randomId(), Space.EMPTY, Markers.EMPTY, JavaType.Primitive.Long)); - } - return varDecls; - } - - private boolean requiresSerialVersionField(@Nullable JavaType type) { - if (type == null) { - return false; - } else if (type instanceof JavaType.Primitive) { - return true; - } else if (type instanceof JavaType.Array) { - return requiresSerialVersionField(((JavaType.Array) type).getElemType()); - } else if (type instanceof JavaType.Parameterized) { - JavaType.Parameterized parameterized = (JavaType.Parameterized) type; - if (parameterized.isAssignableTo("java.util.Collection") || parameterized.isAssignableTo("java.util.Map")) { - //If the type is either a collection or a map, make sure the type parameters are serializable. We - //force all type parameters to be checked to correctly scoop up all non-serializable candidates. - boolean typeParametersSerializable = true; - for (JavaType typeParameter : parameterized.getTypeParameters()) { - typeParametersSerializable = typeParametersSerializable && requiresSerialVersionField(typeParameter); - } - return typeParametersSerializable; - } - //All other parameterized types fall through - } else if (type instanceof JavaType.FullyQualified) { - JavaType.FullyQualified fq = (JavaType.FullyQualified) type; - if (fq.getKind() == JavaType.Class.Kind.Enum) { - return false; - } - - if (fq.getKind() != JavaType.Class.Kind.Interface && - !fq.isAssignableTo("java.lang.Throwable")) { - return fq.isAssignableTo("java.io.Serializable"); - } - } - return false; - } - }; - } -} diff --git a/headless-services/commons/commons-rewrite/src/main/java/org/openrewrite/staticanalysis/ChainStringBuilderAppendCalls.java b/headless-services/commons/commons-rewrite/src/main/java/org/openrewrite/staticanalysis/ChainStringBuilderAppendCalls.java deleted file mode 100644 index 911d39ffdd..0000000000 --- a/headless-services/commons/commons-rewrite/src/main/java/org/openrewrite/staticanalysis/ChainStringBuilderAppendCalls.java +++ /dev/null @@ -1,199 +0,0 @@ -/* - * Copyright 2023 the original author or authors. - *

- * 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 - *

- * https://www.apache.org/licenses/LICENSE-2.0 - *

- * 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.staticanalysis; - -import org.jspecify.annotations.Nullable; -import org.openrewrite.*; -import org.openrewrite.java.JavaIsoVisitor; -import org.openrewrite.java.JavaParser; -import org.openrewrite.java.MethodMatcher; -import org.openrewrite.java.search.UsesMethod; -import org.openrewrite.java.tree.*; - -import java.time.Duration; -import java.util.ArrayList; -import java.util.List; - -import static java.util.Collections.emptyList; -import static java.util.Collections.singletonList; - -public class ChainStringBuilderAppendCalls extends Recipe { - private static final MethodMatcher STRING_BUILDER_APPEND = new MethodMatcher("java.lang.StringBuilder append(String)"); - - @SuppressWarnings("ALL") // Stop NoMutableStaticFieldsInRecipes from suggesting to remove this mutable static field - private static J.Binary additiveBinaryTemplate = null; - - @Override - public String getDisplayName() { - return "Chain `StringBuilder.append()` calls"; - } - - @Override - public String getDescription() { - return "String concatenation within calls to `StringBuilder.append()` causes unnecessary memory allocation. Except for concatenations of String literals, which are joined together at compile time. Replaces inefficient concatenations with chained calls to `StringBuilder.append()`."; - } - - @Override - public @Nullable Duration getEstimatedEffortPerOccurrence() { - return Duration.ofMinutes(2); - } - - @Override - public TreeVisitor getVisitor() { - return Preconditions.check(new UsesMethod<>(STRING_BUILDER_APPEND), Repeat.repeatUntilStable(new JavaIsoVisitor() { - @Override - public J.MethodInvocation visitMethodInvocation(J.MethodInvocation method, ExecutionContext ctx) { - J.MethodInvocation m = super.visitMethodInvocation(method, ctx); - - if (STRING_BUILDER_APPEND.matches(m)) { - List arguments = m.getArguments(); - if (arguments.size() != 1) { - return m; - } - - List flattenExpressions = new ArrayList<>(); - boolean flattenable = flatAdditiveExpressions(arguments.get(0).unwrap(), flattenExpressions); - if (!flattenable) { - return m; - } - - if (flattenExpressions.stream().allMatch(J.Literal.class::isInstance)) { - return m; - } - - // group expressions - List groups = new ArrayList<>(); - List group = new ArrayList<>(); - boolean appendToString = false; - for (Expression exp : flattenExpressions) { - if (appendToString) { - if (exp instanceof J.Literal && - (((J.Literal) exp).getType() == JavaType.Primitive.String) - ) { - group.add(exp); - } else { - addToGroups(group, groups); - groups.add(exp); - } - } else { - if (exp instanceof J.Literal && - (((J.Literal) exp).getType() == JavaType.Primitive.String)) { - addToGroups(group, groups); - appendToString = true; - } else if ((exp instanceof J.Identifier || exp instanceof J.MethodInvocation) && exp.getType() != null) { - JavaType.FullyQualified fullyQualified = TypeUtils.asFullyQualified(exp.getType()); - if (fullyQualified != null && fullyQualified.getFullyQualifiedName().equals("java.lang.String")) { - addToGroups(group, groups); - appendToString = true; - } - } - group.add(exp); - - } - } - addToGroups(group, groups); - - J.MethodInvocation chainedMethods = m.withArguments(singletonList(groups.get(0))); - for (int i = 1; i < groups.size(); i++) { - chainedMethods = chainedMethods.withSelect(chainedMethods) - .withArguments(singletonList(groups.get(i).unwrap())) - .withPrefix(Space.EMPTY); - } - - return chainedMethods; - } - - return m; - } - })); - } - - /** - * Concat two literals to an expression with '+' and surrounded with single space. - */ - public static J.Binary concatAdditionBinary(Expression left, Expression right) { - J.Binary b = getAdditiveBinaryTemplate(); - return b.withPrefix(b.getLeft().getPrefix()) - .withLeft(left) - .withRight(right.withPrefix(Space.build(" " + right.getPrefix().getWhitespace(), emptyList()))); - } - - /** - * Concat expressions to an expression with '+' connected. - */ - public static @Nullable Expression additiveExpression(Expression... expressions) { - Expression expression = null; - for (Expression element : expressions) { - if (element != null) { - expression = (expression == null) ? element : concatAdditionBinary(expression, element); - } - } - return expression; - } - - public static @Nullable Expression additiveExpression(List expressions) { - return additiveExpression(expressions.toArray(new Expression[0])); - } - - public static J.Binary getAdditiveBinaryTemplate() { - if (additiveBinaryTemplate == null) { - //noinspection OptionalGetWithoutIsPresent - J.CompilationUnit cu = JavaParser.fromJavaVersion() - .build() - .parse("class A { String s = \"A\" + \"B\";}") - .map(J.CompilationUnit.class::cast) - .findFirst() - .get(); - additiveBinaryTemplate = (J.Binary) ((J.VariableDeclarations) cu.getClasses().get(0) - .getBody() - .getStatements().get(0)) - .getVariables().get(0) - .getInitializer(); - assert additiveBinaryTemplate != null; - } - return additiveBinaryTemplate; - } - - /** - * Concat an additive expression in a group and add to groups - */ - private static void addToGroups(List group, List groups) { - if (!group.isEmpty()) { - groups.add(additiveExpression(group)); - group.clear(); - } - } - - public static boolean flatAdditiveExpressions(Expression expression, List expressionList) { - if (expression instanceof J.Binary) { - J.Binary b = (J.Binary) expression; - if (b.getOperator() != J.Binary.Type.Addition) { - return false; - } - - return flatAdditiveExpressions(b.getLeft(), expressionList) && - flatAdditiveExpressions(b.getRight(), expressionList); - } else if (expression instanceof J.Literal || - expression instanceof J.Identifier || - expression instanceof J.MethodInvocation || - expression instanceof J.Parentheses) { - expressionList.add(expression.withPrefix(Space.EMPTY)); - return true; - } - - return false; - } -} diff --git a/headless-services/commons/commons-rewrite/src/main/java/org/openrewrite/staticanalysis/InstanceOfPatternMatch.java b/headless-services/commons/commons-rewrite/src/main/java/org/openrewrite/staticanalysis/InstanceOfPatternMatch.java deleted file mode 100644 index 5306421f69..0000000000 --- a/headless-services/commons/commons-rewrite/src/main/java/org/openrewrite/staticanalysis/InstanceOfPatternMatch.java +++ /dev/null @@ -1,527 +0,0 @@ -/* - * Copyright 2021 the original author or authors. - *

- * 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 - *

- * https://www.apache.org/licenses/LICENSE-2.0 - *

- * 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.staticanalysis; - -import org.jspecify.annotations.Nullable; -import org.openrewrite.*; -import org.openrewrite.java.JavaVisitor; -import org.openrewrite.java.VariableNameUtils; -import org.openrewrite.java.search.SemanticallyEqual; -import org.openrewrite.java.search.UsesJavaVersion; -import org.openrewrite.java.tree.*; -import org.openrewrite.java.tree.J.VariableDeclarations.NamedVariable; -import org.openrewrite.marker.Markers; -import org.openrewrite.staticanalysis.groovy.GroovyFileChecker; -import org.openrewrite.staticanalysis.kotlin.KotlinFileChecker; - -import java.time.Duration; -import java.util.*; -import java.util.regex.Pattern; -import java.util.stream.Collectors; -import java.util.stream.Stream; - -import static java.util.Collections.emptyList; -import static java.util.Objects.requireNonNull; -import static org.openrewrite.Tree.randomId; -import static org.openrewrite.java.VariableNameUtils.GenerationStrategy.INCREMENT_NUMBER; - -public class InstanceOfPatternMatch extends Recipe { - - @Override - public String getDisplayName() { - return "Changes code to use Java 17's `instanceof` pattern matching"; - } - - @Override - public String getDescription() { - return "Adds pattern variables to `instanceof` expressions wherever the same (side effect free) expression is referenced in a corresponding type cast expression within the flow scope of the `instanceof`." + - " Currently, this recipe supports `if` statements and ternary operator expressions."; - } - - @Override - public Duration getEstimatedEffortPerOccurrence() { - return Duration.ofMinutes(1); - } - - @Override - public TreeVisitor getVisitor() { - TreeVisitor preconditions = Preconditions.and( - new UsesJavaVersion<>(17), - Preconditions.not(new KotlinFileChecker<>()), - Preconditions.not(new GroovyFileChecker<>()) - ); - - return Preconditions.check(preconditions, new JavaVisitor() { - @Override - public @Nullable J postVisit(J tree, ExecutionContext ctx) { - J result = super.postVisit(tree, ctx); - InstanceOfPatternReplacements original = getCursor().getMessage("flowTypeScope"); - if (original != null && !original.isEmpty()) { - return UseInstanceOfPatternMatching.refactor(result, original, getCursor().getParentOrThrow()); - } - return result; - } - - @Override - public J.InstanceOf visitInstanceOf(J.InstanceOf instanceOf, ExecutionContext ctx) { - instanceOf = (J.InstanceOf) super.visitInstanceOf(instanceOf, ctx); - if (instanceOf.getPattern() != null || !instanceOf.getSideEffects().isEmpty()) { - return instanceOf; - } - - Cursor maybeReplacementRoot = null; - J additionalContext = null; - boolean flowScopeBreakEncountered = false; - for (Iterator it = getCursor().getPathAsCursors(); it.hasNext(); ) { - Cursor next = it.next(); - Object value = next.getValue(); - if (value instanceof J.Binary) { - J.Binary binary = (J.Binary) value; - if (!flowScopeBreakEncountered && binary.getOperator() == J.Binary.Type.And) { - additionalContext = binary; - } else { - flowScopeBreakEncountered = true; - } - } else if (value instanceof J.Unary && ((J.Unary) value).getOperator() == J.Unary.Type.Not) { - // TODO this could be improved (the pattern variable may be applicable in the else case - // or even in subsequent statements (due to the flow scope semantics) - flowScopeBreakEncountered = true; - } else if (value instanceof Statement) { - maybeReplacementRoot = next; - break; - } - } - - if (maybeReplacementRoot != null) { - J root = maybeReplacementRoot.getValue(); - Set contexts = new HashSet<>(); - if (!flowScopeBreakEncountered) { - if (root instanceof J.If) { - contexts.add(((J.If) root).getThenPart()); - } else if (root instanceof J.Ternary) { - contexts.add(((J.Ternary) root).getTruePart()); - } - } - if (additionalContext != null) { - contexts.add(additionalContext); - } - - if (!contexts.isEmpty()) { - InstanceOfPatternReplacements replacements = maybeReplacementRoot - .computeMessageIfAbsent("flowTypeScope", k -> new InstanceOfPatternReplacements(root)); - replacements.registerInstanceOf(instanceOf, contexts); - } - } - return instanceOf; - } - - @Override - public J visitTypeCast(J.TypeCast typeCast, ExecutionContext ctx) { - J result = super.visitTypeCast(typeCast, ctx); - if (result instanceof J.TypeCast) { - InstanceOfPatternReplacements replacements = getCursor().getNearestMessage("flowTypeScope"); - if (replacements != null) { - replacements.registerTypeCast((J.TypeCast) result, getCursor()); - } - } - return result; - } - }); - } - - private static class ExpressionAndType { - private final Expression expression; - private final JavaType type; - public ExpressionAndType(Expression expression, JavaType type) { - this.expression = expression; - this.type = type; - } - public Expression getExpression() { - return expression; - } - public JavaType getType() { - return type; - } - - } - - private static class VariableAndTypeTree { - private final J.VariableDeclarations.NamedVariable variable; - private final TypeTree type; - public VariableAndTypeTree(NamedVariable variable, TypeTree type) { - this.variable = variable; - this.type = type; - } - public J.VariableDeclarations.NamedVariable getVariable() { - return variable; - } - public TypeTree getType() { - return type; - } - - } - - private static class InstanceOfPatternReplacements { - private final J root; - private final Map instanceOfs = new HashMap<>(); - private final Map> contexts = new HashMap<>(); - private final Map> contextScopes = new HashMap<>(); - private final Map replacements = new HashMap<>(); - private final Map variablesToDelete = new HashMap<>(); - - public InstanceOfPatternReplacements(J root) { - this.root = root; - } - - public void registerInstanceOf(J.InstanceOf instanceOf, Set contexts) { - Expression expression = instanceOf.getExpression(); - JavaType type = ((TypedTree) instanceOf.getClazz()).getType(); - if (type == null) { - return; - } - - Optional existing = instanceOfs.keySet().stream() - .filter(k -> TypeUtils.isAssignableTo(type, k.getType()) && - SemanticallyEqual.areEqual(k.getExpression(), expression)) - .findAny(); - if (!existing.isPresent()) { - instanceOfs.put(new ExpressionAndType(expression, type), instanceOf); - this.contexts.put(instanceOf, contexts); - } - } - - public void registerTypeCast(J.TypeCast typeCast, Cursor cursor) { - Expression expression = typeCast.getExpression(); - JavaType type = typeCast.getClazz().getTree().getType(); - - Optional match = instanceOfs.keySet().stream() - .filter(k -> TypeUtils.isAssignableTo(type, k.getType()) && - SemanticallyEqual.areEqual(k.getExpression(), expression)) - .findAny(); - if (match.isPresent()) { - Cursor parent = cursor.getParentTreeCursor(); - J.InstanceOf instanceOf = instanceOfs.get(match.get()); - Set validContexts = contexts.get(instanceOf); - for (Iterator it = cursor.getPath(); it.hasNext(); ) { - Object next = it.next(); - if (validContexts.contains(next)) { - if (isAcceptableTypeCast(typeCast) && isTheSameAsOtherTypeCasts(typeCast, instanceOf)) { - if (parent.getValue() instanceof J.VariableDeclarations.NamedVariable && - !variablesToDelete.containsKey(instanceOf)) { - variablesToDelete.put(instanceOf, new VariableAndTypeTree(parent.getValue(), - requireNonNull(parent.firstEnclosing(J.VariableDeclarations.class).getTypeExpression()))); - } else { - replacements.put(typeCast, instanceOf); - } - contextScopes.computeIfAbsent(instanceOf, k -> new HashSet<>()).add(cursor); - } else { - replacements.entrySet().removeIf(e -> e.getValue() == instanceOf); - variablesToDelete.remove(instanceOf); - contextScopes.remove(instanceOf); - contexts.remove(instanceOf); - instanceOfs.entrySet().removeIf(e -> e.getValue() == instanceOf); - } - break; - } else if (root == next) { - break; - } - } - } - } - - private boolean isAcceptableTypeCast(J.TypeCast typeCast) { - TypeTree typeTree = typeCast.getClazz().getTree(); - if (typeTree instanceof J.ParameterizedType) { - return requireNonNull(((J.ParameterizedType) typeTree).getTypeParameters()).stream().allMatch(J.Wildcard.class::isInstance); - } - return true; - } - - private boolean isTheSameAsOtherTypeCasts(J.TypeCast typeCast, J.InstanceOf instanceOf) { - return replacements - .entrySet() - .stream() - .filter(e -> e.getValue() == instanceOf) - .findFirst() - .map(e -> e.getKey().getType().equals(typeCast.getType())) - .orElse(true); - } - - public boolean isEmpty() { - return replacements.isEmpty() && variablesToDelete.isEmpty(); - } - - public J.InstanceOf processInstanceOf(J.InstanceOf instanceOf, Cursor cursor) { - if (!contextScopes.containsKey(instanceOf)) { - return instanceOf; - } - JavaType type = ((TypedTree) instanceOf.getClazz()).getType(); - String name = patternVariableName(instanceOf, cursor); - J.InstanceOf result = instanceOf.withPattern(new J.Identifier( - randomId(), - Space.build(" ", emptyList()), - Markers.EMPTY, - emptyList(), - name, - type, - null)); - - J currentTypeTree = instanceOf.getClazz(); - TypeTree typeCastTypeTree = computeTypeTreeFromTypeCasts(instanceOf); - // If type tree from type cast is not parameterized then NVM. Instance of should already have proper type - if (typeCastTypeTree instanceof J.ParameterizedType) { - J.ParameterizedType parameterizedType = (J.ParameterizedType) typeCastTypeTree; - result = result.withClazz(parameterizedType.withId(Tree.randomId()).withPrefix(currentTypeTree.getPrefix())); - } - - // update entry in replacements to share the pattern variable name - for (Map.Entry entry : replacements.entrySet()) { - if (entry.getValue() == instanceOf) { - entry.setValue(result); - } - } - return result; - } - - private TypeTree computeTypeTreeFromTypeCasts(J.InstanceOf instanceOf) { - TypeTree typeCastTypeTree = replacements - .entrySet() - .stream() - .filter(e -> e.getValue() == instanceOf) - .findFirst() - .map(e -> e.getKey().getClazz().getTree()) - .orElse(null); - if (typeCastTypeTree == null) { - VariableAndTypeTree variable = variablesToDelete.get(instanceOf); - if (variable != null) { - typeCastTypeTree = variable.getType(); - } - } - return typeCastTypeTree; - } - - private String patternVariableName(J.InstanceOf instanceOf, Cursor cursor) { - VariableNameStrategy strategy; - if (root instanceof J.If) { - VariableAndTypeTree variableData = variablesToDelete.get(instanceOf); - strategy = variableData != null ? - VariableNameStrategy.exact(variableData.getVariable().getSimpleName()) : - VariableNameStrategy.normal(contextScopes.get(instanceOf)); - } else { - strategy = VariableNameStrategy.short_(); - } - String baseName = strategy.variableName(((TypeTree) instanceOf.getClazz()).getType()); - return VariableNameUtils.generateVariableName(baseName, cursor, INCREMENT_NUMBER); - } - - public @Nullable J processTypeCast(J.TypeCast typeCast, Cursor cursor) { - J.InstanceOf instanceOf = replacements.get(typeCast); - if (instanceOf != null && instanceOf.getPattern() != null) { - String name = ((J.Identifier) instanceOf.getPattern()).getSimpleName(); - TypedTree owner = cursor.firstEnclosing(J.MethodDeclaration.class); - owner = owner != null ? owner : cursor.firstEnclosingOrThrow(J.ClassDeclaration.class); - JavaType.Variable fieldType = new JavaType.Variable(null, Flag.Default.getBitMask(), name, owner.getType(), typeCast.getType(), emptyList()); - return new J.Identifier( - randomId(), - typeCast.getPrefix(), - Markers.EMPTY, - emptyList(), - name, - typeCast.getType(), - fieldType); - } - return null; - } - - public @Nullable J processVariableDeclarations(J.VariableDeclarations multiVariable) { - return multiVariable.getVariables().stream().anyMatch(v -> variablesToDelete.values().stream().anyMatch(vd -> vd.getVariable() == v)) ? null : multiVariable; - } - } - - private static class UseInstanceOfPatternMatching extends JavaVisitor { - - private final InstanceOfPatternReplacements replacements; - - public UseInstanceOfPatternMatching(InstanceOfPatternReplacements replacements) { - this.replacements = replacements; - } - - static @Nullable J refactor(@Nullable J tree, InstanceOfPatternReplacements replacements, Cursor cursor) { - return new UseInstanceOfPatternMatching(replacements).visit(tree, 0, cursor); - } - - @Override - public J visitBinary(J.Binary original, Integer integer) { - Expression newLeft = (Expression) super.visitNonNull(original.getLeft(), integer); - if (newLeft != original.getLeft()) { - // The left side changed, so the right side should see any introduced variable names - J.Binary replacement = original.withLeft(newLeft); - Cursor widenedCursor = updateCursor(replacement); - - Expression newRight; - if (original.getRight() instanceof J.InstanceOf) { - newRight = replacements.processInstanceOf((J.InstanceOf) original.getRight(), widenedCursor); - } else if (original.getRight() instanceof J.Parentheses && - ((J.Parentheses) original.getRight()).getTree() instanceof J.InstanceOf) { - @SuppressWarnings("unchecked") - J.Parentheses originalRight = (J.Parentheses) original.getRight(); - newRight = originalRight.withTree(replacements.processInstanceOf(originalRight.getTree(), widenedCursor)); - } else { - newRight = (Expression) super.visitNonNull(original.getRight(), integer, widenedCursor); - } - return replacement.withRight(newRight); - } - // The left side didn't change, so the right side doesn't need to see any introduced variable names - return super.visitBinary(original, integer); - } - - @Override - public J.InstanceOf visitInstanceOf(J.InstanceOf instanceOf, Integer executionContext) { - instanceOf = (J.InstanceOf) super.visitInstanceOf(instanceOf, executionContext); - instanceOf = replacements.processInstanceOf(instanceOf, getCursor()); - return instanceOf; - } - - @Override - public J visitParentheses(J.Parentheses parens, Integer executionContext) { - if (parens.getTree() instanceof J.TypeCast) { - J replacement = replacements.processTypeCast((J.TypeCast) parens.getTree(), getCursor()); - if (replacement != null) { - return replacement.withPrefix(parens.getPrefix()); - } - } - return super.visitParentheses(parens, executionContext); - } - - @Override - public J visitTypeCast(J.TypeCast typeCast, Integer executionContext) { - typeCast = (J.TypeCast) super.visitTypeCast(typeCast, executionContext); - J replacement = replacements.processTypeCast(typeCast, getCursor()); - if (replacement != null) { - return replacement; - } - return typeCast; - } - - @Override - public @Nullable J visitVariableDeclarations(J.VariableDeclarations multiVariable, Integer integer) { - multiVariable = (J.VariableDeclarations) super.visitVariableDeclarations(multiVariable, integer); - return replacements.processVariableDeclarations(multiVariable); - } - } - - private static class VariableNameStrategy { - public static final Pattern NAME_SPLIT_PATTERN = Pattern.compile("[$._]*(?=\\p{Upper}+[\\p{Lower}\\p{Digit}]*)"); - private final Style style; - - @Nullable - private final String name; - - private final Set contextScopes; - - enum Style { - SHORT, NORMAL, EXACT - } - - private VariableNameStrategy(Style style, @Nullable String exactName, Set contextScopes) { - this.style = style; - this.name = exactName; - this.contextScopes = contextScopes; - } - - static VariableNameStrategy short_() { - return new VariableNameStrategy(Style.SHORT, null, Collections.emptySet()); - } - - static VariableNameStrategy normal(Set contextScopes) { - return new VariableNameStrategy(Style.NORMAL, null, contextScopes); - } - - static VariableNameStrategy exact(String name) { - return new VariableNameStrategy(Style.EXACT, name, Collections.emptySet()); - } - - public String variableName(@Nullable JavaType type) { - // the instanceof operator only accepts classes (without generics) and arrays - if (style == Style.EXACT) { - //noinspection DataFlowIssue - return name; - } else if (type instanceof JavaType.FullyQualified) { - String className = ((JavaType.FullyQualified) type).getClassName(); - className = className.substring(className.lastIndexOf('.') + 1); - String baseName = null; - switch (style) { - case SHORT: - StringBuilder builder = new StringBuilder(); - for (int i = 0; i < className.length(); i++) { - char c = className.charAt(i); - if (Character.isUpperCase(c)) { - builder.append(Character.toLowerCase(c)); - } - } - baseName = builder.length() > 0 ? builder.toString() : "o"; - break; - case NORMAL: - Set namesInScope = contextScopes.stream() - .flatMap(c -> VariableNameUtils.findNamesInScope(c).stream()) - .collect(Collectors.toSet()); - List nameSegments = Stream.of(NAME_SPLIT_PATTERN.split(className)) - .filter(s -> !s.isEmpty()).collect(Collectors.toList()); - for (int i = nameSegments.size() - 1; i >= 0; i--) { - String name = String.join("", nameSegments.subList(i, nameSegments.size())); - if (name.length() < 2) { - continue; - } - name = Character.toLowerCase(name.charAt(0)) + name.substring(1); - if (!namesInScope.contains(name)) { - baseName = name; - break; - } - } - if (baseName == null) { - baseName = Character.toLowerCase(className.charAt(0)) + className.substring(1); - } - break; - default: - baseName = "obj"; - } - String candidate = baseName; - OUTER: - while (true) { - for (Cursor scope : contextScopes) { - String newCandidate = VariableNameUtils.generateVariableName(candidate, scope, INCREMENT_NUMBER); - if (!newCandidate.equals(candidate)) { - candidate = newCandidate; - continue OUTER; - } - } - break; - } - return candidate; - } else if (type instanceof JavaType.Primitive) { - String keyword = ((JavaType.Primitive) type).getKeyword(); - return style == Style.SHORT ? keyword.substring(0, 1) : keyword; - } else if (type instanceof JavaType.Array) { - JavaType elemType = ((JavaType.Array) type).getElemType(); - while (elemType instanceof JavaType.Array) { - elemType = ((JavaType.Array) elemType).getElemType(); - } - return variableName(elemType) + 's'; - } - return style == Style.SHORT ? "o" : "obj"; - } - } -} diff --git a/headless-services/commons/commons-rewrite/src/main/java/org/openrewrite/staticanalysis/RemoveUnneededBlock.java b/headless-services/commons/commons-rewrite/src/main/java/org/openrewrite/staticanalysis/RemoveUnneededBlock.java deleted file mode 100644 index 129960d7ba..0000000000 --- a/headless-services/commons/commons-rewrite/src/main/java/org/openrewrite/staticanalysis/RemoveUnneededBlock.java +++ /dev/null @@ -1,100 +0,0 @@ -// -// Source code recreated from a .class file by IntelliJ IDEA -// (powered by FernFlower decompiler) -// - -package org.openrewrite.staticanalysis; - -import java.util.List; -import java.util.Objects; -import java.util.stream.Stream; -import org.openrewrite.ExecutionContext; -import org.openrewrite.Incubating; -import org.openrewrite.Recipe; -import org.openrewrite.TreeVisitor; -import org.openrewrite.internal.ListUtils; -import org.openrewrite.java.JavaVisitor; -import org.openrewrite.java.tree.J; -import org.openrewrite.java.tree.Statement; - -@Incubating( - since = "7.21.0" -) -public class RemoveUnneededBlock extends Recipe { - public RemoveUnneededBlock() { - } - - public String getDisplayName() { - return "Remove unneeded block"; - } - - public String getDescription() { - return "Flatten blocks into inline statements when possible."; - } - - public TreeVisitor getVisitor() { - return new RemoveUnneededBlockStatementVisitor(); - } - - static class RemoveUnneededBlockStatementVisitor extends JavaVisitor { - RemoveUnneededBlockStatementVisitor() { - } - - public J.Block visitBlock(J.Block block, ExecutionContext ctx) { - J.Block bl = (J.Block)super.visitBlock(block, ctx); - J directParent = (J)this.getCursor().getParentTreeCursor().getValue(); - return !(directParent instanceof J.NewClass) && !(directParent instanceof J.ClassDeclaration) ? this.maybeInlineBlock(bl, ctx) : bl; - } - - private J.Block maybeInlineBlock(J.Block block, ExecutionContext ctx) { - List statements = block.getStatements(); - if (statements.isEmpty()) { - return block; - } else { - Statement lastStatement = (Statement)statements.get(statements.size() - 1); - J.Block flattened = block.withStatements(ListUtils.flatMap(statements, (i, stmt) -> { - J.Block nested; - if (stmt instanceof J.Try) { - J.Try _try = (J.Try)stmt; - if (_try.getResources() != null || !_try.getCatches().isEmpty() || _try.getFinally() == null || !_try.getFinally().getStatements().isEmpty()) { - return stmt; - } - - nested = _try.getBody(); - } else { - if (!(stmt instanceof J.Block)) { - return stmt; - } - - nested = (J.Block)stmt; - } - - if (i < statements.size() - 1) { - Stream var10000 = nested.getStatements().stream(); - Objects.requireNonNull(J.VariableDeclarations.class); - if (var10000.anyMatch(J.VariableDeclarations.class::isInstance)) { - return stmt; - } - } - - return ListUtils.map(nested.getStatements(), (j, inlinedStmt) -> { - if (j == 0) { - inlinedStmt = (Statement)inlinedStmt.withPrefix(inlinedStmt.getPrefix().withComments(ListUtils.concatAll(nested.getComments(), inlinedStmt.getComments()))); - } - - return (Statement)this.autoFormat(inlinedStmt, ctx, this.getCursor()); - }); - })); - if (flattened == block) { - return block; - } else { - if (lastStatement instanceof J.Block) { - flattened = flattened.withEnd(flattened.getEnd().withComments(ListUtils.concatAll(((J.Block)lastStatement).getEnd().getComments(), flattened.getEnd().getComments()))); - } - - return flattened; - } - } - } - } -} diff --git a/headless-services/commons/commons-rewrite/src/main/java/org/openrewrite/staticanalysis/ReplaceDeprecatedRuntimeExecMethods.java b/headless-services/commons/commons-rewrite/src/main/java/org/openrewrite/staticanalysis/ReplaceDeprecatedRuntimeExecMethods.java deleted file mode 100644 index fb3e293dbb..0000000000 --- a/headless-services/commons/commons-rewrite/src/main/java/org/openrewrite/staticanalysis/ReplaceDeprecatedRuntimeExecMethods.java +++ /dev/null @@ -1,138 +0,0 @@ -/* - * Copyright 2023 the original author or authors. - *

- * 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 - *

- * https://www.apache.org/licenses/LICENSE-2.0 - *

- * 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.staticanalysis; - -import org.openrewrite.*; -import org.openrewrite.java.JavaIsoVisitor; -import org.openrewrite.java.JavaTemplate; -import org.openrewrite.java.MethodMatcher; -import org.openrewrite.java.search.UsesJavaVersion; -import org.openrewrite.java.tree.Expression; -import org.openrewrite.java.tree.J; -import org.openrewrite.java.tree.JavaType; - -import java.time.Duration; -import java.util.ArrayList; -import java.util.List; - -public class ReplaceDeprecatedRuntimeExecMethods extends Recipe { - private static final MethodMatcher RUNTIME_EXEC_CMD = new MethodMatcher("java.lang.Runtime exec(String)"); - private static final MethodMatcher RUNTIME_EXEC_CMD_ENVP = new MethodMatcher("java.lang.Runtime exec(String, String[])"); - private static final MethodMatcher RUNTIME_EXEC_CMD_ENVP_FILE = new MethodMatcher("java.lang.Runtime exec(String, String[], java.io.File)"); - - @Override - public String getDisplayName() { - return "Replace deprecated `Runtime#exec()` methods"; - } - - @Override - public String getDescription() { - return "Replace `Runtime#exec(String)` methods to use `exec(String[])` instead because the former is deprecated " + - "after Java 18 and is no longer recommended for use by the Java documentation."; - } - - @Override - public Duration getEstimatedEffortPerOccurrence() { - return Duration.ofMinutes(3); - } - - @Override - public TreeVisitor getVisitor() { - return Preconditions.check(new UsesJavaVersion<>(18), new JavaIsoVisitor() { - @Override - public J.MethodInvocation visitMethodInvocation(J.MethodInvocation method, ExecutionContext ctx) { - J.MethodInvocation m = super.visitMethodInvocation(method, ctx); - - if (RUNTIME_EXEC_CMD.matches(m) || RUNTIME_EXEC_CMD_ENVP.matches(m) || RUNTIME_EXEC_CMD_ENVP_FILE.matches(m)) { - Expression command = m.getArguments().get(0); - List commands = new ArrayList<>(); - boolean flattenAble = ChainStringBuilderAppendCalls.flatAdditiveExpressions(command, commands); - - StringBuilder sb = new StringBuilder(); - if (flattenAble) { - for (Expression e : commands) { - if (e instanceof J.Literal && ((J.Literal) e).getType() == JavaType.Primitive.String) { - sb.append(((J.Literal) e).getValue()); - } else { - flattenAble = false; - break; - } - } - } - - updateCursor(m); - if (flattenAble) { - String[] cmds = sb.toString().split(" "); - String templateCode = String.format("new String[] {%s}", toStringArguments(cmds)); - JavaTemplate template = JavaTemplate.builder(templateCode).build(); - - List args = m.getArguments(); - Cursor cursor = new Cursor(getCursor(), args.get(0)); - args.set(0, template.apply(cursor, args.get(0).getCoordinates().replace())); - - if (m.getMethodType() != null) { - List parameterTypes = m.getMethodType().getParameterTypes(); - parameterTypes.set(0, JavaType.ShallowClass.build("java.lang.String[]")); - - return m.withArguments(args) - .withMethodType(m.getMethodType().withParameterTypes(parameterTypes)); - } - } else { - // replace argument to 'command.split(" ")' - List args = m.getArguments(); - boolean needWrap = false; - Expression arg0 = args.get(0); - if (!(arg0 instanceof J.Identifier) && - !(arg0 instanceof J.Literal) && - !(arg0 instanceof J.MethodInvocation)) { - needWrap = true; - } - - String code = needWrap ? "(#{any()}).split(\" \")" : "#{any()}.split(\" \")"; - JavaTemplate template = JavaTemplate.builder(code).contextSensitive().build(); - Cursor cursor = new Cursor(getCursor(), args.get(0)); - arg0 = template.apply(cursor, args.get(0).getCoordinates().replace(), args.get(0)); - args.set(0, arg0); - - if (m.getMethodType() != null) { - List parameterTypes = m.getMethodType().getParameterTypes(); - parameterTypes.set(0, JavaType.ShallowClass.build("java.lang.String[]")); - - return m.withArguments(args).withMethodType(m.getMethodType().withParameterTypes(parameterTypes)); - } - return m; - } - } - - return m; - } - }); - } - - private static String toStringArguments(String[] cmds) { - StringBuilder sb = new StringBuilder(); - for (int i = 0; i < cmds.length; i++) { - String token = cmds[i]; - if (i != 0) { - sb.append(", "); - } - sb.append("\"") - .append(token) - .append("\""); - } - return sb.toString(); - } -} diff --git a/headless-services/commons/commons-rewrite/src/main/java/org/openrewrite/staticanalysis/groovy/GroovyFileChecker.java b/headless-services/commons/commons-rewrite/src/main/java/org/openrewrite/staticanalysis/groovy/GroovyFileChecker.java deleted file mode 100644 index acfe60807a..0000000000 --- a/headless-services/commons/commons-rewrite/src/main/java/org/openrewrite/staticanalysis/groovy/GroovyFileChecker.java +++ /dev/null @@ -1,35 +0,0 @@ -/* - * Copyright 2023 the original author or authors. - *

- * 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 - *

- * https://www.apache.org/licenses/LICENSE-2.0 - *

- * 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.staticanalysis.groovy; - -import org.jspecify.annotations.Nullable; -import org.openrewrite.Tree; -import org.openrewrite.TreeVisitor; -import org.openrewrite.groovy.tree.G; -import org.openrewrite.marker.SearchResult; - -/** - * Add a search marker if vising a Groovy file - */ -public class GroovyFileChecker

extends TreeVisitor { - @Override - public @Nullable Tree visit(@Nullable Tree tree, P p) { - if (tree instanceof G.CompilationUnit) { - return SearchResult.found(tree); - } - return tree; - } -} diff --git a/headless-services/commons/commons-rewrite/src/main/java/org/openrewrite/staticanalysis/kotlin/KotlinFileChecker.java b/headless-services/commons/commons-rewrite/src/main/java/org/openrewrite/staticanalysis/kotlin/KotlinFileChecker.java deleted file mode 100644 index 8f6e89e815..0000000000 --- a/headless-services/commons/commons-rewrite/src/main/java/org/openrewrite/staticanalysis/kotlin/KotlinFileChecker.java +++ /dev/null @@ -1,40 +0,0 @@ -/* - * Copyright 2023 the original author or authors. - *

- * 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 - *

- * https://www.apache.org/licenses/LICENSE-2.0 - *

- * 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.staticanalysis.kotlin; - -import org.jspecify.annotations.Nullable; -import org.openrewrite.Tree; -import org.openrewrite.TreeVisitor; -import org.openrewrite.java.tree.J; -import org.openrewrite.marker.SearchResult; - -/** - * Add a search marker if vising a Kotlin file - */ -public class KotlinFileChecker

extends TreeVisitor { - @Override - public @Nullable Tree visit(@Nullable Tree tree, P p) { - if (tree instanceof J.CompilationUnit cu) { - if (cu.getSourcePath() != null ) { - String fileName = cu.getSourcePath().getFileName().toString(); - if (fileName.endsWith(".kt") || fileName.endsWith(".kts")) { - return SearchResult.found(cu); - } - } - } - return tree; - } -} diff --git a/headless-services/commons/commons-rewrite/src/main/java/org/springframework/ide/vscode/commons/rewrite/LoadUtils.java b/headless-services/commons/commons-rewrite/src/main/java/org/springframework/ide/vscode/commons/rewrite/LoadUtils.java deleted file mode 100644 index 8146172845..0000000000 --- a/headless-services/commons/commons-rewrite/src/main/java/org/springframework/ide/vscode/commons/rewrite/LoadUtils.java +++ /dev/null @@ -1,176 +0,0 @@ -/******************************************************************************* - * Copyright (c) 2022, 2023 VMware, Inc. - * All rights reserved. This program and the accompanying materials - * are made available under the terms of the Eclipse Public License v1.0 - * which accompanies this distribution, and is available at - * https://www.eclipse.org/legal/epl-v10.html - * - * Contributors: - * VMware, Inc. - initial API and implementation - *******************************************************************************/ -package org.springframework.ide.vscode.commons.rewrite; - -import java.lang.reflect.Array; -import java.lang.reflect.Constructor; -import java.lang.reflect.Field; -import java.lang.reflect.InvocationTargetException; -import java.lang.reflect.Type; -import java.time.Duration; -import java.util.ArrayList; -import java.util.Collection; -import java.util.List; -import java.util.function.Function; - -import org.openrewrite.Recipe; -import org.openrewrite.config.DeclarativeRecipe; -import org.openrewrite.config.OptionDescriptor; -import org.openrewrite.config.RecipeDescriptor; -import org.openrewrite.config.RecipeIntrospectionException; -import org.openrewrite.internal.RecipeIntrospectionUtils; - -import com.google.gson.JsonDeserializationContext; -import com.google.gson.JsonDeserializer; -import com.google.gson.JsonElement; -import com.google.gson.JsonParseException; -import com.google.gson.JsonPrimitive; -import com.google.gson.JsonSerializationContext; -import com.google.gson.JsonSerializer; - -public class LoadUtils { - - public static class DurationTypeConverter implements JsonSerializer, JsonDeserializer { - @Override - public JsonElement serialize(Duration src, Type srcType, JsonSerializationContext context) { - return new JsonPrimitive(src.toNanos()); - } - - @Override - public Duration deserialize(JsonElement json, Type type, JsonDeserializationContext context) - throws JsonParseException { - return Duration.ofNanos(json.getAsLong()); - } - } - - public static Recipe createRecipe(RecipeDescriptor d, Function> getRecipeClass) { - return createRecipe(d, getRecipeClass, false); - } - - public static Recipe createRecipe(RecipeDescriptor d, Function> getRecipeClass, boolean shallow) { - Class recipeClazz = getRecipeClass == null ? null : getRecipeClass.apply(d.getName()); - if (recipeClazz == null || DeclarativeRecipe.class.getName().equals(recipeClazz.getName())) { - DeclarativeRecipe recipe = new DeclarativeRecipe(d.getName(), d.getDisplayName(), d.getDescription(), - d.getTags(), d.getEstimatedEffortPerOccurrence(), null, false, d.getMaintainers()); - if (!shallow) { - for (RecipeDescriptor subDescriptor : d.getRecipeList()) { - recipe.getRecipeList().add(createRecipe(subDescriptor, getRecipeClass)); - } - } - return recipe; - } else { - return constructRecipe(recipeClazz, d.getOptions()); - } - } - - public static Recipe constructRecipe(Class recipeClass, List options) { - Constructor primaryConstructor = RecipeIntrospectionUtils.getZeroArgsConstructor(recipeClass); - if (primaryConstructor == null) { - primaryConstructor = RecipeIntrospectionUtils.getPrimaryConstructor(recipeClass); - } - Object[] constructorArgs = new Object[primaryConstructor.getParameterCount()]; - List remainingOptions = new ArrayList<>(options); - for (int i = 0; i < primaryConstructor.getParameters().length; i++) { - java.lang.reflect.Parameter param = primaryConstructor.getParameters()[i]; - int j = 0; - for (; j < remainingOptions.size() && !param.getName().equals(remainingOptions.get(j).getName()); j++) { - // nothing - } - if (j < remainingOptions.size()) { - // found in option descriptors - OptionDescriptor optionDescriptor = remainingOptions.remove(j); - constructorArgs[i] = getOptionValueForType(param.getType(), optionDescriptor); - } else { - // param not found in option descriptors - if (param.getType().isPrimitive()) { - constructorArgs[i] = getPrimitiveDefault(param.getType()); - } else { - constructorArgs[i] = null; - } - } - } - primaryConstructor.setAccessible(true); - try { - Recipe recipe = (Recipe) primaryConstructor.newInstance(constructorArgs); - for (OptionDescriptor option : remainingOptions) { - if (option.getValue() != null) { - Field f = recipe.getClass().getDeclaredField(option.getName()); - f.setAccessible(true); - f.set(recipe, getOptionValueForType(f.getType(), option)); - } - } - return recipe; - } catch (InstantiationException | IllegalAccessException | InvocationTargetException | NoSuchFieldException | SecurityException e) { - // Should never happen - throw new RecipeIntrospectionException("Unable to call primary constructor for Recipe " + recipeClass, e); - } - } - - private static Object getOptionValueForType(Class type, OptionDescriptor option) { - if (option.getValue() instanceof Collection && type.isArray()) { - Collection arrayValue = (Collection) option.getValue(); - Object[] valueToSet = (Object[]) Array.newInstance(type.getComponentType(), arrayValue.size()); - int k = 0; - for (Object v : arrayValue) { - valueToSet[k] = getOptionValue(type.getComponentType(), v); - } - return valueToSet; - } else { - return getOptionValue(type, option.getValue()); - } - } - - private static Object getOptionValue(Class type, Object v) { - if (v != null) { - if (type.equals(int.class) || type.equals(Integer.class)) { - return ((Number) v).intValue(); - } else if (type.equals(long.class) || type.equals(Long.class)) { - return ((Number) v).longValue(); - } else if (type.equals(short.class) || type.equals(Short.class)) { - return ((Number) v).shortValue(); - } else if (type.equals(float.class) || type.equals(Float.class)) { - return ((Number) v).floatValue(); - } else if (type.equals(double.class) || type.equals(Double.class)) { - return ((Number) v).shortValue(); - } else if (Enum.class.isAssignableFrom(type) && v instanceof String) { - try { - return type.getMethod("valueOf").invoke(v); - } catch (NoSuchMethodException | InvocationTargetException | IllegalAccessException e) { - throw new RuntimeException(e); - } - } - } - return v; - } - - private static Object getPrimitiveDefault(Class t) { - if (t.equals(byte.class)) { - return (byte) 0; - } else if (t.equals(short.class)) { - return (short) 0; - } else if (t.equals(int.class)) { - return 0; - } else if (t.equals(long.class)) { - return 0L; - } else if (t.equals(float.class)) { - return 0.0f; - } else if (t.equals(double.class)) { - return 0.0d; - } else if (t.equals(char.class)) { - return '\u0000'; - } else if (t.equals(boolean.class)) { - return false; - } else { - throw new RecipeIntrospectionException(t.getCanonicalName() + " is not a supported primitive type"); - } - } - -} diff --git a/headless-services/commons/commons-rewrite/src/main/java/org/springframework/ide/vscode/commons/rewrite/gradle/GradleIJavaProjectParser.java b/headless-services/commons/commons-rewrite/src/main/java/org/springframework/ide/vscode/commons/rewrite/gradle/GradleIJavaProjectParser.java index d65f2707af..fc5f3b6c20 100644 --- a/headless-services/commons/commons-rewrite/src/main/java/org/springframework/ide/vscode/commons/rewrite/gradle/GradleIJavaProjectParser.java +++ b/headless-services/commons/commons-rewrite/src/main/java/org/springframework/ide/vscode/commons/rewrite/gradle/GradleIJavaProjectParser.java @@ -67,7 +67,7 @@ protected List getJavaProvenance(SourceFile buildFileAst, Path projectDi } @Override - protected List parseBuildFiles(Path projectDir, ExecutionContext ctx) { + public List parseBuildFiles(Path projectDir, ExecutionContext ctx) { OpenRewriteModel openRewriteGradleModel = OpenRewriteModelBuilder.forProjectDirectory(projectDir.toFile(), Paths.get(jp.getProjectBuild().getBuildFile()).toFile()); GradleProject gradleProject = org.openrewrite.gradle.toolingapi.GradleProject.toMarker(openRewriteGradleModel.gradleProject()); GradleParser gradleParser = GradleParser.builder().groovyParser(GroovyParser.builder()).build(); diff --git a/headless-services/commons/commons-rewrite/src/main/java/org/springframework/ide/vscode/commons/rewrite/java/ProjectParser.java b/headless-services/commons/commons-rewrite/src/main/java/org/springframework/ide/vscode/commons/rewrite/java/ProjectParser.java index f3b12c456d..b7b47bec07 100644 --- a/headless-services/commons/commons-rewrite/src/main/java/org/springframework/ide/vscode/commons/rewrite/java/ProjectParser.java +++ b/headless-services/commons/commons-rewrite/src/main/java/org/springframework/ide/vscode/commons/rewrite/java/ProjectParser.java @@ -164,7 +164,7 @@ protected Parser.Input createParserInput(Path p) { abstract protected List getJavaProvenance(SourceFile buildFileAst, Path projectDirectory); - abstract protected List parseBuildFiles(Path projectDir, ExecutionContext ctx); + abstract public List parseBuildFiles(Path projectDir, ExecutionContext ctx); abstract protected Collection getSourceSets(Path projectDir, SourceFile buildFile); diff --git a/headless-services/commons/commons-rewrite/src/main/java/org/springframework/ide/vscode/commons/rewrite/maven/MavenIJavaProjectParser.java b/headless-services/commons/commons-rewrite/src/main/java/org/springframework/ide/vscode/commons/rewrite/maven/MavenIJavaProjectParser.java index 46a8ae118c..081ec8aab9 100644 --- a/headless-services/commons/commons-rewrite/src/main/java/org/springframework/ide/vscode/commons/rewrite/maven/MavenIJavaProjectParser.java +++ b/headless-services/commons/commons-rewrite/src/main/java/org/springframework/ide/vscode/commons/rewrite/maven/MavenIJavaProjectParser.java @@ -102,7 +102,7 @@ protected List getJavaProvenance(SourceFile maven, Path projectDirectory } @Override - protected List parseBuildFiles(Path projectDir, ExecutionContext ctx) { + public List parseBuildFiles(Path projectDir, ExecutionContext ctx) { MavenParser mavenParser = mavenParserBuilder.build(); return mavenParser.parseInputs(() -> getInputs(Stream.of(Paths.get(jp.getProjectBuild().getBuildFile()))).iterator(), null, ctx).collect(Collectors.toList()); } diff --git a/headless-services/commons/commons-rewrite/src/test/java/org/openrewrite/staticanalysis/AddSerialAnnotationToSerialVersionUIDTest.java b/headless-services/commons/commons-rewrite/src/test/java/org/openrewrite/staticanalysis/AddSerialAnnotationToSerialVersionUIDTest.java deleted file mode 100644 index cb3beed753..0000000000 --- a/headless-services/commons/commons-rewrite/src/test/java/org/openrewrite/staticanalysis/AddSerialAnnotationToSerialVersionUIDTest.java +++ /dev/null @@ -1,192 +0,0 @@ -/* - * Copyright 2024 the original author or authors. - *

- * 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 - *

- * https://www.apache.org/licenses/LICENSE-2.0 - *

- * 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.staticanalysis; - -import org.junit.jupiter.api.Test; -import org.openrewrite.DocumentExample; -import org.openrewrite.test.RecipeSpec; -import org.openrewrite.test.RewriteTest; - -import static org.openrewrite.java.Assertions.java; -import static org.openrewrite.java.Assertions.javaVersion; - -class AddSerialAnnotationToSerialVersionUIDTest implements RewriteTest { - @Override - public void defaults(RecipeSpec spec) { - spec.recipe(new AddSerialAnnotationToSerialVersionUID()) - .allSources(sourceSpec -> sourceSpec.markers(javaVersion(17))); - } - - @DocumentExample - @Test - void addSerialAnnotation() { - rewriteRun( - //language=java - java( - """ - import java.io.Serializable; - - class Example implements Serializable { - private static final long serialVersionUID = 1L; - } - """, - """ - import java.io.Serial; - import java.io.Serializable; - - class Example implements Serializable { - @Serial - private static final long serialVersionUID = 1L; - } - """ - ) - ); - } - - @Test - void shouldAddToNewFieldWhenChained() { - rewriteRun( - spec -> spec.recipes( - new AddSerialVersionUidToSerializable(), - new AddSerialAnnotationToSerialVersionUID()), - //language=java - java( - """ - import java.io.Serializable; - - class Example implements Serializable { - } - """, - """ - import java.io.Serial; - import java.io.Serializable; - - class Example implements Serializable { - @Serial - private static final long serialVersionUID = 1; - } - """ - ) - ); - } - - @Test - void shouldNoopIfAlreadyPresent() { - rewriteRun( - //language=java - java( - """ - import java.io.Serializable; - import java.io.Serial; - - class Example implements Serializable { - String var1 = "first variable"; - @Serial - private static final long serialVersionUID = 1L; - int var3 = 666; - } - """ - ) - ); - } - - @Test - void shouldNotAnnotateNonSerializableClass() { - rewriteRun( - //language=java - java( - """ - class Example { - private static final long serialVersionUID = 1L; - } - """ - ) - ); - } - - @Test - void shouldNotAnnotateOnJava11() { - rewriteRun( - //language=java - java( - """ - import java.io.Serializable; - - class Example implements Serializable { - private static final long serialVersionUID = 1L; - } - """, - spec -> spec.markers(javaVersion(11)) - ) - ); - } - - @Test - void shouldNotAnnotateOtherFields() { - rewriteRun( - //language=java - java( - """ - import java.io.Serializable; - - class Example implements Serializable { - static final long serialVersionUID = 1L; - private final long serialVersionUID = 1L; - private static long serialVersionUID = 1L; - private static final int serialVersionUID = 1L; - private static final long foo = 1L; - - void doSomething() { - long serialVersionUID = 1L; - } - } - """ - ) - ); - } - - @Test - void shouldAnnotatedFieldsInInnerClasses() { - rewriteRun( - //language=java - java( - """ - import java.io.Serializable; - - class Outer implements Serializable { - private static final long serialVersionUID = 1; - static class Inner implements Serializable { - private static final long serialVersionUID = 1; - } - } - """, - """ - import java.io.Serial; - import java.io.Serializable; - - class Outer implements Serializable { - @Serial - private static final long serialVersionUID = 1; - static class Inner implements Serializable { - @Serial - private static final long serialVersionUID = 1; - } - } - """ - ) - ); - } -} diff --git a/headless-services/commons/commons-rewrite/src/test/java/org/openrewrite/staticanalysis/AddSerialVersionUidToSerializableTest.java b/headless-services/commons/commons-rewrite/src/test/java/org/openrewrite/staticanalysis/AddSerialVersionUidToSerializableTest.java deleted file mode 100644 index 696f002486..0000000000 --- a/headless-services/commons/commons-rewrite/src/test/java/org/openrewrite/staticanalysis/AddSerialVersionUidToSerializableTest.java +++ /dev/null @@ -1,275 +0,0 @@ -/* - * Copyright 2021 the original author or authors. - *

- * 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 - *

- * https://www.apache.org/licenses/LICENSE-2.0 - *

- * 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.staticanalysis; - -import org.junit.jupiter.api.Test; -import org.openrewrite.DocumentExample; -import org.openrewrite.test.RecipeSpec; -import org.openrewrite.test.RewriteTest; - -import static org.openrewrite.java.Assertions.java; - -@SuppressWarnings("MissingSerialAnnotation") -class AddSerialVersionUidToSerializableTest implements RewriteTest { - - @Override - public void defaults(RecipeSpec spec) { - spec.recipe(new AddSerialVersionUidToSerializable()); - } - - @Test - void doNothingNotSerializable() { - rewriteRun( - //language=java - java( - """ - public class Example { - private String fred; - private int numberOfFreds; - } - """ - ) - ); - } - - @DocumentExample - @Test - void addSerialVersionUID() { - rewriteRun( - //language=java - java( - """ - import java.io.Serializable; - - public class Example implements Serializable { - private String fred; - private int numberOfFreds; - } - """, - """ - import java.io.Serializable; - - public class Example implements Serializable { - private static final long serialVersionUID = 1; - private String fred; - private int numberOfFreds; - } - """ - ) - ); - } - - @Test - void fixSerialVersionUIDModifiers() { - rewriteRun( - //language=java - java( - """ - import java.io.Serializable; - - public class Example implements Serializable { - private final long serialVersionUID = 1; - private String fred; - private int numberOfFreds; - } - """, - """ - import java.io.Serializable; - - public class Example implements Serializable { - private static final long serialVersionUID = 1; - private String fred; - private int numberOfFreds; - } - """ - ) - ); - } - - @Test - void fixSerialVersionUIDNoModifiers() { - rewriteRun( - //language=java - java( - """ - import java.io.Serializable; - - public class Example implements Serializable { - long serialVersionUID = 1; - private String fred; - private int numberOfFreds; - } - """, - """ - import java.io.Serializable; - - public class Example implements Serializable { - private static final long serialVersionUID = 1; - private String fred; - private int numberOfFreds; - } - """ - ) - ); - } - - @Test - void fixSerialVersionUIDNoModifiersWrongType() { - rewriteRun( - //language=java - java( - """ - import java.io.Serializable; - - public class Example implements Serializable { - Long serialVersionUID = 1L; - private String fred; - private int numberOfFreds; - } - """, - """ - import java.io.Serializable; - - public class Example implements Serializable { - private static final long serialVersionUID = 1L; - private String fred; - private int numberOfFreds; - } - """ - ) - ); - } - - @Test - void uidAlreadyPresent() { - rewriteRun( - //language=java - java( - """ - import java.io.Serializable; - - public class Example implements Serializable { - private static final long serialVersionUID = 1; - private String fred; - private int numberOfFreds; - } - """ - ) - ); - } - - @Test - void methodDeclarationsAreNotVisited() { - rewriteRun( - //language=java - java( - """ - import java.io.Serializable; - - public class Example implements Serializable { - private String fred; - private int numberOfFreds; - void doSomething() { - int serialVersionUID = 1; - } - } - """, - """ - import java.io.Serializable; - - public class Example implements Serializable { - private static final long serialVersionUID = 1; - private String fred; - private int numberOfFreds; - void doSomething() { - int serialVersionUID = 1; - } - } - """ - ) - ); - } - - @Test - void doNotAlterAnInterface() { - rewriteRun( - //language=java - java( - """ - import java.io.Serializable; - - public interface Example extends Serializable { - } - """ - ) - ); - } - - @Test - void doNotAlterAnException() { - rewriteRun( - //language=java - java( - """ - import java.io.Serializable; - - public class MyException extends Exception implements Serializable { - } - """ - ) - ); - } - - @Test - void doNotAlterARuntimeException() { - rewriteRun( - //language=java - java( - """ - import java.io.Serializable; - - public class MyException extends RuntimeException implements Serializable { - } - """ - ) - ); - } - - @Test - void serializableInnerClass() { - rewriteRun( - //language=java - java( - """ - import java.io.Serializable; - public class Outer implements Serializable { - public static class Inner implements Serializable { - } - } - """, - """ - import java.io.Serializable; - public class Outer implements Serializable { - private static final long serialVersionUID = 1; - public static class Inner implements Serializable { - private static final long serialVersionUID = 1; - } - } - """ - ) - ); - } -} diff --git a/headless-services/commons/commons-rewrite/src/test/java/org/openrewrite/staticanalysis/ChainStringBuilderAppendCallsTest.java b/headless-services/commons/commons-rewrite/src/test/java/org/openrewrite/staticanalysis/ChainStringBuilderAppendCallsTest.java deleted file mode 100644 index a57b908df9..0000000000 --- a/headless-services/commons/commons-rewrite/src/test/java/org/openrewrite/staticanalysis/ChainStringBuilderAppendCallsTest.java +++ /dev/null @@ -1,313 +0,0 @@ -/* - * Copyright 2023 the original author or authors. - *

- * 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 - *

- * https://www.apache.org/licenses/LICENSE-2.0 - *

- * 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.staticanalysis; - -import org.junit.jupiter.api.Test; -import org.openrewrite.DocumentExample; -import org.openrewrite.test.RecipeSpec; -import org.openrewrite.test.RewriteTest; - -import static org.openrewrite.java.Assertions.java; - -@SuppressWarnings({"StringConcatenationInsideStringBufferAppend", "StringBufferReplaceableByString"}) -class ChainStringBuilderAppendCallsTest implements RewriteTest { - @Override - public void defaults(RecipeSpec spec) { - spec.recipe(new ChainStringBuilderAppendCalls()); - } - - @DocumentExample(value = "Chain `StringBuilder.append()` calls instead of the '+' operator to efficiently concatenate strings and numbers.") - @Test - void objectsConcatenation() { - rewriteRun( - //language=java - java( - """ - class A { - void method1() { - StringBuilder sb = new StringBuilder(); - String op = "+"; - sb.append("A" + op + "B"); - sb.append(1 + op + 2); - } - } - """, - """ - class A { - void method1() { - StringBuilder sb = new StringBuilder(); - String op = "+"; - sb.append("A").append(op).append("B"); - sb.append(1).append(op).append(2); - } - } - """ - ) - ); - } - - @Test - void literalConcatenationIgnored() { - rewriteRun( - //language=java - java( - """ - class A { - void method1() { - StringBuilder sb = new StringBuilder(); - sb.append("A" + "B" + "C"); - } - } - """ - ) - ); - } - - @DocumentExample("Grouping concatenation.") - @Test - void groupedStringsConcatenation() { - rewriteRun( - //language=java - java( - """ - class A { - void method1() { - StringBuilder sb = new StringBuilder(); - String op = "+"; - sb.append("A" + "B" + "C" + op + "D" + "E"); - } - } - """, - """ - class A { - void method1() { - StringBuilder sb = new StringBuilder(); - String op = "+"; - sb.append("A" + "B" + "C").append(op).append("D" + "E"); - } - } - """ - ) - ); - } - - @Test - void unWrap() { - rewriteRun( - //language=java - java( - """ - class A { - void method1() { - StringBuilder sb = new StringBuilder(); - String op = "+"; - sb.append(("A" + op + "B")); - } - } - """, - """ - class A { - void method1() { - StringBuilder sb = new StringBuilder(); - String op = "+"; - sb.append("A").append(op).append("B"); - } - } - """ - ) - ); - } - - @Test - void chainedAppend() { - rewriteRun( - //language=java - java( - """ - class A { - void method1() { - StringBuilder sb = new StringBuilder(); - String op = "+"; - sb.append(("A" + op)).append("B"); - } - } - """, - """ - class A { - void method1() { - StringBuilder sb = new StringBuilder(); - String op = "+"; - sb.append("A").append(op).append("B"); - } - } - """ - ) - ); - } - - @Test - void runMultipleTimes() { - rewriteRun( - //language=java - java( - """ - class A { - void method1() { - StringBuilder sb = new StringBuilder(); - String op = "+"; - sb.append(("A" + op) + "B"); - } - } - """, - """ - class A { - void method1() { - StringBuilder sb = new StringBuilder(); - String op = "+"; - sb.append("A").append(op).append("B"); - } - } - """ - ) - ); - } - - @Test - void correctlyGroupConcatenations() { - rewriteRun( - //language=java - java( - """ - class A { - void method1() { - StringBuilder sb = new StringBuilder(); - String op = "+"; - sb.append(op + 1 + 2 + "A" + "B" + 'x'); - sb.append(1 + 2 + op + 3 + 4); - sb.append(1 + 2 + name() + 3 + 4); - sb.append(op + (1 + 2)); - } - String name() { return "name"; } - } - """, - """ - class A { - void method1() { - StringBuilder sb = new StringBuilder(); - String op = "+"; - sb.append(op).append(1).append(2).append("A" + "B").append('x'); - sb.append(1 + 2).append(op).append(3).append(4); - sb.append(1 + 2).append(name()).append(3).append(4); - sb.append(op).append(1 + 2); - } - String name() { return "name"; } - } - """ - ) - ); - } - - @Test - void appendMethods() { - rewriteRun( - //language=java - java( - """ - class A { - void method1() { - StringBuilder sb = new StringBuilder(); - sb.append(str1() + str2() + str3()); - } - - String str1() { return "A"; } - String str2() { return "B"; } - String str3() { return "C"; } - } - """, - """ - class A { - void method1() { - StringBuilder sb = new StringBuilder(); - sb.append(str1()).append(str2()).append(str3()); - } - - String str1() { return "A"; } - String str2() { return "B"; } - String str3() { return "C"; } - } - """ - ) - ); - } - - @Test - void ChainedAppendWithConstructor() { - rewriteRun( - //language=java - java( - """ - class A { - void method1() { - StringBuilder sb = new StringBuilder().append("A" + operator() + "B"); - } - - String operator() { return "+"; } - } - """, - """ - class A { - void method1() { - StringBuilder sb = new StringBuilder().append("A").append(operator()).append("B"); - } - - String operator() { return "+"; } - } - """ - ) - ); - } - - @Test - void methodArgument() { - rewriteRun( - //language=java - java( - """ - class A { - void method1() { - String op = "+"; - print(new StringBuilder().append("A" + op + "C").toString()); - } - - void print(String str) { - } - } - """, - """ - class A { - void method1() { - String op = "+"; - print(new StringBuilder().append("A").append(op).append("C").toString()); - } - - void print(String str) { - } - } - """ - ) - ); - } -} diff --git a/headless-services/commons/commons-rewrite/src/test/java/org/openrewrite/staticanalysis/InstanceOfPatternMatchTest.java b/headless-services/commons/commons-rewrite/src/test/java/org/openrewrite/staticanalysis/InstanceOfPatternMatchTest.java deleted file mode 100644 index 4eaee8b51a..0000000000 --- a/headless-services/commons/commons-rewrite/src/test/java/org/openrewrite/staticanalysis/InstanceOfPatternMatchTest.java +++ /dev/null @@ -1,1296 +0,0 @@ -/* - * Copyright 2021 the original author or authors. - *

- * 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 - *

- * https://www.apache.org/licenses/LICENSE-2.0 - *

- * 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.staticanalysis; - -import org.junit.jupiter.api.Disabled; -import org.junit.jupiter.api.Nested; -import org.junit.jupiter.api.Test; -import org.openrewrite.Issue; -import org.openrewrite.test.RecipeSpec; -import org.openrewrite.test.RewriteTest; - -import static org.openrewrite.java.Assertions.java; -import static org.openrewrite.java.Assertions.version; - -@SuppressWarnings({"RedundantCast", "DataFlowIssue", "ConstantValue", "ImplicitArrayToString", "PatternVariableCanBeUsed", "UnnecessaryLocalVariable", "SizeReplaceableByIsEmpty", "rawtypes", "ResultOfMethodCallIgnored", "ArraysAsListWithZeroOrOneArgument", "DuplicateCondition"}) -class InstanceOfPatternMatchTest implements RewriteTest { - - @Override - public void defaults(RecipeSpec spec) { - spec.recipe(new InstanceOfPatternMatch()) - .allSources(sourceSpec -> version(sourceSpec, 17)); - } - - - @Nested - class If { - @Test - void ifConditionWithoutPattern() { - rewriteRun( - //language=java - java( - """ - public class A { - void test(Object o) { - Object s = 1; - if (o instanceof String && ((String) (o)).length() > 0) { - if (((String) o).length() > 1) { - System.out.println(o); - } - } - } - } - """, - """ - public class A { - void test(Object o) { - Object s = 1; - if (o instanceof String string && string.length() > 0) { - if (string.length() > 1) { - System.out.println(o); - } - } - } - } - """ - ) - ); - } - - @Test - void multipleCasts() { - rewriteRun( - //language=java - java( - """ - public class A { - void test(Object o, Object o2) { - Object string = 1; - if (o instanceof String && o2 instanceof Integer) { - System.out.println((String) o); - System.out.println((Integer) o2); - } - } - } - """, - """ - public class A { - void test(Object o, Object o2) { - Object string = 1; - if (o instanceof String string1 && o2 instanceof Integer integer) { - System.out.println(string1); - System.out.println(integer); - } - } - } - """ - ) - ); - } - - @Test - void longNames() { - rewriteRun( - //language=java - java( - """ - import java.util.ArrayList; - public class A { - void test(Object o) { - Object list = 1; - if (o instanceof ArrayList) { - System.out.println((ArrayList) o); - } - } - } - """, - """ - import java.util.ArrayList; - public class A { - void test(Object o) { - Object list = 1; - if (o instanceof ArrayList arrayList) { - System.out.println(arrayList); - } - } - } - """ - ) - ); - } - - @Test - void typeParameters_1() { - rewriteRun( - //language=java - java( - """ - import java.util.Collections; - import java.util.List; - import java.util.Map; - import java.util.stream.Collectors; - import java.util.stream.Stream; - public class A { - @SuppressWarnings("unchecked") - public static Stream> applyRoutesType(Object routes) { - if (routes instanceof List) { - List routesList = (List) routes; - if (routesList.isEmpty()) { - return Stream.empty(); - } - if (routesList.stream() - .anyMatch(route -> !(route instanceof Map))) { - return Stream.empty(); - } - return routesList.stream() - .map(route -> (Map) route); - } - return Stream.empty(); - } - } - """ - ) - ); - } - - @Test - void typeParameters_2() { - rewriteRun( - //language=java - java( - """ - import java.util.Collections; - import java.util.List; - import java.util.Map; - import java.util.stream.Collectors; - public class A { - public static List> applyRoutesType(Object routes) { - if (routes instanceof List) { - List routesList = (List) routes; - if (routesList.isEmpty()) { - return Collections.emptyList(); - } - } - return Collections.emptyList(); - } - } - """, - """ - import java.util.Collections; - import java.util.List; - import java.util.Map; - import java.util.stream.Collectors; - public class A { - public static List> applyRoutesType(Object routes) { - if (routes instanceof List routesList) { - if (routesList.isEmpty()) { - return Collections.emptyList(); - } - } - return Collections.emptyList(); - } - } - """ - ) - ); - } - - @Test - void typeParameters_3() { - rewriteRun( - //language=java - java( - """ - import java.util.Collections; - import java.util.List; - public class A { - @SuppressWarnings("unchecked") - public static void applyRoutesType(Object routes) { - if (routes instanceof List) { - List routesList = (List) routes; - String.join(",", (List) routes); - } - } - } - """, - """ - import java.util.Collections; - import java.util.List; - public class A { - @SuppressWarnings("unchecked") - public static void applyRoutesType(Object routes) { - if (routes instanceof List list) { - List routesList = (List) routes; - String.join(",", list); - } - } - } - """ - ) - ); - } - - @Test - void typeParameters_4() { - rewriteRun( - //language=java - java( - """ - import java.util.Collections; - import java.util.List; - public class A { - @SuppressWarnings("unchecked") - public static void applyRoutesType(Object routes) { - if (routes instanceof List) { - String.join(",", (List) routes); - } - } - } - """, """ - import java.util.Collections; - import java.util.List; - public class A { - @SuppressWarnings("unchecked") - public static void applyRoutesType(Object routes) { - if (routes instanceof List list) { - String.join(",", list); - } - } - } - """ - ) - ); - } - - @Test - void typeParameters_5() { - rewriteRun( - //language=java - java( - """ - import java.util.Arrays; - import java.util.Collection; - import java.util.List; - public class A { - @SuppressWarnings("unchecked") - private Collection addValueToList(List previousValues, Object value) { - if (previousValues == null) { - return (value instanceof Collection) ? (Collection) value : Arrays.asList(value); - } - return List.of(); - } - } - """ - ) - ); - } - - @Test - void typeParameters_6() { - rewriteRun( - //language=java - java( - """ - import java.util.Collections; - import java.util.List; - import java.util.Map; - import java.util.stream.Collectors; - public class A { - @SuppressWarnings("unchecked") - public static List> applyRoutesType(Object routes) { - if (routes instanceof List) { - List routesList = (List) routes; - if (routesList.isEmpty()) { - return Collections.emptyList(); - } - if (routesList.stream() - .anyMatch(route -> !(route instanceof Map))) { - return Collections.emptyList(); - } - return routesList.stream() - .map(route -> (Map) route) - .collect(Collectors.toList()); - } - return Collections.emptyList(); - } - } - """ - ) - ); - } - - @Test - void typeParameters_7() { - rewriteRun( - //language=java - java( - """ - import java.util.Collections; - import java.util.List; - import java.util.Map; - import java.util.stream.Collectors; - public class A { - @SuppressWarnings("unchecked") - public static List> applyRoutesType(Object routes) { - if (routes instanceof List) { - return ((List) routes).stream() - .map(route -> (Map) route) - .collect(Collectors.toList()); - } - return Collections.emptyList(); - } - } - """, """ - import java.util.Collections; - import java.util.List; - import java.util.Map; - import java.util.stream.Collectors; - public class A { - @SuppressWarnings("unchecked") - public static List> applyRoutesType(Object routes) { - if (routes instanceof List list) { - return list.stream() - .map(route -> (Map) route) - .collect(Collectors.toList()); - } - return Collections.emptyList(); - } - } - """ - ) - ); - } - - @Test - void typeParameters_8() { - rewriteRun( - //language=java - java( - """ - import java.util.Arrays; - import java.util.Collection; - import java.util.List; - public class A { - @SuppressWarnings("unchecked") - private Collection addValueToList(List previousValues, Object value) { - Collection cl = List.of(); - if (previousValues == null) { - if (value instanceof Collection) { - cl = (Collection) value; - } else { - cl = Arrays.asList(value.toString()); - } - } - return cl; - } - } - """ - ) - ); - } - - @Test - void primitiveArray() { - rewriteRun( - //language=java - java( - """ - public class A { - void test(Object o) { - if (o instanceof int[]) { - System.out.println((int[]) o); - } - } - } - """, - """ - public class A { - void test(Object o) { - if (o instanceof int[] ints) { - System.out.println(ints); - } - } - } - """ - ) - ); - } - - @Test - void matchingVariableInBody() { - rewriteRun( - //language=java - java( - """ - public class A { - void test(Object o) { - if (o instanceof String) { - String str = (String) o; - String str2 = (String) o; - System.out.println(str + str2); - } - } - } - """, - """ - public class A { - void test(Object o) { - if (o instanceof String str) { - String str2 = str; - System.out.println(str + str2); - } - } - } - """ - ) - ); - } - - @Test - void conflictingVariableInBody() { - rewriteRun( - //language=java - java( - """ - public class A { - void test(Object o) { - if (o instanceof String) { - String string = "x"; - System.out.println((String) o); - // String string1 = "y"; - } - } - } - """, - """ - public class A { - void test(Object o) { - if (o instanceof String string1) { - String string = "x"; - System.out.println(string1); - // String string1 = "y"; - } - } - } - """ - ) - ); - } - - @Test - void conflictingVariableOfNestedType() { - rewriteRun( - //language=java - java( - """ - import java.util.Map; - - public class A { - void test(Object o) { - Map.Entry entry = null; - if (o instanceof Map.Entry) { - entry = (Map.Entry) o; - } - System.out.println(entry); - } - } - """, - """ - import java.util.Map; - - public class A { - void test(Object o) { - Map.Entry entry = null; - if (o instanceof Map.Entry entry1) { - entry = entry1; - } - System.out.println(entry); - } - } - """ - ) - ); - } - - @Issue("https://github.com/openrewrite/rewrite/issues/2787") - @Disabled - @Test - void nestedPotentiallyConflictingIfs() { - rewriteRun( - //language=java - java( - """ - public class A { - void test(Object o) { - if (o instanceof String) { - if (o instanceof String) { - System.out.println((String) o); - } - System.out.println((String) o); - } - } - } - """, - """ - public class A { - void test(Object o) { - if (o instanceof String string) { - if (o instanceof String string1) { - System.out.println(string1); - } - System.out.println(string); - } - } - } - """ - ) - ); - } - - @Test - void expressionWithSideEffects() { - rewriteRun( - //language=java - java( - """ - public class A { - void test(Object o) { - Object s = 1; - if (convert(o) instanceof String && ((String) convert(o)).length() > 0) { - if (((String) convert(o)).length() > 1) { - System.out.println(o); - } - } - } - Object convert(Object o) { - return o; - } - } - """ - ) - ); - } - - @Test - void noTypeCast() { - rewriteRun( - //language=java - java( - """ - public class A { - void test(Object o) { - if (o instanceof String) { - System.out.println(o); - } - } - } - """ - ) - ); - } - - @Test - void typeCastInElse() { - rewriteRun( - //language=java - java( - """ - public class A { - void test(Object o) { - if (o instanceof String) { - System.out.println(o); - } else { - System.out.println((String) o); - } - } - } - """ - ) - ); - } - - @Test - void ifConditionWithPattern() { - rewriteRun( - //language=java - java( - """ - public class A { - void test(Object o) { - if (o instanceof String s && s.length() > 0) { - System.out.println(s); - } - } - } - """ - ) - ); - } - - @Test - void orOperationInIfCondition() { - rewriteRun( - //language=java - java( - """ - public class A { - void test(Object o) { - if (o instanceof String || ((String) o).length() > 0) { - if (((String) o).length() > 1) { - System.out.println(o); - } - } - } - } - """ - ) - ); - } - - @Test - void negatedInstanceOfMatchedInElse() { - rewriteRun( - //language=java - java( - """ - public class A { - void test(Object o) { - if (!(o instanceof String)) { - System.out.println(((String) o).length()); - } else { - System.out.println(((String) o).length()); - } - } - } - """ - ) - ); - } - - @Test - @Issue("https://github.com/openrewrite/rewrite-static-analysis/issues/174") - void ifTwoDifferentInstanceOf() { - rewriteRun( - version( - //language=java - java( - """ - class A { - int combinedLength(Object o, Object o2) { - if (o instanceof String && o2 instanceof String) { - return ((String) o).length() + ((String) o2).length(); - } - return -1; - } - } - """, - """ - class A { - int combinedLength(Object o, Object o2) { - if (o instanceof String string && o2 instanceof String string1) { - return string.length() + string1.length(); - } - return -1; - } - } - """ - ), 17 - ) - ); - } - - @Test - @Issue("https://github.com/openrewrite/rewrite-static-analysis/issues/174") - void ifTwoDifferentInstanceOfWithParentheses() { - rewriteRun( - version( - //language=java - java( - """ - class A { - int combinedLength(Object o, Object o2) { - if (o instanceof String && (o2 instanceof String)) { - return ((String) o).length() + ((String) o2).length(); - } - return -1; - } - } - """, - """ - class A { - int combinedLength(Object o, Object o2) { - if (o instanceof String string && (o2 instanceof String string1)) { - return string.length() + string1.length(); - } - return -1; - } - } - """ - ), 17 - ) - ); - } - } - - @SuppressWarnings({"CastCanBeRemovedNarrowingVariableType", "ClassInitializerMayBeStatic"}) - @Nested - class Ternary { - - @Test - void typeCastInTrue() { - rewriteRun( - //language=java - java( - """ - public class A { - String test(Object o) { - return o instanceof String ? ((String) o).substring(1) : o.toString(); - } - } - """, - """ - public class A { - String test(Object o) { - return o instanceof String s ? s.substring(1) : o.toString(); - } - } - """ - ) - ); - } - - @Test - void multipleVariablesOnlyOneUsed() { - rewriteRun( - //language=java - java( - """ - public class A { - String test(Object o1, Object o2) { - return o1 instanceof String && o2 instanceof Number - ? ((String) o1).substring(1) : o1.toString(); - } - } - """, - """ - public class A { - String test(Object o1, Object o2) { - return o1 instanceof String s && o2 instanceof Number - ? s.substring(1) : o1.toString(); - } - } - """ - ) - ); - } - - @Test - void initBlocks() { - rewriteRun( - //language=java - java( - """ - public class A { - static { - Object o = null; - String s = o instanceof String ? ((String) o).substring(1) : String.valueOf(o); - } - { - Object o = null; - String s = o instanceof String ? ((String) o).substring(1) : String.valueOf(o); - } - } - """, - """ - public class A { - static { - Object o = null; - String s = o instanceof String s1 ? s1.substring(1) : String.valueOf(o); - } - { - Object o = null; - String s = o instanceof String s1 ? s1.substring(1) : String.valueOf(o); - } - } - """ - ) - ); - } - - @Test - void typeCastInFalse() { - rewriteRun( - //language=java - java( - """ - public class A { - String test(Object o) { - return o instanceof String ? o.toString() : ((String) o).substring(1); - } - } - """ - ) - ); - } - - @Test - @Issue("https://github.com/openrewrite/rewrite-static-analysis/pull/265") - void multipleCastsInDifferentOperands() { - rewriteRun( - //language=java - java( - """ - import java.util.Comparator; - public class A { - Comparator comparator() { - return (a, b) -> - a instanceof String && b instanceof String ? ((String) a).compareTo((String) b) : 0; - } - } - """, - """ - import java.util.Comparator; - public class A { - Comparator comparator() { - return (a, b) -> - a instanceof String s && b instanceof String s1 ? s.compareTo(s1) : 0; - } - } - """ - ) - ); - } - } - - @Nested - class Binary { - - @Test - void onlyReplacementsBeforeOrOperator() { - rewriteRun( - //language=java - java( - """ - public class A { - boolean test(Object o) { - return o instanceof String && ((String) o).length() > 1 || ((String) o).length() > 2; - } - } - """, - """ - public class A { - boolean test(Object o) { - return o instanceof String s && s.length() > 1 || ((String) o).length() > 2; - } - } - """ - ) - ); - } - - @Test - void methodCallBreaksFlowScope() { - rewriteRun( - //language=java - java( - """ - public class A { - boolean m(Object o) { - return test(o instanceof String) && ((String) o).length() > 1; - } - boolean test(boolean b) { - return b; - } - } - """ - ) - ); - } - } - - @Nested - class Arrays { - - @Test - void string() { - rewriteRun( - //language=java - java( - """ - public class A { - boolean test(Object o) { - return o instanceof String[] && ((java.lang.String[]) o).length > 1 || ((String[]) o).length > 2; - } - } - """, - """ - public class A { - boolean test(Object o) { - return o instanceof String[] ss && ss.length > 1 || ((String[]) o).length > 2; - } - } - """ - ) - ); - } - - @Test - void primitive() { - rewriteRun( - //language=java - java( - """ - public class A { - boolean test(Object o) { - return o instanceof int[] && ((int[]) o).length > 1 || ((int[]) o).length > 2; - } - } - """, - """ - public class A { - boolean test(Object o) { - return o instanceof int[] is && is.length > 1 || ((int[]) o).length > 2; - } - } - """ - ) - ); - } - - @Test - void multiDimensional() { - rewriteRun( - //language=java - java( - """ - public class A { - boolean test(Object o) { - return o instanceof int[][] && ((int[][]) o).length > 1 || ((int[][]) o).length > 2; - } - } - """, - """ - public class A { - boolean test(Object o) { - return o instanceof int[][] is && is.length > 1 || ((int[][]) o).length > 2; - } - } - """ - ) - ); - } - - @Test - void dimensionalMismatch() { - rewriteRun( - //language=java - java( - """ - public class A { - boolean test(Object o) { - return o instanceof int[][] && ((int[]) o).length > 1; - } - } - """ - ) - ); - } - } - - @SuppressWarnings("unchecked") - @Nested - class Generics { - @Test - void wildcardInstanceOf() { - rewriteRun( - //language=java - java( - """ - import java.util.List; - public class A { - Object test(Object o) { - if (o instanceof List) { - return ((List) o).get(0); - } - return o.toString(); - } - } - """, - """ - import java.util.List; - public class A { - Object test(Object o) { - if (o instanceof List list) { - return list.get(0); - } - return o.toString(); - } - } - """ - ) - ); - } - - @Test - void rawInstanceOfAndWildcardParameterizedCast() { - rewriteRun( - //language=java - java( - """ - import java.util.List; - public class A { - Object test(Object o) { - return o instanceof List ? ((List) o).get(0) : o.toString(); - } - } - """, - """ - import java.util.List; - public class A { - Object test(Object o) { - return o instanceof List l ? l.get(0) : o.toString(); - } - } - """ - ) - ); - } - - @Test - void rawInstanceOfAndObjectParameterizedCast() { - rewriteRun( - //language=java - java( - """ - import java.util.List; - public class A { - Object test(Object o) { - return o instanceof List ? ((List) o).get(0) : o.toString(); - } - } - """/*, - """ - import java.util.List; - public class A { - Object test(Object o) { - return o instanceof List l ? l.get(0) : o.toString(); - } - } - """*/ - ) - ); - } - - @Test - void rawInstanceOfAndParameterizedCast() { - rewriteRun( - //language=java - java( - """ - import java.util.List; - public class A { - String test(Object o) { - return o instanceof List ? ((List) o).get(0) : o.toString(); - } - } - """ - ) - ); - } - - @Test - void unboundGenericTypeVariable() { - rewriteRun( - //language=java - java( - """ - import java.util.List; - public class A { - void test(Object t) { - if (t instanceof List) { - List l = (List) t; - System.out.println(l.size()); - } - } - } - """ - ) - ); - } - } - - @SuppressWarnings({"unchecked", "rawtypes"}) - @Nested - class Various { - @Test - void unaryWithoutSideEffects() { - rewriteRun( - //language=java - java( - """ - public class A { - String test(Object o) { - return ((Object) ("1" + ~1)) instanceof String ? ((String) ((Object) ("1" + ~1))).substring(1) : o.toString(); - } - } - """, - """ - public class A { - String test(Object o) { - return ((Object) ("1" + ~1)) instanceof String s ? s.substring(1) : o.toString(); - } - } - """ - ) - ); - } - - @Test - void nestedClasses() { - rewriteRun( - //language=java - java( - """ - public class A { - public static class Else {} - String test(Object o) { - if (o instanceof Else) { - return ((Else) o).toString(); - } - return o.toString(); - } - } - """, - """ - public class A { - public static class Else {} - String test(Object o) { - if (o instanceof Else else1) { - return else1.toString(); - } - return o.toString(); - } - } - """ - ) - ); - } - - @Test - void iterableParameter() { - rewriteRun( - //language=java - java( - """ - import java.util.HashMap; - import java.util.List; - import java.util.Map; - - public class ApplicationSecurityGroupsParameterHelper { - static final String APPLICATION_SECURITY_GROUPS = "application-security-groups"; - public Map transformGatewayParameters(Map parameters) { - Map environment = new HashMap<>(); - Object applicationSecurityGroups = parameters.get(APPLICATION_SECURITY_GROUPS); - if (applicationSecurityGroups instanceof List) { - environment.put(APPLICATION_SECURITY_GROUPS, String.join(",", (List) applicationSecurityGroups)); - } - return environment; - } - } - """, - """ - import java.util.HashMap; - import java.util.List; - import java.util.Map; - - public class ApplicationSecurityGroupsParameterHelper { - static final String APPLICATION_SECURITY_GROUPS = "application-security-groups"; - public Map transformGatewayParameters(Map parameters) { - Map environment = new HashMap<>(); - Object applicationSecurityGroups = parameters.get(APPLICATION_SECURITY_GROUPS); - if (applicationSecurityGroups instanceof List list) { - environment.put(APPLICATION_SECURITY_GROUPS, String.join(",", list)); - } - return environment; - } - } - """ - ) - ); - } - } - - @Nested - class Throws { - @Test - void throwsException() { - rewriteRun( - //language=java - java( - """ - class A { - void test(Throwable t) { - if (t instanceof RuntimeException) { - throw (RuntimeException) t; - } - } - } - """, - """ - class A { - void test(Throwable t) { - if (t instanceof RuntimeException exception) { - throw exception; - } - } - } - """ - ) - ); - } - - @Test - @Issue("https://github.com/openrewrite/rewrite-static-analysis/issues/307") - void throwsExceptionWithExtraParentheses() { - rewriteRun( - //language=java - java( - """ - class A { - void test(Throwable t) { - if (t instanceof Exception) { - // Extra parentheses trips up the replacement - throw ((Exception) t); - } - } - } - """, - """ - class A { - void test(Throwable t) { - if (t instanceof Exception exception) { - // Extra parentheses trips up the replacement - throw exception; - } - } - } - """ - ) - ); - } - - } -} diff --git a/headless-services/commons/commons-rewrite/src/test/java/org/openrewrite/staticanalysis/RemoveUnneededBlockTest.java b/headless-services/commons/commons-rewrite/src/test/java/org/openrewrite/staticanalysis/RemoveUnneededBlockTest.java deleted file mode 100644 index 88cdef6e32..0000000000 --- a/headless-services/commons/commons-rewrite/src/test/java/org/openrewrite/staticanalysis/RemoveUnneededBlockTest.java +++ /dev/null @@ -1,568 +0,0 @@ -/* - * Copyright 2022 the original author or authors. - *

- * 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 - *

- * https://www.apache.org/licenses/LICENSE-2.0 - *

- * 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.staticanalysis; - -import org.junit.jupiter.api.Test; -import org.openrewrite.DocumentExample; -import org.openrewrite.Issue; -import org.openrewrite.test.RecipeSpec; -import org.openrewrite.test.RewriteTest; - -import static org.openrewrite.java.Assertions.java; - -@SuppressWarnings({"UnusedLabel", "StatementWithEmptyBody", "Convert2Diamond", "ConstantConditions", "ClassInitializerMayBeStatic"}) -class RemoveUnneededBlockTest implements RewriteTest { - @Override - public void defaults(RecipeSpec spec) { - spec.recipe(new RemoveUnneededBlock()); - } - - @Test - void doNotChangeMethod() { - rewriteRun( - //language=java - java( - """ - class A { - void test() { - } - } - """ - ) - ); - } - - @Test - void doNotChangeLabeledBlock() { - rewriteRun( - //language=java - java( - """ - class A { - void test() { - testLabel: { - System.out.println("hello!"); - } - } - } - """ - ) - ); - } - - @Test - void doNotChangeEmptyIfBlock() { - rewriteRun( - //language=java - java( - """ - class A { - void test() { - if(true) { } - } - } - """ - ) - ); - } - - @Test - void doNotRemoveDoubleBraceInitBlocksInMethod() { - rewriteRun( - //language=java - java( - """ - import java.util.HashSet; - import java.util.Set; - public class T { - public void whenInitializeSetWithDoubleBraces_containsElements() { - Set countries = new HashSet() { - { - add("a"); - add("b"); - } - }; - } - } - """ - ) - ); - } - - @Test - void doNotRemoveDoubleBraceInitBlocks() { - rewriteRun( - //language=java - java( - """ - import java.util.HashSet; - import java.util.Set; - public class T { - final Set countries = new HashSet() { - { - add("a"); - add("b"); - } - }; - } - """ - ) - ); - } - - @Test - void doNotRemoveObjectArrayInitializer() { - rewriteRun( - //language=java - java( - """ - public class A { - Object[] a = new Object[] { - "a", - "b" - }; - } - """ - ) - ); - } - - @Test - void doNotRemoveObjectArrayArrayInitializer() { - rewriteRun( - //language=java - java( - """ - public class A { - Object[][] a = new Object[][] { - { "a", "b" }, - { "c", "d" } - }; - } - """ - ) - ); - } - - @DocumentExample - @Test - void simplifyNestedBlock() { - rewriteRun( - //language=java - java( - """ - public class A { - void test() { - { - System.out.println("hello!"); - } - } - } - """, - """ - public class A { - void test() { - System.out.println("hello!"); - } - } - """ - ) - ); - } - - @Test - void simplifyDoublyNestedBlock() { - rewriteRun( - //language=java - java( - """ - public class A { - void test() { - { - { System.out.println("hello!"); } - } - } - } - """, - """ - public class A { - void test() { - System.out.println("hello!"); - } - } - """ - ) - ); - } - - @Test - void simplifyBlockNestedInIfBlock() { - rewriteRun( - //language=java - java( - """ - public class A { - void test() { - if (true) { - { System.out.println("hello!"); } - } - } - } - """, - """ - public class A { - void test() { - if (true) { - System.out.println("hello!"); - } - } - } - """ - ) - ); - } - - @Test - void simplifyBlockInStaticInitializerIfBlock() { - rewriteRun( - //language=java - java( - """ - public class A { - static { - { - { - System.out.println("hello static!"); - System.out.println("goodbye static!"); - } - } - } - - { - { - System.out.println("hello init!"); - System.out.println("goodbye init!"); - } - } - } - """, - """ - public class A { - static { - System.out.println("hello static!"); - System.out.println("goodbye static!"); - } - - { - System.out.println("hello init!"); - System.out.println("goodbye init!"); - } - } - """ - ) - ); - } - - @Test - void simplifyCraziness() { - rewriteRun( - //language=java - java( - """ - import java.util.HashSet; - import java.util.Set; - public class A { - static { - { - new HashSet() { - { - add("a"); - add("b"); - { - System.out.println("hello static!"); - System.out.println("goodbye static!"); - } - } - }; - } - } - - { - { - new HashSet() { - { - add("a"); - add("b"); - { - System.out.println("hello init!"); - System.out.println("goodbye init!"); - } - } - }; - } - } - } - """, - """ - import java.util.HashSet; - import java.util.Set; - public class A { - static { - new HashSet() { - { - add("a"); - add("b"); - System.out.println("hello static!"); - System.out.println("goodbye static!"); - } - }; - } - - { - new HashSet() { - { - add("a"); - add("b"); - System.out.println("hello init!"); - System.out.println("goodbye init!"); - } - }; - } - } - """ - ) - ); - } - - @Test - void simplifyDoesNotFormatSurroundingCode() { - rewriteRun( - //language=java - java( - """ - public class A { - static int[] a; - static int[] b; - static { - a = new int[] { 1, 2, 3 }; - b = new int[] {4,5,6}; - { - System.out.println("hello static!"); - } - } - } - """, - """ - public class A { - static int[] a; - static int[] b; - static { - a = new int[] { 1, 2, 3 }; - b = new int[] {4,5,6}; - System.out.println("hello static!"); - } - } - """ - ) - ); - } - - @Test - void simplifyDoesNotFormatInternalCode() { - rewriteRun( - //language=java - java( - """ - public class A { - int[] a; - int[] b; - static { - a = new int[] { 1, 2, 3 }; - b = new int[] {4,5,6}; - { - System.out.println("hello!"); - System.out.println( "world!" ); - } - } - } - """, - """ - public class A { - int[] a; - int[] b; - static { - a = new int[] { 1, 2, 3 }; - b = new int[] {4,5,6}; - System.out.println("hello!"); - System.out.println("world!"); - } - } - """ - ) - ); - } - - @Test - @Issue("https://github.com/openrewrite/rewrite/issues/3073") - void preserveComments() { - rewriteRun( - //language=java - java( - """ - public class A { - static { - // comment before (outside) - { - // comment before (inside) - System.out.println("hello world!"); - // comment after (inside) - } - // comment after (outside) - } - } - """, - """ - public class A { - static { - // comment before (outside) - // comment before (inside) - System.out.println("hello world!"); - // comment after (inside) - // comment after (outside) - } - } - """ - ) - ); - } - - @Test - void preserveBlocksContainingVariableDeclarations() { - rewriteRun( - //language=java - java( - """ - public class A { - static { - { - int i = 0; - } - System.out.println("hello world!"); - } - } - """ - ) - ); - } - - @Test - void inlineLastBlockContainingVariableDeclarations() { - rewriteRun( - //language=java - java( - """ - public class A { - static { - { - System.out.println("hello world!"); - } - { - int i = 0; - } - } - } - """, - """ - public class A { - static { - System.out.println("hello world!"); - int i = 0; - } - } - """ - ) - ); - } - - @Test - @SuppressWarnings("EmptyFinallyBlock") - void removeEmptyTryFinallyBlock() { - rewriteRun( - //language=java - java( - """ - public class A { - public int foo() { - try { - int i = 1; - } finally { - } - } - } - """, - """ - public class A { - public int foo() { - int i = 1; - } - } - """ - ) - ); - } - - @Test - void keepNonEmptyTryFinallyBlock() { - rewriteRun( - //language=java - java( - """ - public class A { - public int foo() { - try { - int i = 1; - } finally { - System.out.println("hello world!"); - } - } - } - """ - ) - ); - } - - @Test - @SuppressWarnings("EmptyFinallyBlock") - void keepNonEmptyTryFinallyBlock2() { - rewriteRun( - //language=java - java( - """ - public class A { - public int foo() { - try { - int i = 1; - } finally { - } - int i = 1; - } - } - """ - ) - ); - } - -} diff --git a/headless-services/commons/commons-rewrite/src/test/java/org/openrewrite/staticanalysis/ReplaceDeprecatedRuntimeExecMethodsTest.java b/headless-services/commons/commons-rewrite/src/test/java/org/openrewrite/staticanalysis/ReplaceDeprecatedRuntimeExecMethodsTest.java deleted file mode 100644 index 36ed0c3ab9..0000000000 --- a/headless-services/commons/commons-rewrite/src/test/java/org/openrewrite/staticanalysis/ReplaceDeprecatedRuntimeExecMethodsTest.java +++ /dev/null @@ -1,297 +0,0 @@ -/* - * Copyright 2023 the original author or authors. - *

- * 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 - *

- * https://www.apache.org/licenses/LICENSE-2.0 - *

- * 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.staticanalysis; - -import org.junit.jupiter.api.Test; -import org.openrewrite.test.RecipeSpec; -import org.openrewrite.test.RewriteTest; - -import static org.openrewrite.java.Assertions.java; -import static org.openrewrite.java.Assertions.version; - -class ReplaceDeprecatedRuntimeExecMethodsTest implements RewriteTest { - @Override - public void defaults(RecipeSpec spec) { - spec.recipe(new ReplaceDeprecatedRuntimeExecMethods()); - } - - @Test - void rawString() { - rewriteRun( - version( - //language=java - java( - """ - import java.io.File; - import java.io.IOException; - - class A { - void method() throws IOException { - Runtime runtime = Runtime.getRuntime(); - String[] envp = { "E1=1", "E2=2"}; - File dir = new File("/tmp"); - - Process process1 = runtime.exec("ls -a -l"); - Process process2 = runtime.exec("ls -a -l", envp); - Process process3 = runtime.exec("ls -a -l", envp, dir); - } - } - """, - """ - import java.io.File; - import java.io.IOException; - - class A { - void method() throws IOException { - Runtime runtime = Runtime.getRuntime(); - String[] envp = { "E1=1", "E2=2"}; - File dir = new File("/tmp"); - - Process process1 = runtime.exec(new String[]{"ls", "-a", "-l"}); - Process process2 = runtime.exec(new String[]{"ls", "-a", "-l"}, envp); - Process process3 = runtime.exec(new String[]{"ls", "-a", "-l"}, envp, dir); - } - } - """ - ), 18) - ); - } - - @Test - void stringVariableAsInput() { - rewriteRun( - version( - //language=java - java( - """ - import java.io.File; - import java.io.IOException; - - class B { - void method() throws IOException { - Runtime runtime = Runtime.getRuntime(); - String command = "ls -al"; - String[] envp = { "E1=1", "E2=2"}; - File dir = new File("/tmp"); - Process process1 = runtime.exec(command); - Process process2 = runtime.exec(command, envp); - Process process3 = runtime.exec(command, envp, dir); - } - } - """, - """ - import java.io.File; - import java.io.IOException; - - class B { - void method() throws IOException { - Runtime runtime = Runtime.getRuntime(); - String command = "ls -al"; - String[] envp = { "E1=1", "E2=2"}; - File dir = new File("/tmp"); - Process process1 = runtime.exec(command.split(" ")); - Process process2 = runtime.exec(command.split(" "), envp); - Process process3 = runtime.exec(command.split(" "), envp, dir); - } - } - """ - ), 18) - ); - } - - @Test - void methodInvocationAsInput() { - rewriteRun( - version( - //language=java - java( - """ - import java.io.IOException; - - class B { - String command() { - return "ls -al"; - } - void method() throws IOException { - Runtime runtime = Runtime.getRuntime(); - Process process = runtime.exec(command()); - } - } - """, - """ - import java.io.IOException; - - class B { - String command() { - return "ls -al"; - } - void method() throws IOException { - Runtime runtime = Runtime.getRuntime(); - Process process = runtime.exec(command().split(" ")); - } - } - """ - ), 18) - ); - } - - @Test - void concatenatedRawStringsAsInput() { - rewriteRun( - version( - //language=java - java( - """ - import java.io.IOException; - - class A { - void method() throws IOException { - Runtime runtime = Runtime.getRuntime(); - Process process = runtime.exec("ls" + " " + "-a" + " " + "-l"); - } - } - """, - """ - import java.io.IOException; - - class A { - void method() throws IOException { - Runtime runtime = Runtime.getRuntime(); - Process process = runtime.exec(new String[]{"ls", "-a", "-l"}); - } - } - """ - ), 18) - ); - } - - @Test - void concatenatedObjectsAsInput() { - rewriteRun( - version( - //language=java - java( - """ - import java.io.IOException; - - class B { - String options = "-a -l"; - void method() throws IOException { - Runtime runtime = Runtime.getRuntime(); - Process process = runtime.exec("ls" + " " + options); - } - } - """, - """ - import java.io.IOException; - - class B { - String options = "-a -l"; - void method() throws IOException { - Runtime runtime = Runtime.getRuntime(); - Process process = runtime.exec(("ls" + " " + options).split(" ")); - } - } - """ - ), 18) - ); - } - - @Test - void deprecatedMethod2() { - rewriteRun( - version( - //language=java - java( - """ - import java.io.IOException; - - class A { - void method() throws IOException { - Runtime runtime = Runtime.getRuntime(); - String[] envp = { "E1=1", "E2=2"}; - Process process = runtime.exec("ls -a -l", envp); - } - } - """, - """ - import java.io.IOException; - - class A { - void method() throws IOException { - Runtime runtime = Runtime.getRuntime(); - String[] envp = { "E1=1", "E2=2"}; - Process process = runtime.exec(new String[]{"ls", "-a", "-l"}, envp); - } - } - """ - ), 18) - ); - } - - - @Test - void doNotChangeIfUsingNewMethods() { - rewriteRun( - version( - //language=java - java( - """ - import java.io.File; - import java.io.IOException; - - class A { - void method() throws IOException { - Runtime runtime = Runtime.getRuntime(); - String[] envp = { "E1=1", "E2=2"}; - File dir = new File("/tmp"); - - Process process1 = runtime.exec(new String[]{"ls", "-al"}); - Process process2 = runtime.exec(new String[]{"ls", "-al"}, envp); - Process process3 = runtime.exec(new String[]{"ls", "-a", "-l"}, envp, dir); - } - } - """ - ), 18) - ); - } - - @Test - void doNotChangeIfUnderJava18() { - rewriteRun( - version( - //language=java - java( - """ - import java.io.File; - import java.io.IOException; - - class A { - void method() throws IOException { - Runtime runtime = Runtime.getRuntime(); - String[] envp = { "E1=1", "E2=2"}; - File dir = new File("/tmp"); - - Process process1 = runtime.exec("ls -al"); - Process process2 = runtime.exec("ls -al", envp); - Process process3 = runtime.exec("ls -al", envp, dir); - } - } - """ - ), 17) - ); - } -} diff --git a/headless-services/commons/commons-rewrite/src/test/java/org/springframework/ide/vscode/commons/rewrite/LoadUtilsTest.java b/headless-services/commons/commons-rewrite/src/test/java/org/springframework/ide/vscode/commons/rewrite/LoadUtilsTest.java deleted file mode 100644 index 2ba47fd8bb..0000000000 --- a/headless-services/commons/commons-rewrite/src/test/java/org/springframework/ide/vscode/commons/rewrite/LoadUtilsTest.java +++ /dev/null @@ -1,124 +0,0 @@ -/******************************************************************************* - * Copyright (c) 2022, 2023 VMware, Inc. - * All rights reserved. This program and the accompanying materials - * are made available under the terms of the Eclipse Public License v1.0 - * which accompanies this distribution, and is available at - * https://www.eclipse.org/legal/epl-v10.html - * - * Contributors: - * VMware, Inc. - initial API and implementation - *******************************************************************************/ -package org.springframework.ide.vscode.commons.rewrite; - -//import static org.junit.jupiter.api.Assertions.assertEquals; -//import static org.junit.jupiter.api.Assertions.assertNotNull; -//import static org.junit.jupiter.api.Assertions.assertTrue; -// -//import java.time.Duration; -// -//import org.junit.jupiter.api.BeforeAll; -import org.junit.jupiter.api.Disabled; -//import org.junit.jupiter.api.Test; -//import org.openrewrite.Recipe; -//import org.openrewrite.config.DeclarativeRecipe; -//import org.openrewrite.config.Environment; -//import org.openrewrite.config.RecipeDescriptor; -//import org.openrewrite.java.dependencies.UpgradeDependencyVersion; -//import org.springframework.ide.vscode.commons.rewrite.LoadUtils.DurationTypeConverter; -// -//import com.google.gson.Gson; -//import com.google.gson.GsonBuilder; - -@Disabled -public class LoadUtilsTest { - -// private static Environment env; -// -// private static Gson serializationGson = new GsonBuilder() -// .registerTypeAdapter(Duration.class, new DurationTypeConverter()) -// .create(); -// -// @BeforeAll -// public static void setupAll() { -// env = Environment.builder().scanRuntimeClasspath().build(); -// } -// -// @SuppressWarnings("unchecked") -// @Test -// public void createRecipeTest() throws Exception { -// RecipeDescriptor recipeDescriptor = env.listRecipeDescriptors().stream().filter(d -> "org.openrewrite.java.spring.boot3.UpgradeSpringBoot_3_0".equals(d.getName())).findFirst().orElse(null); -// assertNotNull(recipeDescriptor); -// -// Recipe r = LoadUtils.createRecipe(recipeDescriptor, id -> { -// try { -// return (Class) Class.forName(id); -// } catch (ClassNotFoundException e) { -// return null; -// } -// }); -// -// assertTrue(r instanceof DeclarativeRecipe); -// assertEquals("org.openrewrite.java.spring.boot3.UpgradeSpringBoot_3_0", r.getName()); -// assertTrue(r.getDescription().startsWith("Migrate applications to the latest Spring Boot 3.0 release.")); -// assertEquals("Migrate to Spring Boot 3.0", r.getDisplayName()); -// assertTrue(r.getRecipeList().size() >= 12); -// -//// Recipe pomRecipe = r.getRecipeList().get(2); -//// assertTrue(pomRecipe instanceof DeclarativeRecipe); -//// assertEquals("org.openrewrite.java.spring.boot3.MavenPomUpgrade", pomRecipe.getName()); -//// assertEquals("Upgrade Maven POM to Spring Boot 3.0 from prior 2.x version.", pomRecipe.getDescription()); -//// assertEquals("Upgrade Maven POM to Spring Boot 3.0 from 2.x", pomRecipe.getDisplayName()); -//// assertTrue(pomRecipe.getRecipeList().size() >= 3); -// -// UpgradeDependencyVersion upgradeDependencyRecipe = r.getRecipeList().stream().filter(UpgradeDependencyVersion.class::isInstance).map(UpgradeDependencyVersion.class::cast).findFirst().get(); -// assertEquals("org.openrewrite.java.dependencies.UpgradeDependencyVersion", upgradeDependencyRecipe.getName()); -// assertEquals("Upgrade Gradle or Maven dependency versions", upgradeDependencyRecipe.getDisplayName()); -// assertTrue(upgradeDependencyRecipe.getNewVersion().startsWith("3.0.")); -// assertEquals("org.springframework.boot", upgradeDependencyRecipe.getGroupId()); -// assertEquals("*", upgradeDependencyRecipe.getArtifactId()); -// } -// -// @SuppressWarnings("unchecked") -// public void deserializeFromJson() throws Exception { -// Recipe r = env.listRecipes().stream().filter(d -> "org.openrewrite.java.spring.boot3.UpgradeSpringBoot_3_0".equals(d.getName())).findFirst().orElse(null); -// RecipeDescriptor recipeDescriptor = r.getDescriptor(); -// assertNotNull(recipeDescriptor); -// -// String json = serializationGson.toJson(recipeDescriptor); -// RecipeDescriptor deserialized = serializationGson.fromJson(json, RecipeDescriptor.class); -// assertNotNull(deserialized); -// -// r = LoadUtils.createRecipe(deserialized, id -> { -// try { -// return (Class) Class.forName(id); -// } catch (ClassNotFoundException e) { -// return null; -// } -// }); -// -// assertTrue(r instanceof DeclarativeRecipe); -// assertEquals("org.openrewrite.java.spring.boot3.UpgradeSpringBoot_3_0", r.getName()); -// assertEquals( -// "Migrate applications to the latest Spring Boot 3.0 release. This recipe will modify an application's build files, make changes to deprecated/preferred APIs, and migrate configuration settings that have changes between versions. This recipe will also chain additional framework migrations (Spring Framework, Spring Data, etc) that are required as part of the migration to Spring Boot 2.7.\n" -// + "" -// + "", -// r.getDescription()); -// assertEquals("Migrate to Spring Boot 3.0", r.getDisplayName()); -// assertEquals(9, r.getRecipeList().size()); -// -// Recipe pomRecipe = r.getRecipeList().get(0); -// assertTrue(pomRecipe instanceof DeclarativeRecipe); -// assertEquals("org.openrewrite.java.dependencies.UpgradeDependencyVersion", pomRecipe.getName()); -// assertEquals("Upgrade Maven POM to Spring Boot 3.0 from prior 2.x version.", pomRecipe.getDescription()); -// assertEquals("Upgrade Maven POM to Spring Boot 3.0 from 2.x", pomRecipe.getDisplayName()); -// assertTrue(pomRecipe.getRecipeList().size() >= 3); -// -// UpgradeDependencyVersion upgradeDependencyRecipe = pomRecipe.getRecipeList().stream().filter(UpgradeDependencyVersion.class::isInstance).map(UpgradeDependencyVersion.class::cast).findFirst().get(); -// assertEquals("org.openrewrite.maven.UpgradeDependencyVersion", upgradeDependencyRecipe.getName()); -// assertEquals("Upgrade Maven dependency version", upgradeDependencyRecipe.getDisplayName()); -// assertEquals(0, upgradeDependencyRecipe.getRecipeList().size()); -// assertTrue(upgradeDependencyRecipe.getNewVersion().startsWith("3.0.")); -// assertEquals("org.springframework.boot", upgradeDependencyRecipe.getGroupId()); -// assertEquals("*", upgradeDependencyRecipe.getArtifactId()); -// } -} diff --git a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/rewrite/RewriteRecipeRepository.java b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/rewrite/RewriteRecipeRepository.java index 077c4413d9..c55fc39818 100644 --- a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/rewrite/RewriteRecipeRepository.java +++ b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/rewrite/RewriteRecipeRepository.java @@ -11,27 +11,15 @@ package org.springframework.ide.vscode.boot.java.rewrite; import java.io.ByteArrayInputStream; -import java.io.FileInputStream; -import java.net.URL; -import java.net.URLClassLoader; import java.nio.file.Path; import java.nio.file.Paths; -import java.time.Duration; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Optional; -import java.util.Properties; -import java.util.Set; import java.util.UUID; import java.util.concurrent.CompletableFuture; -import java.util.function.Consumer; import java.util.function.Function; -import java.util.function.Predicate; -import java.util.regex.Pattern; import java.util.stream.Collectors; import org.eclipse.lsp4j.ApplyWorkspaceEditParams; @@ -49,11 +37,7 @@ import org.openrewrite.RecipeRun; import org.openrewrite.Result; import org.openrewrite.SourceFile; -import org.openrewrite.Validated; -import org.openrewrite.config.DeclarativeRecipe; import org.openrewrite.config.Environment; -import org.openrewrite.config.RecipeDescriptor; -import org.openrewrite.config.YamlResourceLoader; import org.openrewrite.internal.InMemoryLargeSourceSet; import org.openrewrite.java.JavaParser; import org.openrewrite.maven.MavenParser; @@ -64,99 +48,29 @@ import org.springframework.ide.vscode.commons.java.IJavaProject; import org.springframework.ide.vscode.commons.languageserver.IndefiniteProgressTask; import org.springframework.ide.vscode.commons.languageserver.java.JavaProjectFinder; -import org.springframework.ide.vscode.commons.languageserver.util.ListenerList; import org.springframework.ide.vscode.commons.languageserver.util.SimpleLanguageServer; import org.springframework.ide.vscode.commons.protocol.java.ProjectBuild; -import org.springframework.ide.vscode.commons.rewrite.LoadUtils; -import org.springframework.ide.vscode.commons.rewrite.LoadUtils.DurationTypeConverter; import org.springframework.ide.vscode.commons.rewrite.ORDocUtils; import org.springframework.ide.vscode.commons.rewrite.gradle.GradleIJavaProjectParser; import org.springframework.ide.vscode.commons.rewrite.java.ProjectParser; import org.springframework.ide.vscode.commons.rewrite.maven.MavenIJavaProjectParser; import org.springframework.ide.vscode.commons.util.text.TextDocument; -import com.google.gson.Gson; -import com.google.gson.GsonBuilder; -import com.google.gson.JsonArray; -import com.google.gson.JsonElement; - public class RewriteRecipeRepository { - enum RecipeFilter { - ALL, - BOOT_UPGRADE, - NON_BOOT_UPGRADE - } - - private static final Pattern P1 = Pattern.compile("(Upgrade|Migrate)SpringBoot_\\d+_\\d+"); - - private static final Map> RECIPE_LIST_FILTERS = new HashMap<>(); - static { - RECIPE_LIST_FILTERS.put(RecipeFilter.ALL, r -> true); - RECIPE_LIST_FILTERS.put(RecipeFilter.BOOT_UPGRADE, r -> { - String n = lastTokenAfterDot(r.getName()); - return P1.matcher(n).matches(); - }); - RECIPE_LIST_FILTERS.put(RecipeFilter.NON_BOOT_UPGRADE, r -> { - return RECIPE_LIST_FILTERS.get(RecipeFilter.BOOT_UPGRADE).negate().test(r); - }); - } - - private static final String CMD_REWRITE_RELOAD = "sts/rewrite/reload"; - private static final String CMD_REWRITE_EXECUTE = "sts/rewrite/execute"; - private static final String CMD_REWRITE_LIST = "sts/rewrite/list"; - private static final String CMD_REWRITE_SUBLIST = "sts/rewrite/sublist"; private static final Logger log = LoggerFactory.getLogger(RewriteRecipeRepository.class); - private static final Set UNINITIALIZED_SET = Collections.emptySet(); - final private SimpleLanguageServer server; final private JavaProjectFinder projectFinder; - final private ListenerList loadListeners; - private CompletableFuture> recipesFuture = null; - private Set scanFiles; - private Set scanDirs; - private Set recipeFilters; - - static final Gson serializationGson = new GsonBuilder() - .registerTypeAdapter(Duration.class, new DurationTypeConverter()) - .setPrettyPrinting() - .create(); - public RewriteRecipeRepository(SimpleLanguageServer server, JavaProjectFinder projectFinder, BootJavaConfig config) { this.server = server; this.projectFinder = projectFinder; - this.loadListeners = new ListenerList<>(); - this.scanDirs = UNINITIALIZED_SET; - this.scanFiles = UNINITIALIZED_SET; - this.recipeFilters = UNINITIALIZED_SET; - - config.addListener(l -> { - Set recipeFilterFromConfig = config.getRecipesFilters(); - if (recipeFilters == UNINITIALIZED_SET || !recipeFilters.equals(recipeFilterFromConfig)) { - recipeFilters = recipeFilterFromConfig; - } - if (scanDirs == UNINITIALIZED_SET || !scanDirs.equals(config.getRecipeDirectories()) - || scanFiles == UNINITIALIZED_SET || !scanFiles.equals(config.getRecipeFiles())) { - // Eclipse client sends one event for init and the other config changed event due to remote app value expr listener. - // Therefore it is best to store the scanDirs here right after it is received, not during scan process or anything else done async - scanDirs = config.getRecipeDirectories(); - scanFiles = config.getRecipeFiles(); - clearRecipes(); - } - }); - - registerCommands(); } - private synchronized void clearRecipes() { - recipesFuture = null; - } - private synchronized Map loadRecipes() { IndefiniteProgressTask progressTask = server.getProgressService().createIndefiniteProgressTask(UUID.randomUUID().toString(), "Loading Rewrite Recipes", null); Map recipes = new HashMap<>(); @@ -180,68 +94,8 @@ private synchronized Map loadRecipes() { return recipes; } - private boolean isAcceptableGlobalCommandRecipe(Recipe r) { - if (recipeFilters.isEmpty()) { - return isRecipeValid(r); - } else { - for (String filter : recipeFilters) { - if (!filter.isBlank()) { - // Check if wild-card character present - if (filter.indexOf('*') < 0) { - // No wild-card - direct equality - if (filter.equals(r.getName())) { - return isRecipeValid(r); - } - } else { - // Wild-card present - convert to regular expression - if (Pattern.matches(filter.replaceAll("\\*", "\\.*"), r.getName())) { - return isRecipeValid(r); - } - } - } - } - return false; - } - } - - private static boolean isRecipeValid(Recipe r) { - Validated validation = Validated.invalid(null, null, null); - try { - validation = r.validate(); - } catch (Exception e) { - // ignore - } - return validation.isValid(); - } - private Environment createRewriteEnvironment() { Environment.Builder builder = Environment.builder().scanRuntimeClasspath(); - for (String p : scanFiles) { - try { - Path f = Path.of(p); - String pathStr = f.toString(); - if (pathStr.endsWith(".jar")) { - URLClassLoader classLoader = new URLClassLoader(new URL[] { f.toUri().toURL() }, - getClass().getClassLoader()); - builder.scanJar(f, new ArrayList<>(), classLoader); - } else if (pathStr.endsWith(".yml") || pathStr.endsWith(".yaml")) { - builder.load(new YamlResourceLoader(new FileInputStream(f.toFile()), f.toUri(), new Properties())); - } - } catch (Exception e) { - log.error("Skipping folder " + p, e); - } - } -// for (String p : scanDirs) { -// try { -// Path d = Path.of(p); -// if (Files.isDirectory(d)) { -// URLClassLoader classLoader = new URLClassLoader(new URL[] { d.toUri().toURL()}, getClass().getClassLoader()); -// builder.scanPath(d, Collections.emptyList(), classLoader); -// } -// } catch (Exception e) { -// log.error("Skipping folder " + p, e); -// } -// } return builder.build(); } @@ -256,133 +110,21 @@ public CompletableFuture> getRecipe(String name) { return recipes().thenApply(recipes -> Optional.ofNullable(recipes.get(name))); } - private static JsonElement recipeToJson(Recipe r) { - RecipeDescriptor descriptor = r.getDescriptor(); - JsonElement jsonElement = serializationGson.toJsonTree(Map.of( - "name", descriptor.getName(), - "displayName", descriptor.getDisplayName(), - "description", descriptor.getDescription(), - "options", descriptor.getOptions(), - "tags", descriptor.getTags(), - "hasSubRecipes", !descriptor.getRecipeList().isEmpty() - )); - return jsonElement; - } - - CompletableFuture> getRootRecipes(Predicate rootFilter) { - return recipes().thenApply(recipesMap -> recipesMap.values().stream().filter(rootFilter).collect(Collectors.toList())); - } - - CompletableFuture> getSubRecipes(String rootRecipeId, List path) { - return recipes().thenApply(recipesMap -> { - Recipe recipe = recipesMap.get(rootRecipeId); - for (int i : path) { - if (i < recipe.getRecipeList().size()) { - recipe = recipe.getRecipeList().get(i); - } else { - return Collections.emptyList(); - } - } - return recipe == null ? Collections.emptyList() : recipe.getRecipeList(); - }); - } - - Recipe createRecipeFromSelection(Recipe original, RecipeSelectionDescriptor[] selection) { - if (selection == null) { - return original; - } else { - boolean sameSubrecipes = true; - List newSubRecipes = new ArrayList<>(selection.length); - for (int i = 0; i < selection.length; i++) { - if (selection[i].selected) { - Recipe originalSubRecipe = original.getRecipeList().get(i); - Recipe newSubRecipe = createRecipeFromSelection(originalSubRecipe, selection[i].subselection()); - newSubRecipes.add(newSubRecipe); - if (sameSubrecipes) { - sameSubrecipes = newSubRecipe == originalSubRecipe; - } - } else { - sameSubrecipes = false; - } - } - if (sameSubrecipes) { - return original; - } else { - @SuppressWarnings("unchecked") - Recipe newRecipe = LoadUtils.createRecipe(original.getDescriptor(), id -> { - try { - return (Class) Class.forName(id); - } catch (ClassNotFoundException e) { - return null; - } - }, true); - newRecipe.getRecipeList().addAll(newSubRecipes); - return newRecipe; - } - } - } - - private void registerCommands() { - server.onCommand(CMD_REWRITE_LIST, params -> { - RecipeFilter f = params.getArguments().size() > 0 ? RecipeFilter.valueOf(((JsonElement) params.getArguments().get(0)).getAsString()) : RecipeFilter.ALL; - - return getRootRecipes(r -> isAcceptableGlobalCommandRecipe(r) && RECIPE_LIST_FILTERS.get(f).test(r)).thenApply(recipes -> recipes.stream() - .map(RewriteRecipeRepository::recipeToJson) - .collect(Collectors.toList())); - }); - - server.onCommand(CMD_REWRITE_SUBLIST, params -> { - String rootRecipeId = ((JsonElement) params.getArguments().get(0)).getAsString(); - JsonArray path = (JsonArray) params.getArguments().get(1); - - return getSubRecipes(rootRecipeId, path.asList().stream().map(j -> j.getAsInt()).collect(Collectors.toList())).thenApply(recipes -> recipes.stream() - .map(RewriteRecipeRepository::recipeToJson) - .collect(Collectors.toList())); - }); - - server.onCommand(CMD_REWRITE_EXECUTE, params -> { - return recipes().thenCompose(recipesMap -> { - String uri = ((JsonElement) params.getArguments().get(0)).getAsString(); - JsonElement recipesJson = ((JsonElement) params.getArguments().get(1)); - boolean askForPreview = params.getArguments().size() > 2 ? ((JsonElement) params.getArguments().get(2)).getAsBoolean() : false; - - RecipeSelectionDescriptor[] descriptors = serializationGson.fromJson(recipesJson, RecipeSelectionDescriptor[].class); - List recipes = Arrays.stream(descriptors).map(d -> createRecipeFromSelection(recipesMap.get(d.id()), d.subselection())).collect(Collectors.toList()); - if (recipes.size() == 1) { - Recipe r = recipes.get(0); - String progressToken = params.getWorkDoneToken() == null - || params.getWorkDoneToken().getLeft() == null - ? (r.getName() == null ? UUID.randomUUID().toString() : r.getName()) - : params.getWorkDoneToken().getLeft(); - return apply(r, uri, progressToken, askForPreview); - } else { - String name = recipes.size() + " recipes"; - DeclarativeRecipe aggregateRecipe = new DeclarativeRecipe( - name, - name, - recipes.stream().map(r -> r.getDescription()).collect(Collectors.joining("\n")), - recipes.stream().flatMap(r -> r.getTags().stream()).collect(Collectors.toSet()), - null, - null, - false, - Collections.emptyList() - ); - aggregateRecipe.getRecipeList().addAll(recipes); - String progressToken = params.getWorkDoneToken() == null - || params.getWorkDoneToken().getLeft() == null - ? (aggregateRecipe.getName() == null ? UUID.randomUUID().toString() - : aggregateRecipe.getName()) - : params.getWorkDoneToken().getLeft(); - return apply(aggregateRecipe, uri, progressToken, askForPreview); - } - }); - }); - - server.onCommand(CMD_REWRITE_RELOAD, params -> { - clearRecipes(); - return CompletableFuture.completedFuture("executed"); - }); - + @SuppressWarnings("unchecked") + CompletableFuture applyToBuildFiles(Recipe r, String uri, String progressToken, boolean askForPreview) { + return projectFinder.find(new TextDocumentIdentifier(uri)).map(p -> { + final IndefiniteProgressTask progressTask = server.getProgressService().createIndefiniteProgressTask(progressToken, r.getDisplayName(), "Initiated..."); + final IJavaProject project = p; + return CompletableFuture.supplyAsync(() -> { + Path absoluteProjectDir = Paths.get(project.getLocationUri()); + progressTask.progressEvent("Parsing files..."); + ProjectParser projectParser = getProjectParser(project); + return (List) projectParser.parseBuildFiles(absoluteProjectDir, new InMemoryExecutionContext(e -> log.error("Project Parsing error:", e))); + }) + .thenCompose(sources -> computeWorkspaceEditAwareOfPreview(r, sources, progressTask, askForPreview)) + .thenCompose(we -> applyEdit(we, progressTask, r.getDisplayName())) + .whenComplete((o,t) -> progressTask.done()); + }).orElse(CompletableFuture.failedFuture(new IllegalArgumentException("Cannot find Spring Boot project for uri: " + uri))); } CompletableFuture apply(Recipe r, String uri, String progressToken, boolean askForPreview) { @@ -394,14 +136,7 @@ CompletableFuture apply(Recipe r, String uri, String progressToken, bool IJavaProject project = p.get(); Path absoluteProjectDir = Paths.get(project.getLocationUri()); progressTask.progressEvent("Parsing files..."); - ProjectParser projectParser = createRewriteProjectParser(project, - pr -> { - TextDocument doc = server.getTextDocumentService().getLatestSnapshot(pr.toUri().toASCIIString()); - if (doc != null) { - return new Parser.Input(pr, () -> new ByteArrayInputStream(doc.get().getBytes())); - } - return null; - }); + ProjectParser projectParser = getProjectParser(project); List sources = projectParser.parse(absoluteProjectDir, new InMemoryExecutionContext(e -> log.error("Project Parsing error:", e))); return computeWorkspaceEditAwareOfPreview(r, sources, progressTask, askForPreview) @@ -511,18 +246,15 @@ private static ProjectParser createRewriteProjectParser(IJavaProject jp, Functio } } - public void onRecipesLoaded(Consumer l) { - loadListeners.add(l); - } - - private static String lastTokenAfterDot(String s) { - int idx = s.lastIndexOf('.'); - if (idx >= 0 && idx < s.length() - 1) { - return s.substring(idx + 1); - } - return s; - } - - record RecipeSelectionDescriptor(boolean selected, String id, RecipeSelectionDescriptor[] subselection) {}; - + private ProjectParser getProjectParser(IJavaProject jp) { + return createRewriteProjectParser(jp, + pr -> { + TextDocument doc = server.getTextDocumentService().getLatestSnapshot(pr.toUri().toASCIIString()); + if (doc != null) { + return new Parser.Input(pr, () -> new ByteArrayInputStream(doc.get().getBytes())); + } + return null; + }); + } + } diff --git a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/rewrite/SpringBootUpgrade.java b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/rewrite/SpringBootUpgrade.java index 312e572640..6cd3faa214 100644 --- a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/rewrite/SpringBootUpgrade.java +++ b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/rewrite/SpringBootUpgrade.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2022, 2024 VMware, Inc. + * Copyright (c) 2022, 2025 VMware, Inc. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at @@ -10,13 +10,8 @@ *******************************************************************************/ package org.springframework.ide.vscode.boot.java.rewrite; -import java.util.ArrayList; import java.util.Collections; -import java.util.HashMap; -import java.util.List; -import java.util.Map; import java.util.Optional; -import java.util.Set; import java.util.UUID; import org.eclipse.lsp4j.TextDocumentIdentifier; @@ -37,24 +32,6 @@ public class SpringBootUpgrade { final public static String CMD_UPGRADE_SPRING_BOOT = "sts/upgrade/spring-boot"; - private final Map versionsToRecipeId = new HashMap<>(); - { - versionsToRecipeId.put("2.0", "org.openrewrite.java.spring.boot2.UpgradeSpringBoot_2_0"); - versionsToRecipeId.put("2.1", "org.openrewrite.java.spring.boot2.UpgradeSpringBoot_2_1"); - versionsToRecipeId.put("2.2", "org.openrewrite.java.spring.boot2.UpgradeSpringBoot_2_2"); - versionsToRecipeId.put("2.3", "org.openrewrite.java.spring.boot2.UpgradeSpringBoot_2_3"); - versionsToRecipeId.put("2.4", "org.openrewrite.java.spring.boot2.UpgradeSpringBoot_2_4"); - versionsToRecipeId.put("2.5", "org.openrewrite.java.spring.boot2.UpgradeSpringBoot_2_5"); - versionsToRecipeId.put("2.6", "org.openrewrite.java.spring.boot2.UpgradeSpringBoot_2_6"); - versionsToRecipeId.put("2.7", "org.openrewrite.java.spring.boot2.UpgradeSpringBoot_2_7"); - versionsToRecipeId.put("3.0", "org.openrewrite.java.spring.boot3.UpgradeSpringBoot_3_0"); - versionsToRecipeId.put("3.1", "org.openrewrite.java.spring.boot3.UpgradeSpringBoot_3_1"); - versionsToRecipeId.put("3.2", "org.openrewrite.java.spring.boot3.UpgradeSpringBoot_3_2"); - versionsToRecipeId.put("3.3", "org.openrewrite.java.spring.boot3.UpgradeSpringBoot_3_3"); - versionsToRecipeId.put("3.4", "org.springframework.ide.vscode.rewrite.boot3.UpgradeSpringBoot_3_4"); - versionsToRecipeId.put("3.5", "org.springframework.ide.vscode.rewrite.boot3.UpgradeSpringBoot_3_5"); - } - public SpringBootUpgrade(SimpleLanguageServer server, RewriteRecipeRepository recipeRepo, JavaProjectFinder projectFinder) { server.onCommand(CMD_UPGRADE_SPRING_BOOT, params -> { String uri = ((JsonElement) params.getArguments().get(0)).getAsString(); @@ -76,45 +53,15 @@ public SpringBootUpgrade(SimpleLanguageServer server, RewriteRecipeRepository re + version.toMajorMinorVersionStr() + "' is newer or same as the target version '" + targetVersion.toMajorMinorVersionStr() + "'"); -// return recipeRepo.recipes().thenComposeAsync(recipes -> recipeRepo.apply( -// createUpgradeRecipe(recipes, version, targetVersion), -// uri, -// UUID.randomUUID().toString(), -// askForPreview -// )); + Assert.isLegal( + version.getMajor() == targetVersion.getMajor() && version.getMinor() == targetVersion.getMinor(), + "Non patch version upgrades not supported!"); - return recipeRepo.apply(createUpgradeRecipe(version, targetVersion), uri, UUID.randomUUID().toString(), askForPreview); + return recipeRepo.applyToBuildFiles(createUpgradeRecipe(version, targetVersion), uri, UUID.randomUUID().toString(), askForPreview); }); } - static List createRecipeIdsChain(int major, int minor, int targetMajor, int targetMinor, Map versionToRecipeId) { - List ids = new ArrayList<>(); - for (int currentMajor = major, currentMinor = minor; targetMajor > currentMajor || (targetMajor == currentMajor && currentMinor <= targetMinor);) { - String recipeId = versionToRecipeId.get(createVersionString(currentMajor, currentMinor)); - if (recipeId == null) { - currentMajor++; - currentMinor = 0; - } else { - ids.add(recipeId); - currentMinor++; - } - } - return ids; - } - - public boolean canUpgrade(Version version, Version targetVersion) { - if (version.compareTo(targetVersion) <= 0) { - return false; - } - if (version.getMajor() == targetVersion.getMajor() && version.getMinor() == targetVersion.getMinor()) { - return true; - } else { - //TODO: Major or minor version upgrades are NOT available for the time being via open rewrite - return false; - } - } - - private Recipe createUpgradeRecipe(/*Map recipes, */Version version, Version targetVersion) { + private Recipe createUpgradeRecipe(Version version, Version targetVersion) { Recipe recipe = new DeclarativeRecipe("upgrade-spring-boot", "Upgrade Spring Boot from " + version + " to " + targetVersion, "", Collections.emptySet(), null, null, false, Collections.emptyList()); @@ -124,12 +71,6 @@ private Recipe createUpgradeRecipe(/*Map recipes, */Version vers recipe.getRecipeList().add(new UpgradeParentVersion("org.springframework.boot", "spring-boot-starter-parent", version.getMajor() + "." + version.getMinor() + ".x", null, null)); recipe.getRecipeList().add(new org.openrewrite.gradle.UpgradeDependencyVersion("org.springframework.boot", "*", version.getMajor() + "." + version.getMinor() + ".x", null)); recipe.getRecipeList().add(new UpgradePluginVersion("org.springframework.boot", version.getMajor() + "." + version.getMinor() + ".x", null)); -// } else /*if (version.getMajor() == targetVersion.getMajor())*/ { -// List recipedIds = createRecipeIdsChain(version.getMajor(), version.getMinor() + 1, targetVersion.getMajor(), targetVersion.getMinor(), versionsToRecipeId); -// if (!recipedIds.isEmpty()) { -// String recipeId = recipedIds.get(recipedIds.size() - 1); -// Optional.ofNullable(recipes.get(recipeId)).ifPresent(r -> recipe.getRecipeList().add(r)); -// } } if (recipe.getRecipeList().isEmpty()) { @@ -141,26 +82,8 @@ private Recipe createUpgradeRecipe(/*Map recipes, */Version vers } } - private static String createVersionString(int major, int minor) { - StringBuilder sb = new StringBuilder(); - sb.append(major); - sb.append('.'); - sb.append(minor); - return sb.toString(); - } - - static String nearestAvailableMinorVersion(Version v, Set availableVersions) { - for (int major = v.getMajor(), minor = v.getMinor(); minor >= 0; minor--) { - String versionStr = createVersionString(major, minor); - if (availableVersions.contains(versionStr)) { - return versionStr; - } - } - return null; - } - public Optional getNearestAvailableMinorVersion(Version v) { - return Optional.ofNullable(nearestAvailableMinorVersion(v, versionsToRecipeId.keySet())); + return Optional.empty(); } } diff --git a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/validation/generations/UpdateBootVersion.java b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/validation/generations/UpdateBootVersion.java index 26af6762a7..c3d1050d16 100644 --- a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/validation/generations/UpdateBootVersion.java +++ b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/validation/generations/UpdateBootVersion.java @@ -69,7 +69,7 @@ private Optional validateMajorVersion(IJavaProject javaProject, Vers bootUpgradeOpt.flatMap(bu -> bu.getNearestAvailableMinorVersion(latest)).map(targetVersion -> { Version upgradeVersion = Version.parse(targetVersion); - if (!bootUpgradeOpt.get().canUpgrade(javaProjectVersion, upgradeVersion) || javaProjectVersion.compareTo(upgradeVersion) >= 0) { + if (javaProjectVersion.compareTo(upgradeVersion) >= 0) { return null; } CodeAction c = new CodeAction(); @@ -102,7 +102,7 @@ private Optional validateMinorVersion(IJavaProject javaProject, Vers bootUpgradeOpt.flatMap(bu -> bu.getNearestAvailableMinorVersion(latest)).map(targetVersion -> { Version upgradeVersion = Version.parse(targetVersion); - if (!bootUpgradeOpt.get().canUpgrade(javaProjectVersion, upgradeVersion) || javaProjectVersion.compareTo(upgradeVersion) >= 0) { + if (javaProjectVersion.compareTo(upgradeVersion) >= 0) { return null; } CodeAction c = new CodeAction(); diff --git a/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/java/rewrite/RewriteRecipeRepositoryTest.java b/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/java/rewrite/RewriteRecipeRepositoryTest.java deleted file mode 100644 index a780696739..0000000000 --- a/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/java/rewrite/RewriteRecipeRepositoryTest.java +++ /dev/null @@ -1,137 +0,0 @@ -/******************************************************************************* - * Copyright (c) 2023 VMware, Inc. - * All rights reserved. This program and the accompanying materials - * are made available under the terms of the Eclipse Public License v1.0 - * which accompanies this distribution, and is available at - * https://www.eclipse.org/legal/epl-v10.html - * - * Contributors: - * VMware, Inc. - initial API and implementation - *******************************************************************************/ -package org.springframework.ide.vscode.boot.java.rewrite; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.junit.jupiter.api.Assertions.assertEquals; - -import java.util.List; - -import org.junit.jupiter.api.Disabled; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExtendWith; -import org.openrewrite.Recipe; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.context.annotation.Import; -import org.springframework.ide.vscode.boot.bootiful.BootLanguageServerTest; -import org.springframework.ide.vscode.boot.bootiful.SymbolProviderTestConf; -import org.springframework.ide.vscode.boot.java.rewrite.RewriteRecipeRepository.RecipeSelectionDescriptor; -import org.springframework.test.context.junit.jupiter.SpringExtension; - -@ExtendWith(SpringExtension.class) -@BootLanguageServerTest -@Import(SymbolProviderTestConf.class) -@Disabled -public class RewriteRecipeRepositoryTest { - - @Autowired RewriteRecipeRepository recipeRepo; - - @Test - void listSubRecipe() throws Exception { - List recipes = recipeRepo.getSubRecipes("org.openrewrite.java.spring.boot3.UpgradeSpringBoot_3_1", List.of()).get(); - assertEquals(11, recipes.size()); - - recipes = recipeRepo.getSubRecipes("org.openrewrite.java.spring.boot3.UpgradeSpringBoot_3_1", List.of(0, 2)).get(); - assertEquals(22, recipes.size()); - - recipes = recipeRepo.getSubRecipes("org.openrewrite.java.spring.boot3.UpgradeSpringBoot_3_1",List.of(0, 345)).get(); - assertEquals(0, recipes.size()); - - recipes = recipeRepo.getSubRecipes("Hohoho", List.of()).get(); - assertEquals(0, recipes.size()); - - } - - @Test - void listRootRecipes() throws Exception { - List recipes = recipeRepo.getRootRecipes(r -> true).get(); - assertThat(recipes.size()).isGreaterThan(5); - - recipes = recipeRepo.getRootRecipes(r -> r.getName().startsWith("Hohoho")).get(); - assertEquals(0, recipes.size()); - } - - @Test - void createRecipeFromSelectionDescriptor() throws Exception { - RecipeSelectionDescriptor descriptor = new RecipeSelectionDescriptor(true, "org.openrewrite.java.spring.boot3.UpgradeSpringBoot_3_1", new RecipeSelectionDescriptor[] { - new RecipeSelectionDescriptor(true, "org.openrewrite.java.spring.boot3.UpgradeSpringBoot_3_0", new RecipeSelectionDescriptor[] { // pick boot 3.0 - new RecipeSelectionDescriptor(true, "org.openrewrite.java.spring.boot2.UpgradeSpringBoot_2_7", null), // pick boot 2.7 - new RecipeSelectionDescriptor(false, "", null), - new RecipeSelectionDescriptor(true, "org.openrewrite.java.migrate.UpgradeToJava17", new RecipeSelectionDescriptor[] { // pick Java 17 - new RecipeSelectionDescriptor(true, "", null), // java 11 - new RecipeSelectionDescriptor(true, "", null), // java 17 - new RecipeSelectionDescriptor(false, "", null), - new RecipeSelectionDescriptor(false, "", null), - new RecipeSelectionDescriptor(false, "", null), - new RecipeSelectionDescriptor(false, "", null), - new RecipeSelectionDescriptor(false, "", null), - new RecipeSelectionDescriptor(true, "", null), // text blocks - new RecipeSelectionDescriptor(false, "", null), - new RecipeSelectionDescriptor(false, "", null), - new RecipeSelectionDescriptor(false, "", null), - new RecipeSelectionDescriptor(false, "", null), - new RecipeSelectionDescriptor(false, "", null), - }), - new RecipeSelectionDescriptor(false, "", null), - new RecipeSelectionDescriptor(false, "", null), - new RecipeSelectionDescriptor(false, "", null), - new RecipeSelectionDescriptor(false, "", null), - new RecipeSelectionDescriptor(false, "", null), - new RecipeSelectionDescriptor(false, "", null), - new RecipeSelectionDescriptor(false, "", null), - new RecipeSelectionDescriptor(false, "", null), - new RecipeSelectionDescriptor(false, "", null), - new RecipeSelectionDescriptor(false, "", null), - new RecipeSelectionDescriptor(false, "", null), - new RecipeSelectionDescriptor(false, "", null), - new RecipeSelectionDescriptor(false, "", null), - new RecipeSelectionDescriptor(false, "", null), - new RecipeSelectionDescriptor(false, "", null), - new RecipeSelectionDescriptor(false, "", null), - new RecipeSelectionDescriptor(false, "", null), - new RecipeSelectionDescriptor(false, "", null), - }), - new RecipeSelectionDescriptor(false, "", null), - new RecipeSelectionDescriptor(false, "", null), - new RecipeSelectionDescriptor(false, "", null), - new RecipeSelectionDescriptor(false, "", null), - new RecipeSelectionDescriptor(true, "org.openrewrite.java.spring.security6.UpgradeSpringSecurity_6_1", null), - new RecipeSelectionDescriptor(false, "", null), - new RecipeSelectionDescriptor(false, "", null), - new RecipeSelectionDescriptor(false, "", null) - }); - - Recipe boot31Recipes = recipeRepo.getRecipe("org.openrewrite.java.spring.boot3.UpgradeSpringBoot_3_1").get().get(); - - Recipe boot31 = recipeRepo.createRecipeFromSelection(boot31Recipes, descriptor.subselection()); - - assertEquals(2, boot31.getRecipeList().size()); - - Recipe security61 = boot31.getRecipeList().get(1); - assertThat(security61.getRecipeList().size()).isGreaterThanOrEqualTo(5); - - Recipe boot30 = boot31.getRecipeList().get(0); - assertEquals("org.openrewrite.java.spring.boot3.UpgradeSpringBoot_3_0", boot30.getName()); - assertEquals(2, boot30.getRecipeList().size()); - - Recipe boot27 = boot30.getRecipeList().get(0); - assertThat(boot27.getRecipeList().size()).isGreaterThan(10); - - Recipe java17 = boot30.getRecipeList().get(1); - assertThat(java17.getName()).isEqualTo("org.openrewrite.java.migrate.UpgradeToJava17"); - assertThat(java17.getRecipeList().size()).isEqualTo(3); - - assertThat(java17.getRecipeList().get(0).getName()).isEqualTo("org.openrewrite.java.migrate.Java8toJava11"); - assertThat(java17.getRecipeList().get(1).getName()).isEqualTo("org.openrewrite.java.migrate.UpgradeBuildToJava17"); - - } - -} diff --git a/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/java/rewrite/SpringBootUpgradeTest.java b/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/java/rewrite/SpringBootUpgradeTest.java deleted file mode 100644 index 622da0c0d8..0000000000 --- a/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/java/rewrite/SpringBootUpgradeTest.java +++ /dev/null @@ -1,98 +0,0 @@ -/******************************************************************************* - * Copyright (c) 2022, 2023 VMware, Inc. - * All rights reserved. This program and the accompanying materials - * are made available under the terms of the Eclipse Public License v1.0 - * which accompanies this distribution, and is available at - * https://www.eclipse.org/legal/epl-v10.html - * - * Contributors: - * VMware, Inc. - initial API and implementation - *******************************************************************************/ -package org.springframework.ide.vscode.boot.java.rewrite; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertNull; - -import java.util.Collections; -import java.util.List; -import java.util.Map; - -import org.junit.jupiter.api.Test; -import org.springframework.ide.vscode.commons.Version; - -public class SpringBootUpgradeTest { - - private static final Map MINOR_VERSION_TO_RECIPE_ID = Map.of( - "2.0", "org.openrewrite.java.spring.boot2.UpgradeSpringBoot_2_0", - "2.1", "org.openrewrite.java.spring.boot2.UpgradeSpringBoot_2_1", - "2.2", "org.openrewrite.java.spring.boot2.UpgradeSpringBoot_2_2", - "2.3", "org.openrewrite.java.spring.boot2.UpgradeSpringBoot_2_3", - "2.4", "org.openrewrite.java.spring.boot2.UpgradeSpringBoot_2_4", - "2.5", "org.openrewrite.java.spring.boot2.UpgradeSpringBoot_2_5", - "2.6", "org.openrewrite.java.spring.boot2.UpgradeSpringBoot_2_6", - "2.7", "org.openrewrite.java.spring.boot2.UpgradeSpringBoot_2_7", - "3.0", "org.openrewrite.java.spring.boot3.UpgradeSpringBoot_3_0" - ); - - @Test - void recipeIdChain_1() throws Exception { - assertEquals(List.of( - "org.openrewrite.java.spring.boot2.UpgradeSpringBoot_2_0", - "org.openrewrite.java.spring.boot2.UpgradeSpringBoot_2_1", - "org.openrewrite.java.spring.boot2.UpgradeSpringBoot_2_2", - "org.openrewrite.java.spring.boot2.UpgradeSpringBoot_2_3", - "org.openrewrite.java.spring.boot2.UpgradeSpringBoot_2_4", - "org.openrewrite.java.spring.boot2.UpgradeSpringBoot_2_5" - ), SpringBootUpgrade.createRecipeIdsChain(1, 3, 2, 5, MINOR_VERSION_TO_RECIPE_ID)); - } - - @Test - void recipeIdChain_2() throws Exception { - assertEquals(List.of( - "org.openrewrite.java.spring.boot2.UpgradeSpringBoot_2_2", - "org.openrewrite.java.spring.boot2.UpgradeSpringBoot_2_3", - "org.openrewrite.java.spring.boot2.UpgradeSpringBoot_2_4", - "org.openrewrite.java.spring.boot2.UpgradeSpringBoot_2_5", - "org.openrewrite.java.spring.boot2.UpgradeSpringBoot_2_6", - "org.openrewrite.java.spring.boot2.UpgradeSpringBoot_2_7" - ), SpringBootUpgrade.createRecipeIdsChain(2, 2, 2, 7, MINOR_VERSION_TO_RECIPE_ID)); - } - - @Test - void recipeIdChain_3() throws Exception { - assertEquals(List.of( - "org.openrewrite.java.spring.boot2.UpgradeSpringBoot_2_0", - "org.openrewrite.java.spring.boot2.UpgradeSpringBoot_2_1", - "org.openrewrite.java.spring.boot2.UpgradeSpringBoot_2_2", - "org.openrewrite.java.spring.boot2.UpgradeSpringBoot_2_3", - "org.openrewrite.java.spring.boot2.UpgradeSpringBoot_2_4", - "org.openrewrite.java.spring.boot2.UpgradeSpringBoot_2_5", - "org.openrewrite.java.spring.boot2.UpgradeSpringBoot_2_6", - "org.openrewrite.java.spring.boot2.UpgradeSpringBoot_2_7", - "org.openrewrite.java.spring.boot3.UpgradeSpringBoot_3_0" - ), SpringBootUpgrade.createRecipeIdsChain(1, 3, 3, 0, MINOR_VERSION_TO_RECIPE_ID)); - } - - @Test - void recipeIdChain_4() throws Exception { - assertEquals(List.of( - "org.openrewrite.java.spring.boot2.UpgradeSpringBoot_2_2" - ), SpringBootUpgrade.createRecipeIdsChain(2, 2, 2, 2, MINOR_VERSION_TO_RECIPE_ID)); - } - - @Test - void recipeIdChain_5() throws Exception { - assertEquals(List.of( - ), SpringBootUpgrade.createRecipeIdsChain(2, 7, 2, 2, MINOR_VERSION_TO_RECIPE_ID)); - } - - @Test - void nearestMinorVersion() throws Exception { - assertEquals("3.0", SpringBootUpgrade.nearestAvailableMinorVersion(new Version(3, 0, 2, null), MINOR_VERSION_TO_RECIPE_ID.keySet())); - assertEquals("3.0", SpringBootUpgrade.nearestAvailableMinorVersion(new Version(3, 3, 1, null), MINOR_VERSION_TO_RECIPE_ID.keySet())); - assertNull(SpringBootUpgrade.nearestAvailableMinorVersion(new Version(4, 3, 1, null), MINOR_VERSION_TO_RECIPE_ID.keySet())); - assertNull(SpringBootUpgrade.nearestAvailableMinorVersion(new Version(1, 5, 0, null), MINOR_VERSION_TO_RECIPE_ID.keySet())); - assertNull(SpringBootUpgrade.nearestAvailableMinorVersion(new Version(3, 0, 2, null), Collections.emptySet())); - } -} - From bad4c70bee381976aa8a91bb8bc4807410879291 Mon Sep 17 00:00:00 2001 From: BoykoAlex Date: Thu, 30 Oct 2025 17:22:25 -0700 Subject: [PATCH 6/6] Update init.gradle Signed-off-by: BoykoAlex --- .../commons/commons-rewrite/src/main/resources/init.gradle | 1 - 1 file changed, 1 deletion(-) diff --git a/headless-services/commons/commons-rewrite/src/main/resources/init.gradle b/headless-services/commons/commons-rewrite/src/main/resources/init.gradle index 432ca385d4..83af97635a 100644 --- a/headless-services/commons/commons-rewrite/src/main/resources/init.gradle +++ b/headless-services/commons/commons-rewrite/src/main/resources/init.gradle @@ -29,7 +29,6 @@ initscript { dependencies { classpath 'org.openrewrite.gradle.tooling:plugin:8.65.0' - classpath 'org.openrewrite:rewrite-maven:8.65.0' } }