diff --git a/build.gradle.kts b/build.gradle.kts
index 597622de2..ad735e2c4 100644
--- a/build.gradle.kts
+++ b/build.gradle.kts
@@ -120,8 +120,8 @@ dependencies {
testImplementation("com.github.marschall:memoryfilesystem:latest.release")
// for generating properties migration configurations
- testImplementation("com.fasterxml.jackson.core:jackson-databind:latest.release")
- testImplementation("com.fasterxml.jackson.module:jackson-module-kotlin:latest.release")
+ testImplementation("com.fasterxml.jackson.core:jackson-databind:2.12.+")
+ testImplementation("com.fasterxml.jackson.module:jackson-module-kotlin:2.12.+")
testImplementation("io.github.classgraph:classgraph:latest.release")
testRuntimeOnly("org.openrewrite:rewrite-java-11:${rewriteVersion}")
diff --git a/tmp/ComponentToBeanConfiguration.java b/tmp/ComponentToBeanConfiguration.java
deleted file mode 100644
index bc9c0580f..000000000
--- a/tmp/ComponentToBeanConfiguration.java
+++ /dev/null
@@ -1,247 +0,0 @@
-/*
- * 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.AutoConfigure;
-import org.openrewrite.Validated;
-import org.openrewrite.internal.lang.Nullable;
-import org.openrewrite.java.AutoFormat;
-import org.openrewrite.java.JavaRefactorVisitor;
-import org.openrewrite.java.tree.J;
-import org.openrewrite.java.tree.JavaType;
-import org.openrewrite.java.tree.TypeUtils;
-
-import java.util.*;
-import java.util.stream.Collectors;
-import java.util.stream.Stream;
-
-import static java.util.Collections.emptyList;
-import static java.util.stream.Collectors.toList;
-import static org.openrewrite.Formatting.formatFirstPrefix;
-import static org.openrewrite.Validated.required;
-
-/**
- * Generates a new {@code @Configuration} class (or updates an existing one) with {@code @Bean} definitions for all
- * {@code @Component} annotated types and places it in the same package as the main {@code @SpringBootApplication}.
- */
-// FIXME generator!
-@AutoConfigure
-public class ComponentToBeanConfiguration extends JavaRefactorVisitor {
- private String configurationClass;
-
- @Nullable
- private J.CompilationUnit existingConfiguration;
-
- @Nullable
- private J.CompilationUnit springBootApplication;
-
- private final Set componentsToCreateBeansDefinitionsFor = new TreeSet<>(
- Comparator.comparing(J.ClassDecl::getSimpleName));
-
- public ComponentToBeanConfiguration() {
- setCursoringOn();
- }
-
- @Override
- public Validated validate() {
- return required("configurationClass", configurationClass);
- }
-
- public void setConfigurationClass(String configurationClass) {
- this.configurationClass = configurationClass;
- }
-
-// @Override
-// public J.CompilationUnit getGenerated() {
-// if (existingConfiguration == null && springBootApplication == null) {
-// // TODO how do we warn about this, don't know where to put new configuration class?
-// return null;
-// }
-//
-// return Optional.ofNullable(existingConfiguration)
-// .orElseGet(() -> {
-// Path sourceSet = Paths.get(springBootApplication.getSourcePath())
-// .getParent();
-//
-// String pkg = springBootApplication.getPackageDecl().getExpr().printTrimmed();
-// for (int i = 0; i < pkg.chars().filter(n -> n == '.').count(); i++) {
-// sourceSet = sourceSet.getParent();
-// }
-//
-// J.CompilationUnit configurationClass = J.CompilationUnit.buildEmptyClass(sourceSet, pkg, this.configurationClass);
-// return configurationClass.refactor()
-// .visit(new AddAnnotation.Scoped(configurationClass.getClasses().get(0),
-// "org.springframework.context.annotation.Configuration"))
-// .fix(1).getFixed();
-// }).refactor().visit(new AddBeanDefinitions()).fix().getFixed();
-// }
-
- private class AddBeanDefinitions extends JavaRefactorVisitor {
- public AddBeanDefinitions() {
- setCursoringOn();
- }
-
- @Override
- public boolean isIdempotent() {
- return false;
- }
-
- @Override
- public J visitClassDecl(J.ClassDecl classDecl) {
- J.ClassDecl configClass = refactor(classDecl, super::visitClassDecl);
- if (!configClass.findAnnotationsOnClass("@org.springframework.context.annotation.Configuration").isEmpty()) {
- J.CompilationUnit cu = getCursor().firstEnclosing(J.CompilationUnit.class);
- assert cu != null;
-
- for (J.ClassDecl component : componentsToCreateBeansDefinitionsFor) {
- JavaType.Class componentType = TypeUtils.asClass(component.getType());
- if (componentType == null) {
- // TODO how to report that we can't generate a bean definition
- continue;
- }
-
- maybeAddImport(componentType);
-
- List fieldCollaborators = autowiredFields(component);
- List constructorCollaborators = constructorInjectedFields(component);
-
- List paramTypes = emptyList();
- String constructorArgs = "";
- String params = "";
- if (!fieldCollaborators.isEmpty()) {
- params = fieldCollaborators.stream()
- .map(param -> param.getTypeExpr().printTrimmed() + " " + param.getVars().iterator().next().printTrimmed())
- .collect(Collectors.joining(", "));
- paramTypes = fieldCollaborators.stream()
- .map(J.VariableDecls::getTypeAsClass)
- .collect(toList());
- } else if (!constructorCollaborators.isEmpty()) {
- params = constructorCollaborators.stream()
- .map(param -> param.getTypeExpr().printTrimmed() + " " + param.getVars().iterator().next().printTrimmed())
- .collect(Collectors.joining(", "));
- paramTypes = constructorCollaborators.stream()
- .map(J.VariableDecls::getTypeAsClass)
- .collect(toList());
- constructorArgs = constructorCollaborators.stream()
- .flatMap(param -> param.getVars().stream())
- .map(J.VariableDecls.NamedVar::getSimpleName)
- .collect(Collectors.joining(", "));
- }
-
- String className = componentType.getClassName();
- String lowerClassName = Character.toLowerCase(className.charAt(0)) + className.substring(1);
-
- String beanDefinitionSource = "@Bean\n" +
- className + " " + lowerClassName + "(" + params + ") {\n";
-
- if (!fieldCollaborators.isEmpty()) {
- beanDefinitionSource += className + " " + lowerClassName + " = new " + className + "();\n";
- for (J.VariableDecls fieldCollaborator : fieldCollaborators) {
- beanDefinitionSource += fieldCollaborator.getVars().stream().findAny()
- .map(field -> {
- String lowerName = field.getSimpleName();
- return lowerClassName + ".set" + Character.toUpperCase(lowerName.charAt(0)) + lowerName.substring(1) +
- "(" + lowerName + ");\n";
- })
- .orElse("");
- }
- beanDefinitionSource += "return " + lowerClassName + ";\n";
- } else {
- beanDefinitionSource += "return new " + className + "(" + constructorArgs + ");\n";
- }
-
- beanDefinitionSource += "}";
-
- J.MethodDecl beanDefinition = treeBuilder.buildMethodDeclaration(
- configClass,
- beanDefinitionSource,
- Stream.concat(
- Stream.of(
- JavaType.Class.build("org.springframework.context.annotation.Bean"),
- componentType
- ),
- paramTypes.stream()
- ).toArray(JavaType.Class[]::new)
- );
-
- andThen(new AutoFormat(beanDefinition));
-
- List statements = new ArrayList<>(configClass.getBody().getStatements());
- statements.add(beanDefinition.withPrefix("\n" + beanDefinition.getPrefix()));
-
- configClass = configClass.withBody(configClass.getBody().withStatements(statements));
- }
- }
-
- return configClass;
- }
-
- private List autowiredFields(J.ClassDecl component) {
- return component.getFields().stream()
- .filter(f -> f.getAnnotations().stream()
- .anyMatch(ann ->
- TypeUtils.hasElementType(ann.getType(), "org.springframework.beans.factory.annotation.Autowired") ||
- TypeUtils.hasElementType(ann.getType(), "javax.inject.Inject")
- )
- )
- .collect(toList());
- }
-
- private List constructorInjectedFields(J.ClassDecl component) {
- return component.getMethods().stream()
- .filter(J.MethodDecl::isConstructor)
- .max(Comparator.comparing(m -> m.getParams().getParams().size()))
- .map(m -> m.getParams().getParams().stream()
- .filter(J.VariableDecls.class::isInstance)
- .map(J.VariableDecls.class::cast)
- .collect(toList())
- )
- .orElse(emptyList());
- }
- }
-
- @Override
- public J visitClassDecl(J.ClassDecl classDecl) {
- J.ClassDecl c = refactor(classDecl, super::visitClassDecl);
-
- JavaType.Class classType = TypeUtils.asClass(classDecl.getType());
- if (classType != null && classType.getFullyQualifiedName().equals(configurationClass)) {
- existingConfiguration = getCursor().firstEnclosing(J.CompilationUnit.class);
- return c;
- }
-
- if (!classDecl.findAnnotationsOnClass("@org.springframework.boot.autoconfigure.SpringBootApplication").isEmpty()) {
- springBootApplication = getCursor().firstEnclosing(J.CompilationUnit.class);
- }
-
- List components = Stream.concat(
- classDecl.findAnnotationsOnClass("@org.springframework.stereotype.Component").stream(),
- Stream.concat(
- classDecl.findAnnotationsOnClass("@org.springframework.stereotype.Repository").stream(),
- classDecl.findAnnotationsOnClass("@org.springframework.stereotype.Service").stream()
- )
- ).collect(toList());
-
- if (!components.isEmpty()) {
- List annotations = new ArrayList<>(c.getAnnotations());
- annotations.removeAll(components);
- c = c.withAnnotations(formatFirstPrefix(annotations, c.getAnnotations().get(0).getFormatting().getPrefix()));
- componentsToCreateBeansDefinitionsFor.add(c);
- }
-
- return c;
- }
-}
diff --git a/tmp/ComponentToBeanConfigurationTest.kt b/tmp/ComponentToBeanConfigurationTest.kt
deleted file mode 100644
index d014985bf..000000000
--- a/tmp/ComponentToBeanConfigurationTest.kt
+++ /dev/null
@@ -1,203 +0,0 @@
-/*
- * 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.RefactorVisitor
-import org.openrewrite.RefactorVisitorTestForParser
-import org.openrewrite.java.JavaParser
-import org.openrewrite.java.tree.J
-import java.nio.file.Path
-
-class ComponentToBeanConfigurationTest(
- override val parser: JavaParser = JavaParser.fromJavaVersion()
- .classpath("spring-boot-autoconfigure", "spring-beans", "spring-context")
- .build(),
- override val visitors: Iterable> = listOf(
- ComponentToBeanConfiguration().apply {
- setConfigurationClass("MyConfiguration")
- }
- )
-) : RefactorVisitorTestForParser {
-
- private val app = "io/moderne/app/MyApplication" to """
- package io.moderne.app;
-
- import org.springframework.boot.autoconfigure.SpringBootApplication;
-
- @SpringBootApplication
- public class MyApplication {
- }
- """.trimIndent()
-
- private val component = "io/moderne/app/components/MyComponent" to """
- package io.moderne.app.components;
-
- import org.springframework.stereotype.Component;
-
- @Component
- public class MyComponent {
- }
- """.trimIndent()
-
- private val repository = "io/moderne/app/repositories/MyRepository" to """
- package io.moderne.app.repositories;
-
- import org.springframework.stereotype.Repository;
-
- @Repository
- public class MyRepository {
- }
- """.trimIndent()
-
- private val service = "io/moderne/app/services/MyService" to """
- package io.moderne.app.services;
-
- import io.moderne.app.repositories.MyRepository;
- import org.springframework.stereotype.Service;
-
- @Service
- public class MyService {
- private final MyRepository repo;
-
- public MyService(MyRepository repo) {
- this.repo = repo;
- }
- }
- """.trimIndent()
-
-// @Test
-// fun beanWithNoCollaborators(@TempDir tempDir: Path) {
-// parse(tempDir, app, component).map { cu ->
-// assertThat(cu.refactor().visit(visitor).fix().fixed.printTrimmed()).doesNotContain("@Component")
-// }
-//
-// val generated = visitor.generated
-//
-// assertThat(generated).isNotNull()
-//
-// assertRefactored(generated!!, """
-// package io.moderne.app;
-//
-// import io.moderne.app.components.MyComponent;
-// import org.springframework.context.annotation.Configuration;
-//
-// @Configuration
-// public class MyConfiguration {
-//
-// @Bean
-// MyComponent myComponent() {
-// return new MyComponent();
-// }
-// }
-// """)
-// }
-
-// @Test
-// fun beanWithConstructorInjectableCollaborators(@TempDir tempDir: Path) {
-// parse(tempDir, app, repository, service).map { cu ->
-// assertThat(cu.refactor().visit(visitor).fix().fixed.printTrimmed()).doesNotContain("@Component")
-// }
-//
-// val generated = visitor.generated
-//
-// assertThat(generated).isNotNull()
-//
-// assertRefactored(generated!!, """
-// package io.moderne.app;
-//
-// import io.moderne.app.repositories.MyRepository;
-// import io.moderne.app.services.MyService;
-// import org.springframework.context.annotation.Configuration;
-//
-// @Configuration
-// public class MyConfiguration {
-//
-// @Bean
-// MyRepository myRepository() {
-// return new MyRepository();
-// }
-//
-// @Bean
-// MyService myService(MyRepository repo) {
-// return new MyService(repo);
-// }
-// }
-// """)
-// }
-//
-// @Test
-// fun beanWithFieldInjectedCollaborators(@TempDir tempDir: Path) {
-// val serviceFieldInjectable = "io/moderne/app/services/MyService" to """
-// package io.moderne.app.services;
-//
-// import io.moderne.app.repositories.MyRepository;
-// import org.springframework.beans.factory.annotation.Autowired;
-// import org.springframework.stereotype.Service;
-//
-// @Service
-// public class MyService {
-// @Autowired
-// private MyRepository repo;
-//
-// public void setRepo(MyRepository repo) {
-// this.repo = repo;
-// }
-// }
-// """.trimIndent()
-//
-// parse(tempDir, app, repository, serviceFieldInjectable).map { cu ->
-// assertThat(cu.refactor().visit(visitor).fix().fixed.printTrimmed()).doesNotContain("@Component")
-// }
-//
-// val generated = visitor.generated
-//
-// assertThat(generated).isNotNull()
-//
-// assertRefactored(generated!!, """
-// package io.moderne.app;
-//
-// import io.moderne.app.repositories.MyRepository;
-// import io.moderne.app.services.MyService;
-// import org.springframework.context.annotation.Configuration;
-//
-// @Configuration
-// public class MyConfiguration {
-//
-// @Bean
-// MyRepository myRepository() {
-// return new MyRepository();
-// }
-//
-// @Bean
-// MyService myService(MyRepository repo) {
-// MyService myService = new MyService();
-// myService.setRepo(repo);
-// return myService;
-// }
-// }
-// """)
-// }
-
- private fun parse(root: Path, vararg sources: Pair): List = parser.parse(
- sources.map { s ->
- val sourcePath = root.resolve(Path.of(s.first + ".java"))
- sourcePath.parent.toFile().mkdirs()
- sourcePath.toFile().writeText(s.second)
- sourcePath
- },
- null
- )
-}
diff --git a/tmp/ConstructorInjection.java b/tmp/ConstructorInjection.java
deleted file mode 100644
index 1ed73d5e9..000000000
--- a/tmp/ConstructorInjection.java
+++ /dev/null
@@ -1,144 +0,0 @@
-/*
- * 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.AutoConfigure;
-import org.openrewrite.java.AddAnnotation;
-import org.openrewrite.java.GenerateConstructorUsingFields;
-import org.openrewrite.java.JavaParser;
-import org.openrewrite.java.JavaRefactorVisitor;
-import org.openrewrite.java.tree.J;
-import org.openrewrite.java.tree.JavaType;
-
-import java.util.List;
-import java.util.Set;
-
-import static java.util.stream.Collectors.toList;
-import static java.util.stream.Collectors.toSet;
-import static org.openrewrite.Formatting.firstPrefix;
-import static org.openrewrite.Formatting.formatFirstPrefix;
-import static org.openrewrite.Tree.randomId;
-import static org.openrewrite.java.tree.TypeUtils.isOfClassType;
-
-@AutoConfigure
-public class ConstructorInjection extends JavaRefactorVisitor {
- private final JavaParser javaParser = JavaParser.fromJavaVersion().build();
-
- private boolean useLombokRequiredArgsAnnotation = false;
- private boolean useJsr305Annotations = false;
-
- public void setUseJsr305Annotations(boolean useJsr305Annotations) {
- this.useJsr305Annotations = useJsr305Annotations;
- }
-
- public void setUseLombokRequiredArgsAnnotation(boolean useLombokRequiredArgsAnnotation) {
- this.useLombokRequiredArgsAnnotation = useLombokRequiredArgsAnnotation;
- }
-
- @Override
- public J visitClassDecl(J.ClassDecl classDecl) {
- J.ClassDecl cd = refactor(classDecl, super::visitClassDecl);
-
- if (cd.getFields().stream().anyMatch(this::isFieldInjected)) {
- List statements = cd.getBody().getStatements().stream()
- .map(stat -> stat.whenType(J.VariableDecls.class)
- .filter(this::isFieldInjected)
- .map(mv -> {
- J.VariableDecls fixedField = mv
- .withAnnotations(mv.getAnnotations().stream()
- .filter(ann -> !isFieldInjectionAnnotation(ann) ||
- (useJsr305Annotations && ann.getArgs() != null && ann.getArgs().getArgs().stream()
- .anyMatch(arg -> arg.whenType(J.Assign.class)
- .map(assign -> ((J.Ident) assign.getVariable()).getSimpleName().equals("required"))
- .orElse(false))))
- .map(ann -> {
- if (isFieldInjectionAnnotation(ann)) {
- maybeAddImport("javax.annotation.Nonnull");
-
- JavaType.Class nonnullType = JavaType.Class.build("javax.annotation.Nonnull");
- return ann
- .withAnnotationType(J.Ident.build(randomId(), "Nonnull", nonnullType,
- ann.getAnnotationType().getFormatting()))
- .withArgs(null)
- .withType(nonnullType);
- }
- return ann;
- })
- .collect(toList()))
- .withModifiers("private", "final");
-
- maybeRemoveImport("org.springframework.beans.factory.annotation.Autowired");
-
- return (J) (fixedField.getAnnotations().isEmpty() && !mv.getAnnotations().isEmpty() ?
- fixedField.withModifiers(formatFirstPrefix(fixedField.getModifiers(),
- firstPrefix(mv.getAnnotations()))) :
- fixedField);
- })
- .orElse(stat))
- .collect(toList());
-
- if (!hasRequiredArgsConstructor(cd)) {
- andThen(useLombokRequiredArgsAnnotation ?
- new AddAnnotation.Scoped(cd, "lombok.RequiredArgsConstructor") :
- new GenerateConstructorUsingFields.Scoped(cd, getInjectedFields(cd)));
- }
-
- List setterNames = getInjectedFields(cd).stream()
- .map(mv -> {
- String name = mv.getVars().get(0).getSimpleName();
- return "set" + name.substring(0, 1).toUpperCase() + name.substring(1);
- })
- .collect(toList());
-
- cd = cd.withBody(cd.getBody().withStatements(statements.stream()
- .filter(stat -> stat.whenType(J.MethodDecl.class)
- .map(md -> !setterNames.contains(md.getSimpleName()))
- .orElse(true))
- .collect(toList())));
- }
-
- return cd;
- }
-
- private boolean hasRequiredArgsConstructor(J.ClassDecl cd) {
- Set injectedFieldNames = getInjectedFields(cd).stream().map(f -> f.getVars().get(0).getSimpleName()).collect(toSet());
-
- return cd.getBody().getStatements().stream().anyMatch(stat -> stat.whenType(J.MethodDecl.class)
- .filter(J.MethodDecl::isConstructor)
- .map(md -> md.getParams().getParams().stream()
- .map(p -> p.whenType(J.VariableDecls.class)
- .map(mv -> mv.getVars().get(0).getSimpleName())
- .orElseThrow(() -> new RuntimeException("not possible to get here")))
- .allMatch(injectedFieldNames::contains))
- .orElse(false));
- }
-
- private List getInjectedFields(J.ClassDecl cd) {
- return cd.getBody().getStatements().stream()
- .filter(stat -> stat.whenType(J.VariableDecls.class).map(this::isFieldInjected).orElse(false))
- .map(J.VariableDecls.class::cast)
- .collect(toList());
- }
-
- private boolean isFieldInjected(J.VariableDecls mv) {
- return mv.getAnnotations().stream().anyMatch(this::isFieldInjectionAnnotation);
- }
-
- private boolean isFieldInjectionAnnotation(J.Annotation ann) {
- return isOfClassType(ann.getType(), "javax.inject.Inject") ||
- isOfClassType(ann.getType(), "org.springframework.beans.factory.annotation.Autowired");
- }
-}
diff --git a/tmp/ConstructorInjectionTest.kt b/tmp/ConstructorInjectionTest.kt
deleted file mode 100644
index 7582753db..000000000
--- a/tmp/ConstructorInjectionTest.kt
+++ /dev/null
@@ -1,119 +0,0 @@
-/*
- * 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.junit.jupiter.api.Test
-import org.openrewrite.RefactorVisitor
-import org.openrewrite.RefactorVisitorTestForParser
-import org.openrewrite.java.JavaParser
-import org.openrewrite.java.tree.J
-
-class ConstructorInjectionTest : RefactorVisitorTestForParser {
-
- override val parser: JavaParser = JavaParser.fromJavaVersion()
- .classpath("spring-beans")
- .build()
- override val visitors: Iterable> = listOf()
-
- @Test
- fun constructorInjection() = assertRefactored(
- visitors = listOf(ConstructorInjection()),
- before = """
- import org.springframework.beans.factory.annotation.Autowired;
-
- public class UsersController {
- @Autowired
- private UsersService usersService;
-
- @Autowired
- UsernameService usernameService;
-
- public void setUsersService(UsersService usersService) {
- this.usersService = usersService;
- }
- }
- """,
- after = """
- public class UsersController {
- private final UsersService usersService;
-
- private final UsernameService usernameService;
-
- public UsersController(UsersService usersService, UsernameService usernameService) {
- this.usersService = usersService;
- this.usernameService = usernameService;
- }
- }
- """
- )
-
- @Test
- fun constructorInjectionWithLombok() = assertRefactored(
- visitors = listOf(ConstructorInjection().apply { setUseLombokRequiredArgsAnnotation(true) }),
- before = """
- import org.springframework.beans.factory.annotation.Autowired;
-
- public class UsersController {
- @Autowired
- private UsersService usersService;
-
- @Autowired
- UsernameService usernameService;
- }
- """,
- after = """
- import lombok.RequiredArgsConstructor;
-
- @RequiredArgsConstructor
- public class UsersController {
- private final UsersService usersService;
-
- private final UsernameService usernameService;
- }
- """
- )
-
- @Test
- fun constructorInjectionWithJSR305() = assertRefactored(
- visitors = listOf(ConstructorInjection().apply { setUseJsr305Annotations(true) }),
- before = """
- import org.springframework.beans.factory.annotation.Autowired;
-
- public class UsersController {
- @Autowired(required = false)
- private UsersService usersService;
-
- @Autowired
- UsernameService usernameService;
- }
- """,
- after = """
- import javax.annotation.Nonnull;
-
- public class UsersController {
- @Nonnull
- private final UsersService usersService;
-
- private final UsernameService usernameService;
-
- public UsersController(UsersService usersService, UsernameService usernameService) {
- this.usersService = usersService;
- this.usernameService = usernameService;
- }
- }
- """
- )
-}
diff --git a/tmp/ValueToConfigurationProperties.java b/tmp/ValueToConfigurationProperties.java
deleted file mode 100644
index b96cf6649..000000000
--- a/tmp/ValueToConfigurationProperties.java
+++ /dev/null
@@ -1,907 +0,0 @@
-/*
- * 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.boot2;
-
-import org.openrewrite.AutoConfigure;
-import org.openrewrite.Formatting;
-import org.openrewrite.SourceFile;
-import org.openrewrite.java.*;
-import org.openrewrite.java.tree.J;
-import org.openrewrite.java.tree.JavaType;
-import org.openrewrite.java.tree.Statement;
-
-import java.nio.file.Path;
-import java.nio.file.Paths;
-import java.util.*;
-import java.util.stream.Collectors;
-import java.util.stream.Stream;
-
-import static org.openrewrite.Formatting.EMPTY;
-import static org.openrewrite.Tree.randomId;
-import static org.openrewrite.internal.StringUtils.capitalize;
-import static org.openrewrite.internal.StringUtils.uncapitalize;
-
-/**
- * Notes on ValueToConfigurationProperties
- *
- * 1. Scanning phase: Visit class fields, constructors for @Value annotations create a tree of @Value annotation contents.
- * Given @Values containing these property paths:
- * app.config.bar, app.config.foo, screen.resolution.horizontal, screen.resolution.vertical, screen.refresh-rate
- * The resulting tree should be:
- *
- * root
- * / \
- * app screen
- * / / \
- * config resolution refreshRate
- * / / \
- * bar horizontal vertical
- *
- * Store list of classes where every field is @Value annotated as it can be reused instead of generating a new class
- * Store a list of any existing @ConfigurationProperties classes
- * Record list of fields whose names don't match the last piece of their @Value annotations.
- * Leaf nodes of tree have links back to their original appearance(s)
- *
- * 1.b.: Config Class Generation:
- * For each subtree where there is not an existing ConfigurationProperties class, create a new (empty) ConfigurationProperties class
- * Any new classes should be placed adjacent in the source tree to the Spring Application class
- *
- * 2. Config Class Update Phase:
- * Go through the config classes and create fields, getters, setters, corresponding to each node of the tree
- *
- * 3. Reference Update phase:
- * Go through ALL classes and anywhere anything @Value annotated appears, replace it with the corresponding @ConfigurationProperties type.
- * May involve collapsing multiple arguments into a single argument, updating references to those arguments
- */
-@AutoConfigure
-public class ValueToConfigurationProperties extends JavaRefactorVisitor {
-
- private static final String valueAnnotationSignature = "@org.springframework.beans.factory.annotation.Value";
- private static final String configurationPropertiesFqn = "org.springframework.boot.context.properties.ConfigurationProperties";
- private static final String configurationPropertiesSignature = "@" + configurationPropertiesFqn;
- private static final JavaType.Class configurationPropertiesAnnotationType = JavaType.Class.build(
- configurationPropertiesFqn,
- Collections.emptyList(),
- Collections.emptyList(),
- Collections.singletonList(JavaType.Class.build("java.lang.annotation.Annotation")),
- Collections.emptyList(),
- null);
- private static final String springBootApplicationSignature = "@org.springframework.boot.autoconfigure.SpringBootApplication";
-
- // Visible for testing
- PrefixParentNode prefixTree = PrefixTree.build();
- J.CompilationUnit springBootApplication;
- private boolean configClassesGenerated = false;
- JavaParser jp;
- /**
- * Keep track of any classes generated by name.
- * Will just be the stubs, before any fields/getters/setters have been filled in
- */
- private Map generatedClasses;
-
- public ValueToConfigurationProperties() {
- setCursoringOn();
- }
-
- @Override
- public J visitCompilationUnit(J.CompilationUnit cu) {
- if (configClassesGenerated && springBootApplication != null) {
- andThen(new PopulateConfigurationPropertiesClasses());
- andThen(new UpdateReferences());
- andThen(new RemoveValueAnnotations());
- }
- return super.visitCompilationUnit(cu);
- }
-
- @Override
- public J visitClassDecl(J.ClassDecl classDecl) {
- if (!configClassesGenerated) {
- classDecl.getFields()
- .forEach(prefixTree::put);
- classDecl.getMethods()
- .forEach(prefixTree::put);
- // Any generated config classes will adopt the package of the Spring Boot Application class
- // and be placed adjacent to it in the source tree
- if (classDecl.findAnnotations(springBootApplicationSignature).size() > 0) {
- J.CompilationUnit cu = getCursor().firstEnclosing(J.CompilationUnit.class);
- assert cu != null;
- springBootApplication = cu;
- jp = cu.buildParser();
- }
- }
- return super.visitClassDecl(classDecl);
- }
-
-
- @Override
- public Collection generate() {
- if(!configClassesGenerated && springBootApplication != null) {
- generatedClasses = new HashMap<>();
- configClassesGenerated = true;
- return prefixTree.getLongestCommonPrefixes().stream().map( commonPrefix -> {
-
- String className = prefixTree.get(commonPrefix).getEnclosingClassName();
- String peckage = (springBootApplication.getPackageDecl() == null) ? "" : springBootApplication.getPackageDecl().printTrimmed() + ";\n\n";
- String newClassText = peckage +
- "import org.springframework.boot.context.properties.ConfigurationProperties;\n\n" +
- "@ConfigurationProperties(\"" + String.join(".", commonPrefix) + "\")\n" +
- "public class " + className + " {\n" +
- "}\n";
- Path parentDir = springBootApplication.getSourcePath().getParent();
- Path sourcePath;
- if(parentDir == null) {
- sourcePath = Paths.get(className + ".java");
- } else {
- sourcePath = parentDir.resolve(className + ".java");
- }
- J.CompilationUnit cu = fillConfigurationPropertiesTypeAttribution(
- jp.reset().parse(newClassText)
- .get(0)
- .withSourcePath(sourcePath));
- generatedClasses.put(className, cu);
- return cu;
-
- }).collect(Collectors.toList());
- } else {
- return Collections.emptyList();
- }
- }
- private static J.CompilationUnit fillConfigurationPropertiesTypeAttribution(J.CompilationUnit cu) {
- return (J.CompilationUnit) new FillTypeAttributions(new JavaType.Class[]{configurationPropertiesAnnotationType}).visitCompilationUnit(cu);
- }
-
- private class PopulateConfigurationPropertiesClasses extends JavaRefactorVisitor {
- @Override
- public J visitClassDecl(J.ClassDecl classDecl) {
- J.ClassDecl cd = refactor(classDecl, super::visitClassDecl);
- List configPropsAnnotations = cd.findAnnotations(configurationPropertiesSignature);
- if (configPropsAnnotations.size() > 0) {
- J.Annotation configPropsAnnotation = configPropsAnnotations.get(0);
- if (configPropsAnnotation.getArgs() == null) {
- return cd;
- }
- String configPropsPrefix = (String) ((J.Literal) configPropsAnnotation.getArgs().getArgs().get(0)).getValue();
- if (configPropsPrefix == null) {
- return cd;
- }
- PrefixParentNode treeForConfigPropsClass = (PrefixParentNode)prefixTree.get(configPropsPrefix);
- for(PrefixTree pt : treeForConfigPropsClass.children.values()) {
- generateClassesFieldsGettersSetters(cd, pt);
- }
- }
- return cd;
- }
-
- /**
- * Based on the contents of the supplied PrefixTree, create inner classes, fields, getters, and setters
- * Given a class declaration like:
- * class Example {}
- * and a PrefixTree like this, assuming all properties are Strings
- * root
- * |
- * app
- * |
- * config
- * / \
- * foo bar
- *
- * Example will end up looking similar to the following (whitespace and ordering of fields/methods not guaranteed):
- *
- * class Example {
- * private AppConfig appConfig;
- * public AppConfig getAppConfig() { return appConfig; }
- * public void setAppConfig(AppConfig value) { appConfig = value; }
- * public static class AppConfig {
- * private String foo;
- * public String getFoo() { return foo; }
- * public void setFoo(String value) { foo = value; }
- * private String bar;
- * public String getBar() { return bar; }
- * public void setBar(String value) { bar = value; }
- * }
- * }
- */
- private void generateClassesFieldsGettersSetters(J.ClassDecl cd, PrefixTree pt) {
- if(pt instanceof PrefixTerminalNode) {
- PrefixTerminalNode node = (PrefixTerminalNode) pt;
- String fieldName = node.name;
- String fieldTypeExpr = node.type.toTypeTree().printTrimmed();
- andThen(new AddField.Scoped(cd,
- Collections.singletonList(new J.Modifier.Private(randomId(), EMPTY)),
- fieldTypeExpr,
- fieldName,
- null));
- andThen(new GenerateGetter.Scoped(cd, fieldName));
- andThen(new GenerateSetter.Scoped(cd, fieldName));
- } else if(pt instanceof PrefixParentNode) {
- // Search through any inner class declarations that may exist, use existing declaration if one exists
- String fieldName = pt.getName();
- String innerClassName = capitalize(fieldName);
- Optional maybeInnerClassDecl = cd.getBody().getStatements().stream()
- .filter(it -> it instanceof J.ClassDecl)
- .map(J.ClassDecl.class::cast)
- .filter(it -> it.getSimpleName().equals(innerClassName))
- .findAny();
-
- J.ClassDecl innerClassDecl;
- if(maybeInnerClassDecl.isPresent()) {
- innerClassDecl = maybeInnerClassDecl.get();
- } else {
- innerClassDecl = treeBuilder.buildInnerClassDeclaration(
- cd,
- "public static class " + innerClassName + " {\n}\n");
- List withNewDecl = cd.getBody().getStatements();
- withNewDecl.add(innerClassDecl);
- cd = cd.withBody(cd.getBody().withStatements(withNewDecl));
- andThen(new AutoFormat(innerClassDecl));
- }
- assert innerClassDecl.getType() != null;
- andThen(new AddField.Scoped(cd,
- Collections.singletonList(new J.Modifier.Private(randomId(), EMPTY)),
- innerClassDecl.getType().getFullyQualifiedName(),
- fieldName,
- null));
- andThen(new GenerateGetter.Scoped(cd, fieldName));
- andThen(new GenerateSetter.Scoped(cd, fieldName));
- // There needs to be a field and getter/setter for the inner class. Any of these may already exist
-
- // Recurse on any children
- ((PrefixParentNode) pt).children.values().forEach(subTree -> generateClassesFieldsGettersSetters(innerClassDecl, subTree));
- }
- }
- }
-
- /**
- * Where fields or constructor parameters previously had @Value annotations, this will update them
- * to be initialized via ConfigurationProperties classes passed into their constructor(s)
- */
- private class UpdateReferences extends JavaRefactorVisitor {
- @Override
- public J visitClassDecl(J.ClassDecl classDecl) {
- J.ClassDecl cd = refactor(classDecl, super::visitClassDecl);
- List valueAnnotatedFields = cd.getFields().stream()
- .filter(it -> it.findAnnotations(valueAnnotationSignature).size() > 0)
- .collect(Collectors.toList());
- List valueAnnotatedConstructors = cd.getMethods().stream()
- .filter(it -> it.getReturnTypeExpr() == null)
- .filter(it -> it.findAnnotations(valueAnnotationSignature).size() > 0)
- .collect(Collectors.toList());
- if(valueAnnotatedFields.size() > 0 || valueAnnotatedConstructors.size() > 0) {
- andThen(new Scoped(cd, valueAnnotatedFields, valueAnnotatedConstructors));
- }
- return cd;
- }
- private class Scoped extends JavaRefactorVisitor {
- final J.ClassDecl scope;
- final List valueAnnotatedFields;
- final List valueAnnotatedConstructors;
-
- private Scoped(J.ClassDecl scope, List valueAnnotatedFields, List valueAnnotatedConstructors) {
- this.scope = scope;
- this.valueAnnotatedFields = valueAnnotatedFields;
- this.valueAnnotatedConstructors = valueAnnotatedConstructors;
- setCursoringOn();
- }
-
- @Override
- public J visitCompilationUnit(J.CompilationUnit cu) {
-
- return super.visitCompilationUnit(cu);
- }
-
- @Override
- public J visitClassDecl(J.ClassDecl classDecl) {
- J.ClassDecl cd = refactor(classDecl, super::visitClassDecl);
- if(!scope.isScope(cd)) {
- return cd;
- }
- List constructors = cd.getMethods().stream()
- .filter(it -> it.getReturnTypeExpr() == null)
- .collect(Collectors.toList());
-
- // We'll put all the constructors back in, later
- List bodyStatementsWithoutConstructors = cd.getBody().getStatements();
- bodyStatementsWithoutConstructors.removeAll(constructors);
-
- // If there's no explicit constructor, add one
- if(constructors.size() == 0) {
- J.MethodDecl newConstructor = treeBuilder.buildMethodDeclaration(cd,
- "\npublic " + cd.getSimpleName() + "() {\n}\n\n");
- andThen(new AutoFormat(newConstructor));
- List withNewConstructor = cd.getBody().getStatements();
- withNewConstructor.add(0, newConstructor);
- cd = cd.withBody(cd.getBody().withStatements(withNewConstructor));
-
- constructors.add(newConstructor);
- }
- // Get list of each @ConfigurationProperties class that needs to be passed in to the constructors for this class
- List prefixes = Stream.concat(
- valueAnnotatedFields.stream()
- .flatMap(field -> field.findAnnotations(valueAnnotationSignature).stream()),
- valueAnnotatedConstructors.stream()
- .flatMap(constructor -> constructor.getParams().getParams().stream()
- .filter(decl -> decl instanceof J.VariableDecls)
- .map(J.VariableDecls.class::cast)
- .flatMap(decl -> decl.findAnnotations(valueAnnotationSignature).stream()))
- )
- .filter(Objects::nonNull)
- .map(ValueToConfigurationProperties::getValueValue)
- .distinct()
- .map(it -> prefixTree.get(it))
- .collect(Collectors.toList());
-
- List configPropsCompilationUnits = prefixes.stream()
- .map(PrefixTree::getEnclosingClassName)
- .distinct()
- .map(generatedClasses::get)
- .collect(Collectors.toList());
-
- // Add imports for the config classes
- configPropsCompilationUnits.stream()
- .map(cu -> cu.getClasses().get(0).getType())
- .filter(Objects::nonNull)
- .map(JavaType.FullyQualified::getFullyQualifiedName)
- .forEach(this::maybeAddImport);
-
- // There might be a more generic "add parameter to method declaration" visitor to be extracted from this
- final J.ClassDecl finalCd = cd;
- constructors = constructors.stream()
- .map(constructor -> {
- // Add parameters for each ConfigurationProperties class to each constructor
- for (J.CompilationUnit configPropsCu : configPropsCompilationUnits) {
- J.MethodDecl.Parameters parameters = constructor.getParams();
- List parameterStatements = parameters.getParams();
- J.ClassDecl configPropsClass = configPropsCu.getClasses().get(0);
- J.VariableDecls newParam = treeBuilder.buildFieldDeclaration(finalCd,
- configPropsClass.getSimpleName() + " " + uncapitalize(configPropsClass.getSimpleName() + ";"),
- configPropsClass.getType()
- )
- .withFormatting(Formatting.format(" "));
- parameterStatements.add(newParam);
- parameters = parameters.withParams(parameterStatements);
- if(constructor.getModifiers().size() > 0) {
- // Make sure there's a space between the access modifier and the name of the constructor
- constructor = constructor.withName(constructor.getName().withFormatting(Formatting.format(" ")));
- }
- constructor = constructor.withParams(parameters);
- }
- return constructor;
- })
- .map(constructor -> {
- /*
- * Move any @Value annotated constructor parameters to be variables internal to the constructor
- * So something like this:
- * public A(@Value("${foo.bar}") String param) {
- * // does something with param
- * }
- * Becomes:
- * public A() {
- * String param = fooConfiguration.getBar();
- * // still able to do stuff with param
- * }
- *
- * Subsequent steps within this chain of map() calls will add the "fooConfiguration" parameter
- */
- J.MethodDecl.Parameters parameters = constructor.getParams();
- List valueAnnotatedParams = parameters.getParams().stream()
- .filter(param -> param instanceof J.VariableDecls)
- .map(J.VariableDecls.class::cast)
- .filter(param -> param.findAnnotations(valueAnnotationSignature).size() > 0)
- .collect(Collectors.toList());
-
- List assignmentStatementsToAdd = valueAnnotatedParams.stream()
- .map(param -> {
- PrefixTerminalNode node = prefixTree.get(param);
- String initializingExpression = node.getInitializingExpression();
- assert param.getTypeExpr() != null;
- return (J.VariableDecls) treeBuilder.buildSnippet(getCursor(),
- param.getTypeExpr().print() + " " + param.getVars().get(0).getSimpleName() + " = " + initializingExpression + ";",
- param.getTypeAsClass()).get(0);
- })
- .collect(Collectors.toList());
- J.Block body = getBodyOrEmptyBlock(constructor);
- List statements = body.getStatements();
- for(J.VariableDecls statementToAdd : assignmentStatementsToAdd) {
- // Inserting at the beginning so the assignments will come before any later statement which may reference them
- // But what if the constructor body begins with a this() or super() call?
- // That case isn't handled right now
- statements.add(0, statementToAdd);
- }
- constructor = constructor.withBody(body.withStatements(statements));
-
- // Remove any parameters which were annotated with @Value
- List withoutValueParams = parameters.getParams();
- withoutValueParams.removeAll(valueAnnotatedParams);
- return constructor.withParams(parameters.withParams(withoutValueParams));
- })
- .map(constructor -> {
- // Add statements to the body of the constructor initializing all @Value annotated fields
- J.Block body = getBodyOrEmptyBlock(constructor);
- List statements = body.getStatements();
- for(J.VariableDecls field : valueAnnotatedFields) {
- PrefixTerminalNode node = prefixTree.get(field);
- String initializingExpression = node.getInitializingExpression();
- J.Assign assignment = (J.Assign) treeBuilder.buildSnippet(getCursor(),
- "this." + field.getVars().get(0).getSimpleName() + " = " + initializingExpression + ";",
- field.getTypeExpr().getType()).get(0);
-
- // Area to improve:
- // None of the statements/expressions in the above snippet are going to come out
- // type-attributed correctly. Which could cause confusion/trouble for visitors coming later
- statements.add(assignment);
- }
- // Make sure the "}" doesn't end up on the same line as the final statement
- body = body.withStatements(statements)
- .withEnd(body.getEnd().withPrefix("\n"));
- return constructor.withBody(body);
- })
- .collect(Collectors.toList());
- // Put the modified constructors back into the class body and clean up their formatting
- constructors.forEach(it -> andThen(new AutoFormat(it)));
- bodyStatementsWithoutConstructors.addAll(0, constructors);
- cd = cd.withBody(cd.getBody().withStatements(bodyStatementsWithoutConstructors));
-
- return cd;
- }
- }
- }
-
- /**
- * Finally removes @Value annotations wherever they may be found.
- */
- private class RemoveValueAnnotations extends JavaRefactorVisitor {
- @Override
- public J visitMultiVariable(J.VariableDecls multiVariable) {
- J.VariableDecls mv = refactor(multiVariable, super::visitMultiVariable);
- List valueAnnotations = mv.findAnnotations(valueAnnotationSignature);
- if(valueAnnotations.size() > 0) {
- List otherAnnotations = mv.getAnnotations();
- otherAnnotations.removeAll(valueAnnotations);
- mv = mv.withAnnotations(otherAnnotations);
- }
- return mv;
- }
-
- @Override
- public J visitMethod(J.MethodDecl method) {
- J.MethodDecl m = refactor(method, super::visitMethod);
- // Only applicable to constructors which are the only method declarations with no return type
- if(m.getReturnTypeExpr() == null) {
- List argsWithoutValueAnnotations = m.getParams().getParams().stream()
- .map(it -> {
- if (it instanceof J.VariableDecls) {
- J.VariableDecls param = (J.VariableDecls) it;
- List valueAnnotations = param.findAnnotations(valueAnnotationSignature);
- List otherAnnotations = param.getAnnotations();
- otherAnnotations.removeAll(valueAnnotations);
- return param.withAnnotations(otherAnnotations);
- } else {
- return it;
- }
- })
- .collect(Collectors.toList());
- m = m.withParams(m.getParams().withParams(argsWithoutValueAnnotations));
- }
- return m;
- }
- }
- /**
- * Get the existing body of the method or an empty block if the method has no body
- */
- private static J.Block getBodyOrEmptyBlock(J.MethodDecl methodDecl) {
- J.Block result = methodDecl.getBody();
- if(result == null) {
- result = new J.Block<>(randomId(), null, new ArrayList<>(), EMPTY, new J.Block.End(randomId(), EMPTY));
- }
- return result;
- }
-
- /**
- * Extracts, de-dashes, and camelCases the value string from a @Value annotation
- * Given: @Value("${app.screen.refresh-rate}")
- * Returns: app.screen.refreshRate
- */
- private static String getValueValue(J.Annotation value) {
- assert value.getArgs() != null;
- String valueValue = (String) ((J.Literal) value.getArgs().getArgs().get(0)).getValue();
- assert valueValue != null;
- valueValue = valueValue.replace("${", "")
- .replace("}", "");
- valueValue = Arrays.stream(valueValue.split("-"))
- .map(part -> Character.toUpperCase(part.charAt(0)) + part.substring(1))
- .collect(Collectors.joining(""));
- return Character.toLowerCase(valueValue.charAt(0)) + valueValue.substring(1);
- }
-
- interface PrefixTree {
- String getName();
-
- PrefixTree put(List pathSegments, JavaType type);
-
- default PrefixTree get(String path) {
- return get(Arrays.asList(path.split("\\.")));
- }
-
- PrefixTree get(List pathSegments);
-
- PrefixParentNode getParent();
-
- default boolean isRoot() { return getParent() == null; }
-
- /**
- * Get the name of the outermost class that will be generated to contain properties with the current prefix.
- * Example output:
- *
- * PrefixTree getClassName()
- * root ""
- * |
- * app "App"
- * |
- * config "AppConfigConfiguration"
- * | \
- * | prop "AppConfigConfiguration"
- * intermediate "AppConfigConfiguration"
- * |
- * terminal "AppConfigConfiguration"
- *
- */
- String getEnclosingClassName();
-
- /**
- * Get a string representation of an expression that would produce the appropriate value for this node
- *
- * PrefixTree getInitializingExpression()
- * root ""
- * |
- * app "App"
- * |
- * config "appConfigConfiguration"
- * | \
- * | prop "appConfigConfiguration.getProp()"
- * intermediate "appConfigConfiguration.getIntermediate()"
- * |
- * terminal "appConfigConfiguration.getIntermediate().getTerminal()"
- *
- */
- String getInitializingExpression();
-
- /**
- * Return true if the current node is part of the common prefix for its tree
- * A node is part of the common prefix iff:
- * 1. It is the root node OR
- * 2. Its parent is part of the common prefix AND it is the only child of its parent node
- *
- *
- * PrefixTree isPartOfCommonPrefix()
- * root true
- * |
- * app true
- * |
- * config true
- * | \
- * | prop false
- * intermediate false
- * |
- * terminal false
- *
- */
- boolean isPartOfCommonPrefix();
-
- default String getFullPath() {
- if(isRoot()) {
- return "";
- }
- if(getParent().isRoot()) {
- return getName();
- } else {
- return getParent().getFullPath() + "." + getName();
- }
- }
-
- static PrefixParentNode build() {
- return new PrefixParentNode(null, "root");
- }
- }
-
- /**
- * A node of a PrefixTree with no children of its own.
- * Keeps track of the element or elements which reference it
- */
- static class PrefixTerminalNode implements PrefixTree {
- final String name;
- final PrefixParentNode parent;
- final JavaType type;
-
- public PrefixTerminalNode(PrefixParentNode parent, String name, JavaType type) {
- this.name = name;
- this.parent = parent;
- this.type = type;
- }
-
- @Override
- public PrefixTree put(List pathSegments, JavaType type) {
- if (pathSegments == null) {
- throw new IllegalArgumentException("pathSegments may not be null");
- }
- if (type != null && this.type != type) {
- throw new IllegalArgumentException(
- "terminal node cannot have two types. type is currently recorded as \"" +
- this.type.toTypeTree().printTrimmed() + "\", cannot reassign it to \"" +
- type.toTypeTree().printTrimmed());
- }
- if (pathSegments.size() > 1) {
- throw new IllegalArgumentException("Cannot add new path segment to terminal node");
- }
- return this;
- }
-
- @Override
- public PrefixTree get(List pathSegments) {
- if (pathSegments == null) {
- throw new IllegalArgumentException("pathSegments may not be null");
- }
- if (pathSegments.size() == 0) {
- return this;
- } else if (pathSegments.size() == 1 && pathSegments.get(0).equals(name)) {
- return this;
- } else {
- throw new IllegalArgumentException(
- "Terminal node \"" + name + "\" does not match requested path \"" +
- String.join(".", pathSegments) + "\"");
- }
- }
-
- @Override
- public PrefixParentNode getParent() {
- return parent;
- }
-
- @Override
- public String getEnclosingClassName() {
- return parent.getEnclosingClassName();
- }
-
- @Override
- public String getInitializingExpression() {
- return parent.getInitializingExpression() + ".get" + capitalize(name) + "()";
- }
-
- @Override
- public boolean isPartOfCommonPrefix() {
- return false;
- }
-
- @Override
- public String getName() {
- return name;
- }
- }
-
- /**
- * A PrefixTree with children. Has no data except via its child nodes. Get an instance via PrefixTree.build()
- */
- static class PrefixParentNode implements PrefixTree {
- final String name;
- final Map children = new HashMap<>();
- final PrefixParentNode parent;
-
- private PrefixParentNode(PrefixParentNode parent, String name) {
- this.name = name;
- this.parent = parent;
- }
-
- PrefixTree buildChild(List pathSegments, JavaType type) {
- if (pathSegments.size() == 0) {
- throw new IllegalArgumentException("pathSegments may not be null");
- }
- String nodeName = pathSegments.get(0);
- List remainingSegments = pathSegments.subList(1, pathSegments.size());
- if (remainingSegments.size() == 0) {
- return new PrefixTerminalNode(this, nodeName, type);
- } else {
- PrefixParentNode intermediateNode = new PrefixParentNode(this, nodeName);
- PrefixTree child = intermediateNode.buildChild(remainingSegments, type);
- intermediateNode.children.put(child.getName(), child);
- return intermediateNode;
- }
- }
-
- void put(J.VariableDecls field) {
- List valueAnnotations = field.findAnnotations(valueAnnotationSignature);
- if (valueAnnotations.size() == 0) {
- return;
- }
- J.Annotation valueAnnotation = valueAnnotations.get(0);
- String path = getValueValue(valueAnnotation);
- List pathSegments = Arrays.asList(path.split("\\."));
- if(field.getTypeExpr() != null) {
- put(pathSegments, field.getTypeExpr().getType());
- }
- }
-
- /**
- * Get the terminal node matching the @Value annotation on the field parameter, or null if no such
- * terminal node exists or the field parameter is not @Value-annotated.
- */
- PrefixTerminalNode get(J.VariableDecls field) {
- List valueAnnotations = field.findAnnotations(valueAnnotationSignature);
- if (valueAnnotations.size() == 0) {
- return null;
- }
- J.Annotation valueAnnotation = valueAnnotations.get(0);
- String path = getValueValue(valueAnnotation);
- return (PrefixTerminalNode) get(Arrays.asList(path.split("\\.")));
- }
-
- void put(J.MethodDecl methodDecl) {
- methodDecl.getParams().getParams().stream()
- .filter(param -> param instanceof J.VariableDecls)
- .map(J.VariableDecls.class::cast)
- .filter(param -> param.findAnnotations(valueAnnotationSignature).size() > 0)
- .forEach(param -> {
- if(param.getTypeExpr() != null) {
- J.Annotation valueAnnotation = param.findAnnotations(valueAnnotationSignature).get(0);
- List pathSegments = Arrays.asList(getValueValue(valueAnnotation).split("\\."));
- put(pathSegments, param.getTypeExpr().getType());
- }
- });
- }
-
- @Override
- public PrefixTree put(List pathSegments, JavaType type) {
- if (pathSegments == null) {
- throw new IllegalArgumentException("pathSegments may not be null");
- }
- if (pathSegments.size() == 0) {
- throw new IllegalArgumentException("pathSegments may not be empty");
- }
- String nodeName = pathSegments.get(0);
-
- if (children.containsKey(nodeName)) {
- PrefixTree existingNode = children.get(nodeName);
- existingNode.put(pathSegments.subList(1, pathSegments.size()), type);
- } else {
- children.put(nodeName, buildChild(pathSegments, type));
- }
- return this;
- }
-
- @Override
- public PrefixTree get(List pathSegments) {
- if (pathSegments == null) {
- throw new IllegalArgumentException("pathSegments may not be null");
- }
- if (pathSegments.size() == 0) {
- return this;
- }
- String nodeName = pathSegments.get(0);
- List remainingSegments = pathSegments.subList(1, pathSegments.size());
- if (children.containsKey(nodeName)) {
- return children.get(nodeName).get(remainingSegments);
- } else {
- return null;
- }
- }
-
- @Override
- public PrefixParentNode getParent() {
- return parent;
- }
-
- @Override
- public String getEnclosingClassName() {
- if(isRoot()) {
- return "";
- }
- if(isPartOfCommonPrefix()) {
- String extra = "";
- if(children.size() > 1 || !children.values().stream().allMatch(it -> it instanceof PrefixParentNode && it.isPartOfCommonPrefix())) {
- extra = "Configuration";
- }
- return getParent().getEnclosingClassName() + capitalize(getName()) + extra;
- } else {
- return getParent().getEnclosingClassName();
- }
- }
-
- @Override
- public String getInitializingExpression() {
- if(isRoot()) {
- return "";
- }
- if(isPartOfCommonPrefix()) {
- String extra = "";
- String resultName = parent.getInitializingExpression() + capitalize(name);
- if(children.size() > 1 || !children.values().stream().allMatch(it -> it instanceof PrefixParentNode && it.isPartOfCommonPrefix())) {
- resultName = uncapitalize(resultName);
- extra = "Configuration";
- }
- return resultName + extra;
- } else {
- return getParent().getInitializingExpression() + ".get" + capitalize(name) + "()";
- }
- }
-
- @Override
- public boolean isPartOfCommonPrefix() {
- if(isRoot() || parent.isRoot()) {
- return true;
- }
- return parent.isPartOfCommonPrefix()
- && parent.children.size() == 1
- && parent.children.values().iterator().next() == this;
- }
-
- public boolean allChildrenTerminal() {
- return children.values().stream().allMatch(it -> it instanceof PrefixTerminalNode);
- }
-
- public boolean onlyChildIsParentNode() {
- return (children.size() == 1 && children.values().iterator().next() instanceof PrefixParentNode);
- }
-
- @Override
- public String getName() {
- return name;
- }
-
- /**
- * Return the longest paths down each branch of the tree for which there is exactly one non-terminal child
- * or no non-terminal children and any number of terminal children.
- *
- * So for a tree like:
- * root
- * / \
- * app screen
- * / / \
- * config refreshRate resolution
- * / \
- * foo bar
- *
- * This will return a list like
- * [app.config, screen]
- */
- public List> getLongestCommonPrefixes() {
- List> result = new ArrayList<>();
- for (PrefixTree subtree : children.values()) {
- if (subtree instanceof PrefixParentNode) {
- List intermediate = new ArrayList<>();
- getUntilTerminalOrMultipleChildren(intermediate, (PrefixParentNode) subtree);
- result.add(intermediate);
- } else {
- List root = Collections.singletonList("root");
- if (!result.contains(root)) {
- result.add(root);
- }
- }
- }
- return result;
- }
-
- private void getUntilTerminalOrMultipleChildren(List resultSoFar, PrefixParentNode parentNode) {
- PrefixTree node = parentNode;
- List children;
- do {
- if (!(node instanceof PrefixParentNode)) {
- break;
- }
- resultSoFar.add(node.getName());
- children = new ArrayList<>(((PrefixParentNode) node).children.values());
- node = children.get(0);
- } while (children.size() == 1 && children.get(0) instanceof PrefixParentNode);
- }
- }
-}
diff --git a/tmp/ValueToConfigurationPropertiesTest.kt b/tmp/ValueToConfigurationPropertiesTest.kt
deleted file mode 100644
index 44e8c8b2e..000000000
--- a/tmp/ValueToConfigurationPropertiesTest.kt
+++ /dev/null
@@ -1,123 +0,0 @@
-/*
- * 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.boot2
-
-import org.assertj.core.api.Assertions.assertThat
-import org.junit.jupiter.api.Test
-import org.openrewrite.*
-import org.openrewrite.java.JavaParser
-import org.openrewrite.java.tree.J
-import org.openrewrite.java.tree.JavaType
-
-class ValueToConfigurationPropertiesTest : RefactorVisitorTestForParser {
- override val parser: JavaParser = JavaParser.fromJavaVersion()
- .classpath("spring-boot", "spring-beans")
- .build()
- override val visitors: Iterable> = listOf(ValueToConfigurationProperties())
-
- @Test
- fun testBasicVTCP() {
- val springApplication = """
- package org.example;
- import org.springframework.boot.autoconfigure.SpringBootApplication;
- @SpringBootApplication
- public class ASpringBootApplication {
- public static void main(String[] args) {}
- }
- """.trimIndent()
- val classUsingValue = """
- package org.example;
- import org.springframework.beans.factory.annotation.Value;
-
- public class ExampleValueClass {
- public ExampleValueClass(@Value("${"$"}{app.config.constructor-param}") String baz) {}
- @Value("${"$"}{app.config.foo}")
- String foo;
-
- @Value("${"$"}{app.config.bar}")
- String bar;
-
- @Value("${"$"}{screen.resolution.height}")
- int height;
-
- @Value("${"$"}{screen.resolution.width}")
- int width;
-
- @Value("${"$"}{screen.refresh-rate}")
- int refreshRate;
- }
- """.trimIndent()
- val vtcp = ValueToConfigurationProperties()
- val results = Refactor()
- .visit(vtcp)
- .fix(parser.parse(classUsingValue, springApplication))
- .map { it.fixed as J.CompilationUnit }
-
- val prefixtree = vtcp.prefixTree
-
- val foo = prefixtree.get("app.config.foo")
- assertThat(foo).isNotNull
- assertThat(foo).isInstanceOf(ValueToConfigurationProperties.PrefixTerminalNode::class.java)
- val bar = prefixtree.get("app.config.bar")
- assertThat(bar).isNotNull
- assertThat(bar).isInstanceOf(ValueToConfigurationProperties.PrefixTerminalNode::class.java)
- val refreshRate = prefixtree.get("screen.refreshRate")
- assertThat(refreshRate).isNotNull
- assertThat(refreshRate).isInstanceOf(ValueToConfigurationProperties.PrefixTerminalNode::class.java)
- val width = prefixtree.get("screen.resolution.height")
- assertThat(width).isNotNull
- assertThat(width).isInstanceOf(ValueToConfigurationProperties.PrefixTerminalNode::class.java)
-
- val config = prefixtree.get("app.config")
- assertThat(config).isNotNull
- assertThat(config).isInstanceOf(ValueToConfigurationProperties.PrefixParentNode::class.java)
-
- val longestCommonPrefixPaths = prefixtree.longestCommonPrefixes
- assertThat(longestCommonPrefixPaths).isNotEmpty
- assertThat(longestCommonPrefixPaths).contains(listOf("app", "config"), listOf("screen"))
-
- // Now check out some actual results
- assertThat(results.size).isEqualTo(3)
- .`as`("There should be the generated AppConfigConfiguration and ScreenConfiguration classes, " +
- "plus a modified ExampleValueClass")
-
- val appConfig = results.find { it.classes.first().name.simpleName == "AppConfigConfiguration" }
- assertThat(appConfig).isNotNull
- val screenConfig = results.find { it.classes.first().name.simpleName == "ScreenConfiguration" }
- assertThat(screenConfig).isNotNull
- val exampleValue = results.find { it.classes.first().name.simpleName == "ExampleValueClass" }
- assertThat(exampleValue).isNotNull
-
- appConfig.assertHasMethod("getFoo", "setFoo", "getConstructorParam", "setConstructorParam")
- screenConfig.assertHasMethod("getResolution", "setResolution", "getRefreshRate", "setRefreshRate")
- // The constructor of ExampleValue should have been amended to accept an AppConfigConfiguration
- // and a ScreenConfiguration as its two arguments
- val constructors = exampleValue!!.classes.first().methods.filter { it.isConstructor }
- assertThat(constructors.size).isEqualTo(1)
- val constructor = constructors.first()
- val constructorParams = constructor.params.params
- assertThat(constructorParams.size).isEqualTo(2)
- assertThat(constructorParams.find { it.hasClassType(JavaType.Class.build("org.example.AppConfigConfiguration")) }).isNotNull
- assertThat(constructorParams.find { it.hasClassType(JavaType.Class.build("org.example.ScreenConfiguration")) }).isNotNull
- }
-
- private fun J.CompilationUnit?.assertHasMethod(vararg names: String) =
- names.forEach { name ->
- assertThat(this!!.classes.first().methods.find { it.name.simpleName == name }).isNotNull
- }
-
-
-}