From 4b14a0b42cb407276ab205221fbcea907f64cd38 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?St=C3=A9phane=20Nicoll?= Date: Tue, 10 Oct 2023 11:45:28 +0200 Subject: [PATCH 1/3] Add support for specifying compiler options This commit is a prerequisite to help suppressing deprecating warnings by allowing tests to validate that the compiler does not encounter them. See gh-29597 --- .../core/test/tools/TestCompiler.java | 60 +++++++++++--- .../core/test/tools/TestCompilerTests.java | 81 ++++++++++++++++++- 2 files changed, 127 insertions(+), 14 deletions(-) diff --git a/spring-core-test/src/main/java/org/springframework/core/test/tools/TestCompiler.java b/spring-core-test/src/main/java/org/springframework/core/test/tools/TestCompiler.java index cc468f2ef5d..5d4b04ea75b 100644 --- a/spring-core-test/src/main/java/org/springframework/core/test/tools/TestCompiler.java +++ b/spring-core-test/src/main/java/org/springframework/core/test/tools/TestCompiler.java @@ -24,6 +24,7 @@ import java.util.List; import java.util.Locale; import java.util.function.Consumer; import java.util.function.UnaryOperator; +import java.util.stream.Stream; import javax.annotation.processing.Processor; import javax.tools.Diagnostic; @@ -41,6 +42,7 @@ import org.springframework.lang.Nullable; * * @author Phillip Webb * @author Scott Frederick + * @author Stephane Nicoll * @since 6.0 * @see #forSystem() */ @@ -59,10 +61,12 @@ public final class TestCompiler { private final List processors; + private final List compilerOptions; + private TestCompiler(@Nullable ClassLoader classLoader, JavaCompiler compiler, SourceFiles sourceFiles, ResourceFiles resourceFiles, ClassFiles classFiles, - List processors) { + List processors, List compilerOptions) { this.classLoader = classLoader; this.compiler = compiler; @@ -70,6 +74,7 @@ public final class TestCompiler { this.resourceFiles = resourceFiles; this.classFiles = classFiles; this.processors = processors; + this.compilerOptions = compilerOptions; } @@ -88,7 +93,7 @@ public final class TestCompiler { */ public static TestCompiler forCompiler(JavaCompiler javaCompiler) { return new TestCompiler(null, javaCompiler, SourceFiles.none(), - ResourceFiles.none(), ClassFiles.none(), Collections.emptyList()); + ResourceFiles.none(), ClassFiles.none(), Collections.emptyList(), Collections.emptyList()); } /** @@ -108,7 +113,7 @@ public final class TestCompiler { public TestCompiler withSources(SourceFile... sourceFiles) { return new TestCompiler(this.classLoader, this.compiler, this.sourceFiles.and(sourceFiles), this.resourceFiles, - this.classFiles, this.processors); + this.classFiles, this.processors, this.compilerOptions); } /** @@ -119,7 +124,7 @@ public final class TestCompiler { public TestCompiler withSources(Iterable sourceFiles) { return new TestCompiler(this.classLoader, this.compiler, this.sourceFiles.and(sourceFiles), this.resourceFiles, - this.classFiles, this.processors); + this.classFiles, this.processors, this.compilerOptions); } /** @@ -130,7 +135,7 @@ public final class TestCompiler { public TestCompiler withSources(SourceFiles sourceFiles) { return new TestCompiler(this.classLoader, this.compiler, this.sourceFiles.and(sourceFiles), this.resourceFiles, - this.classFiles, this.processors); + this.classFiles, this.processors, this.compilerOptions); } /** @@ -140,7 +145,8 @@ public final class TestCompiler { */ public TestCompiler withResources(ResourceFile... resourceFiles) { return new TestCompiler(this.classLoader, this.compiler, this.sourceFiles, - this.resourceFiles.and(resourceFiles), this.classFiles, this.processors); + this.resourceFiles.and(resourceFiles), this.classFiles, this.processors, + this.compilerOptions); } /** @@ -150,7 +156,8 @@ public final class TestCompiler { */ public TestCompiler withResources(Iterable resourceFiles) { return new TestCompiler(this.classLoader, this.compiler, this.sourceFiles, - this.resourceFiles.and(resourceFiles), this.classFiles, this.processors); + this.resourceFiles.and(resourceFiles), this.classFiles, this.processors, + this.compilerOptions); } /** @@ -160,7 +167,8 @@ public final class TestCompiler { */ public TestCompiler withResources(ResourceFiles resourceFiles) { return new TestCompiler(this.classLoader, this.compiler, this.sourceFiles, - this.resourceFiles.and(resourceFiles), this.classFiles, this.processors); + this.resourceFiles.and(resourceFiles), this.classFiles, this.processors, + this.compilerOptions); } /** @@ -170,7 +178,8 @@ public final class TestCompiler { */ public TestCompiler withClasses(Iterable classFiles) { return new TestCompiler(this.classLoader, this.compiler, this.sourceFiles, - this.resourceFiles, this.classFiles.and(classFiles), this.processors); + this.resourceFiles, this.classFiles.and(classFiles), this.processors, + this.compilerOptions); } /** @@ -182,7 +191,7 @@ public final class TestCompiler { List mergedProcessors = new ArrayList<>(this.processors); mergedProcessors.addAll(Arrays.asList(processors)); return new TestCompiler(this.classLoader, this.compiler, this.sourceFiles, - this.resourceFiles, this.classFiles, mergedProcessors); + this.resourceFiles, this.classFiles, mergedProcessors, this.compilerOptions); } /** @@ -194,7 +203,32 @@ public final class TestCompiler { List mergedProcessors = new ArrayList<>(this.processors); processors.forEach(mergedProcessors::add); return new TestCompiler(this.classLoader, this.compiler, this.sourceFiles, - this.resourceFiles, this.classFiles, mergedProcessors); + this.resourceFiles, this.classFiles, mergedProcessors, this.compilerOptions); + } + + /** + * Create a new {@link TestCompiler} instance with the additional compiler options. + * @param options the additional compiler options + * @return a new {@code TestCompiler} instance + * @since 6.1 + */ + public TestCompiler withCompilerOptions(String... options) { + List mergedCompilerOptions = Stream.concat(this.compilerOptions.stream(), + Arrays.stream(options)).distinct().toList(); + return new TestCompiler(this.classLoader, this.compiler, this.sourceFiles, + this.resourceFiles, this.classFiles, this.processors, mergedCompilerOptions); + } + + /** + * Create a new {@link TestCompiler} instance that fails if any warning is + * encountered. This sets the {@code -Xlint:all} and {@code -Werror} compiler + * options. + * @return a new {@code TestCompiler} instance + * @since 6.1 + * @see #withCompilerOptions(String...) + */ + public TestCompiler failOnWarning() { + return withCompilerOptions("-Xlint:all", "-Werror"); } /** @@ -275,8 +309,8 @@ public final class TestCompiler { standardFileManager, classLoaderToUse, this.classFiles, this.resourceFiles); if (!this.sourceFiles.isEmpty()) { Errors errors = new Errors(); - CompilationTask task = this.compiler.getTask(null, fileManager, errors, null, - null, compilationUnits); + CompilationTask task = this.compiler.getTask(null, fileManager, errors, + this.compilerOptions, null, compilationUnits); if (!this.processors.isEmpty()) { task.setProcessors(this.processors); } diff --git a/spring-core-test/src/test/java/org/springframework/core/test/tools/TestCompilerTests.java b/spring-core-test/src/test/java/org/springframework/core/test/tools/TestCompilerTests.java index 63a3055c5ee..c921a728553 100644 --- a/spring-core-test/src/test/java/org/springframework/core/test/tools/TestCompilerTests.java +++ b/spring-core-test/src/test/java/org/springframework/core/test/tools/TestCompilerTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2022 the original author or authors. + * Copyright 2002-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. @@ -43,6 +43,7 @@ import static org.assertj.core.api.Assertions.assertThatExceptionOfType; * @author Phillip Webb * @author Andy Wilkinson * @author Scott Frederick + * @author Stephane Nicoll */ class TestCompilerTests { @@ -87,6 +88,20 @@ class TestCompilerTests { } """; + private static final String HELLO_DEPRECATED = """ + package com.example; + + import java.util.function.Supplier; + + public class Hello implements Supplier { + + @Deprecated + public String get() { + return "Hello Deprecated"; + } + + } + """; @Test @SuppressWarnings("unchecked") @@ -119,6 +134,70 @@ class TestCompilerTests { })); } + @Test + @SuppressWarnings("unchecked") + void compileWhenSourceUseDeprecateCodeAndNoOptionSet() { + SourceFile main = SourceFile.of(""" + package com.example; + + public class Main { + + public static void main(String[] args) { + new Hello().get(); + } + + } + """); + TestCompiler.forSystem().withSources( + SourceFile.of(HELLO_DEPRECATED), main).compile(compiled -> { + Supplier supplier = compiled.getInstance(Supplier.class, + "com.example.Hello"); + assertThat(supplier.get()).isEqualTo("Hello Deprecated"); + }); + } + + @Test + void compileWhenSourceUseDeprecateCodeAndFailOnWarningIsSet() { + SourceFile main = SourceFile.of(""" + package com.example; + + public class Main { + + public static void main(String[] args) { + new Hello().get(); + } + + } + """); + assertThatExceptionOfType(CompilationException.class).isThrownBy( + () -> TestCompiler.forSystem().failOnWarning().withSources( + SourceFile.of(HELLO_DEPRECATED), main).compile(compiled -> { + })).withMessageContaining("warnings found and -Werror specified"); + } + + @Test + @SuppressWarnings("unchecked") + void compileWhenSourceUseDeprecateCodeAndFailOnWarningWithSuppressWarnings() { + SourceFile main = SourceFile.of(""" + package com.example; + + public class Main { + + @SuppressWarnings("deprecation") + public static void main(String[] args) { + new Hello().get(); + } + + } + """); + TestCompiler.forSystem().failOnWarning().withSources( + SourceFile.of(HELLO_DEPRECATED), main).compile(compiled -> { + Supplier supplier = compiled.getInstance(Supplier.class, + "com.example.Hello"); + assertThat(supplier.get()).isEqualTo("Hello Deprecated"); + }); + } + @Test void withSourcesArrayAddsSource() { SourceFile sourceFile = SourceFile.of(HELLO_WORLD); From 35372e5e72f950ad07e67b9efd5ab76acb07675f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?St=C3=A9phane=20Nicoll?= Date: Tue, 10 Oct 2023 13:41:47 +0200 Subject: [PATCH 2/3] Simplify InstanceSupplierCodeGeneratorTests --- .../InstanceSupplierCodeGeneratorTests.java | 95 ++++++++----------- 1 file changed, 42 insertions(+), 53 deletions(-) diff --git a/spring-beans/src/test/java/org/springframework/beans/factory/aot/InstanceSupplierCodeGeneratorTests.java b/spring-beans/src/test/java/org/springframework/beans/factory/aot/InstanceSupplierCodeGeneratorTests.java index b849b370220..370e318bc44 100644 --- a/spring-beans/src/test/java/org/springframework/beans/factory/aot/InstanceSupplierCodeGeneratorTests.java +++ b/spring-beans/src/test/java/org/springframework/beans/factory/aot/InstanceSupplierCodeGeneratorTests.java @@ -68,18 +68,20 @@ class InstanceSupplierCodeGeneratorTests { private final TestGenerationContext generationContext; + private final DefaultListableBeanFactory beanFactory; + InstanceSupplierCodeGeneratorTests() { this.generationContext = new TestGenerationContext(); + this.beanFactory = new DefaultListableBeanFactory(); } @Test void generateWhenHasDefaultConstructor() { BeanDefinition beanDefinition = new RootBeanDefinition(TestBean.class); - DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory(); - compile(beanFactory, beanDefinition, (instanceSupplier, compiled) -> { - TestBean bean = getBean(beanFactory, beanDefinition, instanceSupplier); + compile(beanDefinition, (instanceSupplier, compiled) -> { + TestBean bean = getBean(beanDefinition, instanceSupplier); assertThat(bean).isInstanceOf(TestBean.class); assertThat(compiled.getSourceFile()) .contains("InstanceSupplier.using(TestBean::new)"); @@ -91,11 +93,9 @@ class InstanceSupplierCodeGeneratorTests { @Test void generateWhenHasConstructorWithParameter() { BeanDefinition beanDefinition = new RootBeanDefinition(InjectionComponent.class); - DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory(); - beanFactory.registerSingleton("injected", "injected"); - compile(beanFactory, beanDefinition, (instanceSupplier, compiled) -> { - InjectionComponent bean = getBean(beanFactory, beanDefinition, - instanceSupplier); + this.beanFactory.registerSingleton("injected", "injected"); + compile(beanDefinition, (instanceSupplier, compiled) -> { + InjectionComponent bean = getBean(beanDefinition, instanceSupplier); assertThat(bean).isInstanceOf(InjectionComponent.class).extracting("bean") .isEqualTo("injected"); }); @@ -107,10 +107,9 @@ class InstanceSupplierCodeGeneratorTests { void generateWhenHasConstructorWithInnerClassAndDefaultConstructor() { RootBeanDefinition beanDefinition = new RootBeanDefinition( NoDependencyComponent.class); - DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory(); - beanFactory.registerSingleton("configuration", new InnerComponentConfiguration()); - compile(beanFactory, beanDefinition, (instanceSupplier, compiled) -> { - NoDependencyComponent bean = getBean(beanFactory, beanDefinition, + this.beanFactory.registerSingleton("configuration", new InnerComponentConfiguration()); + compile(beanDefinition, (instanceSupplier, compiled) -> { + NoDependencyComponent bean = getBean(beanDefinition, instanceSupplier); assertThat(bean).isInstanceOf(NoDependencyComponent.class); assertThat(compiled.getSourceFile()).contains( @@ -124,11 +123,10 @@ class InstanceSupplierCodeGeneratorTests { void generateWhenHasConstructorWithInnerClassAndParameter() { BeanDefinition beanDefinition = new RootBeanDefinition( EnvironmentAwareComponent.class); - DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory(); - beanFactory.registerSingleton("configuration", new InnerComponentConfiguration()); - beanFactory.registerSingleton("environment", new StandardEnvironment()); - compile(beanFactory, beanDefinition, (instanceSupplier, compiled) -> { - EnvironmentAwareComponent bean = getBean(beanFactory, beanDefinition, + this.beanFactory.registerSingleton("configuration", new InnerComponentConfiguration()); + this.beanFactory.registerSingleton("environment", new StandardEnvironment()); + compile(beanDefinition, (instanceSupplier, compiled) -> { + EnvironmentAwareComponent bean = getBean(beanDefinition, instanceSupplier); assertThat(bean).isInstanceOf(EnvironmentAwareComponent.class); assertThat(compiled.getSourceFile()).contains( @@ -142,10 +140,9 @@ class InstanceSupplierCodeGeneratorTests { void generateWhenHasConstructorWithGeneric() { BeanDefinition beanDefinition = new RootBeanDefinition( NumberHolderFactoryBean.class); - DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory(); - beanFactory.registerSingleton("number", 123); - compile(beanFactory, beanDefinition, (instanceSupplier, compiled) -> { - NumberHolder bean = getBean(beanFactory, beanDefinition, instanceSupplier); + this.beanFactory.registerSingleton("number", 123); + compile(beanDefinition, (instanceSupplier, compiled) -> { + NumberHolder bean = getBean(beanDefinition, instanceSupplier); assertThat(bean).isInstanceOf(NumberHolder.class); assertThat(bean).extracting("number").isNull(); // No property actually set assertThat(compiled.getSourceFile()).contains("NumberHolderFactoryBean::new"); @@ -158,9 +155,8 @@ class InstanceSupplierCodeGeneratorTests { void generateWhenHasPrivateConstructor() { BeanDefinition beanDefinition = new RootBeanDefinition( TestBeanWithPrivateConstructor.class); - DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory(); - compile(beanFactory, beanDefinition, (instanceSupplier, compiled) -> { - TestBeanWithPrivateConstructor bean = getBean(beanFactory, beanDefinition, + compile(beanDefinition, (instanceSupplier, compiled) -> { + TestBeanWithPrivateConstructor bean = getBean(beanDefinition, instanceSupplier); assertThat(bean).isInstanceOf(TestBeanWithPrivateConstructor.class); assertThat(compiled.getSourceFile()) @@ -175,11 +171,10 @@ class InstanceSupplierCodeGeneratorTests { BeanDefinition beanDefinition = BeanDefinitionBuilder .rootBeanDefinition(String.class) .setFactoryMethodOnBean("stringBean", "config").getBeanDefinition(); - DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory(); - beanFactory.registerBeanDefinition("config", BeanDefinitionBuilder + this.beanFactory.registerBeanDefinition("config", BeanDefinitionBuilder .genericBeanDefinition(SimpleConfiguration.class).getBeanDefinition()); - compile(beanFactory, beanDefinition, (instanceSupplier, compiled) -> { - String bean = getBean(beanFactory, beanDefinition, instanceSupplier); + compile(beanDefinition, (instanceSupplier, compiled) -> { + String bean = getBean(beanDefinition, instanceSupplier); assertThat(bean).isInstanceOf(String.class); assertThat(bean).isEqualTo("Hello"); assertThat(compiled.getSourceFile()).contains( @@ -195,11 +190,10 @@ class InstanceSupplierCodeGeneratorTests { .rootBeanDefinition(String.class) .setFactoryMethodOnBean("privateStaticStringBean", "config") .getBeanDefinition(); - DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory(); - beanFactory.registerBeanDefinition("config", BeanDefinitionBuilder + this.beanFactory.registerBeanDefinition("config", BeanDefinitionBuilder .genericBeanDefinition(SimpleConfiguration.class).getBeanDefinition()); - compile(beanFactory, beanDefinition, (instanceSupplier, compiled) -> { - String bean = getBean(beanFactory, beanDefinition, instanceSupplier); + compile(beanDefinition, (instanceSupplier, compiled) -> { + String bean = getBean(beanDefinition, instanceSupplier); assertThat(bean).isInstanceOf(String.class); assertThat(bean).isEqualTo("Hello"); assertThat(compiled.getSourceFile()) @@ -215,11 +209,10 @@ class InstanceSupplierCodeGeneratorTests { BeanDefinition beanDefinition = BeanDefinitionBuilder .rootBeanDefinition(Integer.class) .setFactoryMethodOnBean("integerBean", "config").getBeanDefinition(); - DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory(); - beanFactory.registerBeanDefinition("config", BeanDefinitionBuilder + this.beanFactory.registerBeanDefinition("config", BeanDefinitionBuilder .genericBeanDefinition(SimpleConfiguration.class).getBeanDefinition()); - compile(beanFactory, beanDefinition, (instanceSupplier, compiled) -> { - Integer bean = getBean(beanFactory, beanDefinition, instanceSupplier); + compile(beanDefinition, (instanceSupplier, compiled) -> { + Integer bean = getBean(beanDefinition, instanceSupplier); assertThat(bean).isInstanceOf(Integer.class); assertThat(bean).isEqualTo(42); assertThat(compiled.getSourceFile()) @@ -236,13 +229,12 @@ class InstanceSupplierCodeGeneratorTests { .setFactoryMethodOnBean("create", "config").getBeanDefinition(); beanDefinition.setResolvedFactoryMethod(ReflectionUtils .findMethod(SampleFactory.class, "create", Number.class, String.class)); - DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory(); - beanFactory.registerBeanDefinition("config", BeanDefinitionBuilder + this.beanFactory.registerBeanDefinition("config", BeanDefinitionBuilder .genericBeanDefinition(SampleFactory.class).getBeanDefinition()); - beanFactory.registerSingleton("number", 42); - beanFactory.registerSingleton("string", "test"); - compile(beanFactory, beanDefinition, (instanceSupplier, compiled) -> { - String bean = getBean(beanFactory, beanDefinition, instanceSupplier); + this.beanFactory.registerSingleton("number", 42); + this.beanFactory.registerSingleton("string", "test"); + compile(beanDefinition, (instanceSupplier, compiled) -> { + String bean = getBean(beanDefinition, instanceSupplier); assertThat(bean).isInstanceOf(String.class); assertThat(bean).isEqualTo("42test"); assertThat(compiled.getSourceFile()).contains("SampleFactory.create("); @@ -257,11 +249,10 @@ class InstanceSupplierCodeGeneratorTests { .rootBeanDefinition(Integer.class) .setFactoryMethodOnBean("throwingIntegerBean", "config") .getBeanDefinition(); - DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory(); - beanFactory.registerBeanDefinition("config", BeanDefinitionBuilder + this.beanFactory.registerBeanDefinition("config", BeanDefinitionBuilder .genericBeanDefinition(SimpleConfiguration.class).getBeanDefinition()); - compile(beanFactory, beanDefinition, (instanceSupplier, compiled) -> { - Integer bean = getBean(beanFactory, beanDefinition, instanceSupplier); + compile(beanDefinition, (instanceSupplier, compiled) -> { + Integer bean = getBean(beanDefinition, instanceSupplier); assertThat(bean).isInstanceOf(Integer.class); assertThat(bean).isEqualTo(42); assertThat(compiled.getSourceFile()).doesNotContain(") throws Exception {"); @@ -287,17 +278,15 @@ class InstanceSupplierCodeGeneratorTests { } @SuppressWarnings("unchecked") - private T getBean(DefaultListableBeanFactory beanFactory, - BeanDefinition beanDefinition, InstanceSupplier instanceSupplier) { + private T getBean(BeanDefinition beanDefinition, InstanceSupplier instanceSupplier) { ((RootBeanDefinition) beanDefinition).setInstanceSupplier(instanceSupplier); - beanFactory.registerBeanDefinition("testBean", beanDefinition); - return (T) beanFactory.getBean("testBean"); + this.beanFactory.registerBeanDefinition("testBean", beanDefinition); + return (T) this.beanFactory.getBean("testBean"); } - private void compile(DefaultListableBeanFactory beanFactory, BeanDefinition beanDefinition, - BiConsumer, Compiled> result) { + private void compile(BeanDefinition beanDefinition, BiConsumer, Compiled> result) { - DefaultListableBeanFactory freshBeanFactory = new DefaultListableBeanFactory(beanFactory); + DefaultListableBeanFactory freshBeanFactory = new DefaultListableBeanFactory(this.beanFactory); freshBeanFactory.registerBeanDefinition("testBean", beanDefinition); RegisteredBean registeredBean = RegisteredBean.of(freshBeanFactory, "testBean"); DeferredTypeBuilder typeBuilder = new DeferredTypeBuilder(); From ea66883d7986337e6135a9493d0a45fb65d188c7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?St=C3=A9phane=20Nicoll?= Date: Tue, 10 Oct 2023 14:52:18 +0200 Subject: [PATCH 3/3] Detect use of deprecated API This commit is a best effort attempt at identifying the members that code generation invokes and might be deprecated. It introduces a CodeWarnings helper class that records warnings, with special handling for `@Deprecated`. See gh-29597 --- .../aot/BeanDefinitionMethodGenerator.java | 3 + .../beans/factory/aot/CodeWarnings.java | 125 ++++++++++++++++++ .../aot/InstanceSupplierCodeGenerator.java | 13 ++ .../BeanDefinitionMethodGeneratorTests.java | 35 ++++- .../beans/factory/aot/CodeWarningsTests.java | 123 +++++++++++++++++ .../InstanceSupplierCodeGeneratorTests.java | 120 ++++++++++++++++- .../generator/deprecation/DeprecatedBean.java | 27 ++++ .../deprecation/DeprecatedConstructor.java | 34 +++++ .../deprecation/DeprecatedForRemovalBean.java | 27 ++++ .../DeprecatedForRemovalConstructor.java | 34 +++++ ...precatedForRemovalMemberConfiguration.java | 36 +++++ .../DeprecatedMemberConfiguration.java | 40 ++++++ 12 files changed, 614 insertions(+), 3 deletions(-) create mode 100644 spring-beans/src/main/java/org/springframework/beans/factory/aot/CodeWarnings.java create mode 100644 spring-beans/src/test/java/org/springframework/beans/factory/aot/CodeWarningsTests.java create mode 100644 spring-beans/src/testFixtures/java/org/springframework/beans/testfixture/beans/factory/generator/deprecation/DeprecatedBean.java create mode 100644 spring-beans/src/testFixtures/java/org/springframework/beans/testfixture/beans/factory/generator/deprecation/DeprecatedConstructor.java create mode 100644 spring-beans/src/testFixtures/java/org/springframework/beans/testfixture/beans/factory/generator/deprecation/DeprecatedForRemovalBean.java create mode 100644 spring-beans/src/testFixtures/java/org/springframework/beans/testfixture/beans/factory/generator/deprecation/DeprecatedForRemovalConstructor.java create mode 100644 spring-beans/src/testFixtures/java/org/springframework/beans/testfixture/beans/factory/generator/deprecation/DeprecatedForRemovalMemberConfiguration.java create mode 100644 spring-beans/src/testFixtures/java/org/springframework/beans/testfixture/beans/factory/generator/deprecation/DeprecatedMemberConfiguration.java diff --git a/spring-beans/src/main/java/org/springframework/beans/factory/aot/BeanDefinitionMethodGenerator.java b/spring-beans/src/main/java/org/springframework/beans/factory/aot/BeanDefinitionMethodGenerator.java index 13756a8fc6e..01c2ab71a7c 100644 --- a/spring-beans/src/main/java/org/springframework/beans/factory/aot/BeanDefinitionMethodGenerator.java +++ b/spring-beans/src/main/java/org/springframework/beans/factory/aot/BeanDefinitionMethodGenerator.java @@ -164,11 +164,14 @@ class BeanDefinitionMethodGenerator { this.aotContributions.forEach(aotContribution -> aotContribution.applyTo(generationContext, codeGenerator)); + CodeWarnings codeWarnings = new CodeWarnings(); + codeWarnings.detectDeprecation(this.registeredBean.getBeanClass()); return generatedMethods.add("getBeanDefinition", method -> { method.addJavadoc("Get the $L definition for '$L'.", (this.registeredBean.isInnerBean() ? "inner-bean" : "bean"), getName()); method.addModifiers(modifier, Modifier.STATIC); + codeWarnings.suppress(method); method.returns(BeanDefinition.class); method.addCode(codeGenerator.generateCode(generationContext)); }); diff --git a/spring-beans/src/main/java/org/springframework/beans/factory/aot/CodeWarnings.java b/spring-beans/src/main/java/org/springframework/beans/factory/aot/CodeWarnings.java new file mode 100644 index 00000000000..9912f445195 --- /dev/null +++ b/spring-beans/src/main/java/org/springframework/beans/factory/aot/CodeWarnings.java @@ -0,0 +1,125 @@ +/* + * Copyright 2002-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.springframework.beans.factory.aot; + +import java.lang.reflect.AnnotatedElement; +import java.util.Collections; +import java.util.LinkedHashSet; +import java.util.Set; +import java.util.StringJoiner; +import java.util.stream.Stream; + +import org.springframework.javapoet.AnnotationSpec; +import org.springframework.javapoet.CodeBlock; +import org.springframework.javapoet.MethodSpec; +import org.springframework.lang.Nullable; + +/** + * Helper class to register warnings that the compiler may trigger on + * generated code. + * + * @author Stephane Nicoll + * @see SuppressWarnings + */ +class CodeWarnings { + + private final Set warnings = new LinkedHashSet<>(); + + /** + * Register a warning to be included for this block. Does nothing if + * the warning is already registered. + * @param warning the warning to register, if it hasn't been already + */ + public void register(String warning) { + this.warnings.add(warning); + } + + /** + * Detect the presence of {@link Deprecated} on the specified elements. + * @param elements the elements to check + * @return {@code this} instance + */ + public CodeWarnings detectDeprecation(AnnotatedElement... elements) { + for (AnnotatedElement element : elements) { + register(element.getAnnotation(Deprecated.class)); + } + return this; + } + + /** + * Detect the presence of {@link Deprecated} on the specified elements. + * @param elements the elements to check + * @return {@code this} instance + */ + public CodeWarnings detectDeprecation(Stream elements) { + elements.forEach(element -> register(element.getAnnotation(Deprecated.class))); + return this; + } + + /** + * Include a {@link SuppressWarnings} on the specified method if necessary. + * @param method the method to update + */ + public void suppress(MethodSpec.Builder method) { + if (this.warnings.isEmpty()) { + return; + } + method.addAnnotation(buildAnnotationSpec()); + } + + /** + * Return the currently registered warnings. + * @return the warnings + */ + protected Set getWarnings() { + return Collections.unmodifiableSet(this.warnings); + } + + private void register(@Nullable Deprecated annotation) { + if (annotation != null) { + if (annotation.forRemoval()) { + register("removal"); + } + else { + register("deprecation"); + } + } + } + + private AnnotationSpec buildAnnotationSpec() { + return AnnotationSpec.builder(SuppressWarnings.class) + .addMember("value", generateValueCode()).build(); + } + + private CodeBlock generateValueCode() { + if (this.warnings.size() == 1) { + return CodeBlock.of("$S", this.warnings.iterator().next()); + } + CodeBlock values = CodeBlock.join(this.warnings.stream() + .map(warning -> CodeBlock.of("$S", warning)).toList(), ", "); + return CodeBlock.of("{ $L }", values); + } + + @Override + public String toString() { + return new StringJoiner(", ", CodeWarnings.class.getSimpleName(), "") + .add(this.warnings.toString()) + .toString(); + } + +} + diff --git a/spring-beans/src/main/java/org/springframework/beans/factory/aot/InstanceSupplierCodeGenerator.java b/spring-beans/src/main/java/org/springframework/beans/factory/aot/InstanceSupplierCodeGenerator.java index 87de98852ec..3087d8b481a 100644 --- a/spring-beans/src/main/java/org/springframework/beans/factory/aot/InstanceSupplierCodeGenerator.java +++ b/spring-beans/src/main/java/org/springframework/beans/factory/aot/InstanceSupplierCodeGenerator.java @@ -21,6 +21,7 @@ import java.lang.reflect.Executable; import java.lang.reflect.Member; import java.lang.reflect.Method; import java.lang.reflect.Modifier; +import java.lang.reflect.Parameter; import java.lang.reflect.Proxy; import java.util.Arrays; import java.util.function.Consumer; @@ -189,11 +190,15 @@ public class InstanceSupplierCodeGenerator { private CodeBlock generateCodeForInaccessibleConstructor(String beanName, Class beanClass, Constructor constructor, boolean dependsOnBean, Consumer hints) { + CodeWarnings codeWarnings = new CodeWarnings(); + codeWarnings.detectDeprecation(beanClass, constructor) + .detectDeprecation(Arrays.stream(constructor.getParameters()).map(Parameter::getType)); hints.accept(this.generationContext.getRuntimeHints().reflection()); GeneratedMethod generatedMethod = generateGetInstanceSupplierMethod(method -> { method.addJavadoc("Get the bean instance supplier for '$L'.", beanName); method.addModifiers(PRIVATE_STATIC); + codeWarnings.suppress(method); method.returns(ParameterizedTypeName.get(BeanInstanceSupplier.class, beanClass)); int parameterOffset = (!dependsOnBean) ? 0 : 1; method.addStatement(generateResolverForConstructor(beanClass, constructor, parameterOffset)); @@ -206,8 +211,12 @@ public class InstanceSupplierCodeGenerator { String beanName, Class beanClass, Constructor constructor, Class declaringClass, boolean dependsOnBean, javax.lang.model.element.Modifier... modifiers) { + CodeWarnings codeWarnings = new CodeWarnings(); + codeWarnings.detectDeprecation(beanClass, constructor, declaringClass) + .detectDeprecation(Arrays.stream(constructor.getParameters()).map(Parameter::getType)); method.addJavadoc("Get the bean instance supplier for '$L'.", beanName); method.addModifiers(modifiers); + codeWarnings.suppress(method); method.returns(ParameterizedTypeName.get(BeanInstanceSupplier.class, beanClass)); int parameterOffset = (!dependsOnBean) ? 0 : 1; @@ -300,9 +309,13 @@ public class InstanceSupplierCodeGenerator { String factoryMethodName = factoryMethod.getName(); Class suppliedType = ClassUtils.resolvePrimitiveIfNecessary(factoryMethod.getReturnType()); + CodeWarnings codeWarnings = new CodeWarnings(); + codeWarnings.detectDeprecation(declaringClass, factoryMethod, suppliedType) + .detectDeprecation(Arrays.stream(factoryMethod.getParameters()).map(Parameter::getType)); method.addJavadoc("Get the bean instance supplier for '$L'.", beanName); method.addModifiers(modifiers); + codeWarnings.suppress(method); method.returns(ParameterizedTypeName.get(BeanInstanceSupplier.class, suppliedType)); CodeBlock.Builder code = CodeBlock.builder(); diff --git a/spring-beans/src/test/java/org/springframework/beans/factory/aot/BeanDefinitionMethodGeneratorTests.java b/spring-beans/src/test/java/org/springframework/beans/factory/aot/BeanDefinitionMethodGeneratorTests.java index 19a9b5fd07c..3dbcad3e149 100644 --- a/spring-beans/src/test/java/org/springframework/beans/factory/aot/BeanDefinitionMethodGeneratorTests.java +++ b/spring-beans/src/test/java/org/springframework/beans/factory/aot/BeanDefinitionMethodGeneratorTests.java @@ -26,6 +26,7 @@ import java.util.function.Supplier; import javax.lang.model.element.Modifier; import javax.xml.parsers.DocumentBuilderFactory; +import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; import org.springframework.aot.generate.GeneratedMethod; @@ -52,6 +53,7 @@ import org.springframework.beans.testfixture.beans.factory.aot.TestHierarchy; import org.springframework.beans.testfixture.beans.factory.aot.TestHierarchy.Implementation; import org.springframework.beans.testfixture.beans.factory.aot.TestHierarchy.One; import org.springframework.beans.testfixture.beans.factory.aot.TestHierarchy.Two; +import org.springframework.beans.testfixture.beans.factory.generator.deprecation.DeprecatedBean; import org.springframework.core.ResolvableType; import org.springframework.core.test.io.support.MockSpringFactoriesLoader; import org.springframework.core.test.tools.CompileWithForkedClassLoader; @@ -66,6 +68,7 @@ import org.springframework.util.ReflectionUtils; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatIllegalStateException; +import static org.assertj.core.api.Assertions.assertThatNoException; /** * Tests for {@link BeanDefinitionMethodGenerator} and @@ -740,6 +743,32 @@ class BeanDefinitionMethodGeneratorTests { }); } + @Nested + @SuppressWarnings("deprecation") + class DeprecationTests { + + private static final TestCompiler TEST_COMPILER = TestCompiler.forSystem() + .withCompilerOptions("-Xlint:all", "-Xlint:-rawtypes", "-Werror"); + + @Test + void generateBeanDefinitionMethodWithDeprecatedTargetClass() { + RootBeanDefinition beanDefinition = new RootBeanDefinition(DeprecatedBean.class); + RegisteredBean registeredBean = registerBean(beanDefinition); + BeanDefinitionMethodGenerator generator = new BeanDefinitionMethodGenerator( + methodGeneratorFactory, registeredBean, null, + Collections.emptyList()); + MethodReference method = generator.generateBeanDefinitionMethod( + generationContext, beanRegistrationsCode); + compileAndCheckWarnings(method); + } + + private void compileAndCheckWarnings(MethodReference methodReference) { + assertThatNoException().isThrownBy(() -> compile(TEST_COMPILER, methodReference, + ((instanceSupplier, compiled) -> {}))); + } + + } + private void testBeanDefinitionMethodInCurrentFile(Class targetType, RootBeanDefinition beanDefinition) { RegisteredBean registeredBean = registerBean(new RootBeanDefinition(beanDefinition)); BeanDefinitionMethodGenerator generator = new BeanDefinitionMethodGenerator( @@ -764,6 +793,10 @@ class BeanDefinitionMethodGeneratorTests { } private void compile(MethodReference method, BiConsumer result) { + compile(TestCompiler.forSystem(), method, result); + } + + private void compile(TestCompiler testCompiler, MethodReference method, BiConsumer result) { this.beanRegistrationsCode.getTypeBuilder().set(type -> { CodeBlock methodInvocation = method.toInvokeCodeBlock(ArgumentCodeGenerator.none(), this.beanRegistrationsCode.getClassName()); @@ -775,7 +808,7 @@ class BeanDefinitionMethodGeneratorTests { .addCode("return $L;", methodInvocation).build()); }); this.generationContext.writeGeneratedContent(); - TestCompiler.forSystem().with(this.generationContext).compile(compiled -> + testCompiler.with(this.generationContext).compile(compiled -> result.accept((RootBeanDefinition) compiled.getInstance(Supplier.class).get(), compiled)); } diff --git a/spring-beans/src/test/java/org/springframework/beans/factory/aot/CodeWarningsTests.java b/spring-beans/src/test/java/org/springframework/beans/factory/aot/CodeWarningsTests.java new file mode 100644 index 00000000000..365ae5ced2b --- /dev/null +++ b/spring-beans/src/test/java/org/springframework/beans/factory/aot/CodeWarningsTests.java @@ -0,0 +1,123 @@ +/* + * Copyright 2002-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.springframework.beans.factory.aot; + +import java.util.function.Consumer; + +import javax.lang.model.element.Modifier; + +import org.junit.jupiter.api.Test; + +import org.springframework.aot.test.generate.TestGenerationContext; +import org.springframework.beans.testfixture.beans.factory.aot.DeferredTypeBuilder; +import org.springframework.beans.testfixture.beans.factory.generator.deprecation.DeprecatedBean; +import org.springframework.beans.testfixture.beans.factory.generator.deprecation.DeprecatedForRemovalBean; +import org.springframework.core.test.tools.Compiled; +import org.springframework.core.test.tools.TestCompiler; +import org.springframework.javapoet.MethodSpec; +import org.springframework.javapoet.MethodSpec.Builder; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Tests for {@link CodeWarnings}. + * + * @author Stephane Nicoll + */ +class CodeWarningsTests { + + private static final TestCompiler TEST_COMPILER = TestCompiler.forSystem() + .withCompilerOptions("-Xlint:all", "-Werror"); + + private final CodeWarnings codeWarnings; + + private final TestGenerationContext generationContext; + + CodeWarningsTests() { + this.codeWarnings = new CodeWarnings(); + this.generationContext = new TestGenerationContext(); + } + + @Test + void registerNoWarningDoesNotIncludeAnnotation() { + compile(method -> { + this.codeWarnings.suppress(method); + method.addStatement("$T bean = $S", String.class, "Hello"); + }, compiled -> assertThat(compiled.getSourceFile()).doesNotContain("@SuppressWarnings")); + } + + @Test + @SuppressWarnings("deprecation") + void registerWarningSuppressesIt() { + this.codeWarnings.register("deprecation"); + compile(method -> { + this.codeWarnings.suppress(method); + method.addStatement("$T bean = new $T()", DeprecatedBean.class, DeprecatedBean.class); + }, compiled -> assertThat(compiled.getSourceFile()) + .contains("@SuppressWarnings(\"deprecation\")")); + } + + @Test + @SuppressWarnings({ "deprecation", "removal" }) + void registerSeveralWarningsSuppressesThem() { + this.codeWarnings.register("deprecation"); + this.codeWarnings.register("removal"); + compile(method -> { + this.codeWarnings.suppress(method); + method.addStatement("$T bean = new $T()", DeprecatedBean.class, DeprecatedBean.class); + method.addStatement("$T another = new $T()", DeprecatedForRemovalBean.class, DeprecatedForRemovalBean.class); + }, compiled -> assertThat(compiled.getSourceFile()) + .contains("@SuppressWarnings({ \"deprecation\", \"removal\" })")); + } + + @Test + @SuppressWarnings("deprecation") + void detectDeprecationOnAnnotatedElementWithDeprecated() { + this.codeWarnings.detectDeprecation(DeprecatedBean.class); + assertThat(this.codeWarnings.getWarnings()).containsExactly("deprecation"); + } + + @Test + @SuppressWarnings("removal") + void detectDeprecationOnAnnotatedElementWithDeprecatedForRemoval() { + this.codeWarnings.detectDeprecation(DeprecatedForRemovalBean.class); + assertThat(this.codeWarnings.getWarnings()).containsExactly("removal"); + } + + @Test + void toStringIncludeWarnings() { + this.codeWarnings.register("deprecation"); + this.codeWarnings.register("rawtypes"); + assertThat(this.codeWarnings).hasToString("CodeWarnings[deprecation, rawtypes]"); + } + + private void compile(Consumer method, + Consumer result) { + DeferredTypeBuilder typeBuilder = new DeferredTypeBuilder(); + this.generationContext.getGeneratedClasses().addForFeature("TestCode", typeBuilder); + typeBuilder.set(type -> { + type.addModifiers(Modifier.PUBLIC); + Builder methodBuilder = MethodSpec.methodBuilder("apply") + .addModifiers(Modifier.PUBLIC); + method.accept(methodBuilder); + type.addMethod(methodBuilder.build()); + }); + this.generationContext.writeGeneratedContent(); + TEST_COMPILER.with(this.generationContext).compile(result); + } + +} diff --git a/spring-beans/src/test/java/org/springframework/beans/factory/aot/InstanceSupplierCodeGeneratorTests.java b/spring-beans/src/test/java/org/springframework/beans/factory/aot/InstanceSupplierCodeGeneratorTests.java index 370e318bc44..5e166745de6 100644 --- a/spring-beans/src/test/java/org/springframework/beans/factory/aot/InstanceSupplierCodeGeneratorTests.java +++ b/spring-beans/src/test/java/org/springframework/beans/factory/aot/InstanceSupplierCodeGeneratorTests.java @@ -23,6 +23,8 @@ import java.util.function.Supplier; import javax.lang.model.element.Modifier; import org.assertj.core.api.ThrowingConsumer; +import org.junit.jupiter.api.Disabled; +import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; import org.springframework.aot.generate.GeneratedClass; @@ -44,6 +46,12 @@ import org.springframework.beans.testfixture.beans.factory.generator.InnerCompon import org.springframework.beans.testfixture.beans.factory.generator.InnerComponentConfiguration.EnvironmentAwareComponent; import org.springframework.beans.testfixture.beans.factory.generator.InnerComponentConfiguration.NoDependencyComponent; import org.springframework.beans.testfixture.beans.factory.generator.SimpleConfiguration; +import org.springframework.beans.testfixture.beans.factory.generator.deprecation.DeprecatedBean; +import org.springframework.beans.testfixture.beans.factory.generator.deprecation.DeprecatedConstructor; +import org.springframework.beans.testfixture.beans.factory.generator.deprecation.DeprecatedForRemovalBean; +import org.springframework.beans.testfixture.beans.factory.generator.deprecation.DeprecatedForRemovalConstructor; +import org.springframework.beans.testfixture.beans.factory.generator.deprecation.DeprecatedForRemovalMemberConfiguration; +import org.springframework.beans.testfixture.beans.factory.generator.deprecation.DeprecatedMemberConfiguration; import org.springframework.beans.testfixture.beans.factory.generator.factory.NumberHolder; import org.springframework.beans.testfixture.beans.factory.generator.factory.NumberHolderFactoryBean; import org.springframework.beans.testfixture.beans.factory.generator.factory.SampleFactory; @@ -57,6 +65,7 @@ import org.springframework.javapoet.ParameterizedTypeName; import org.springframework.util.ReflectionUtils; import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatNoException; /** * Tests for {@link InstanceSupplierCodeGenerator}. @@ -261,6 +270,108 @@ class InstanceSupplierCodeGeneratorTests { .satisfies(hasMethodWithMode(ExecutableMode.INTROSPECT)); } + @Nested + @SuppressWarnings("deprecation") + class DeprecationTests { + + private static final TestCompiler TEST_COMPILER = TestCompiler.forSystem() + .withCompilerOptions("-Xlint:all", "-Xlint:-rawtypes", "-Werror"); + + @Test + @Disabled("Need to move to a separate method so that the warning can be suppressed") + void generateWhenTargetClassIsDeprecated() { + compileAndCheckWarnings(new RootBeanDefinition(DeprecatedBean.class)); + } + + @Test + void generateWhenTargetConstructorIsDeprecated() { + compileAndCheckWarnings(new RootBeanDefinition(DeprecatedConstructor.class)); + } + + @Test + void generateWhenTargetFactoryMethodIsDeprecated() { + BeanDefinition beanDefinition = BeanDefinitionBuilder + .rootBeanDefinition(String.class) + .setFactoryMethodOnBean("deprecatedString", "config").getBeanDefinition(); + beanFactory.registerBeanDefinition("config", BeanDefinitionBuilder + .genericBeanDefinition(DeprecatedMemberConfiguration.class).getBeanDefinition()); + compileAndCheckWarnings(beanDefinition); + } + + @Test + void generateWhenTargetFactoryMethodParameterIsDeprecated() { + BeanDefinition beanDefinition = BeanDefinitionBuilder + .rootBeanDefinition(String.class) + .setFactoryMethodOnBean("deprecatedParameter", "config").getBeanDefinition(); + beanFactory.registerBeanDefinition("config", BeanDefinitionBuilder + .genericBeanDefinition(DeprecatedMemberConfiguration.class).getBeanDefinition()); + beanFactory.registerBeanDefinition("parameter", new RootBeanDefinition(DeprecatedBean.class)); + compileAndCheckWarnings(beanDefinition); + } + + @Test + void generateWhenTargetFactoryMethodReturnTypeIsDeprecated() { + BeanDefinition beanDefinition = BeanDefinitionBuilder + .rootBeanDefinition(DeprecatedBean.class) + .setFactoryMethodOnBean("deprecatedReturnType", "config").getBeanDefinition(); + beanFactory.registerBeanDefinition("config", BeanDefinitionBuilder + .genericBeanDefinition(DeprecatedMemberConfiguration.class).getBeanDefinition()); + compileAndCheckWarnings(beanDefinition); + } + + private void compileAndCheckWarnings(BeanDefinition beanDefinition) { + assertThatNoException().isThrownBy(() -> compile(TEST_COMPILER, beanDefinition, + ((instanceSupplier, compiled) -> {}))); + } + + } + + @Nested + @SuppressWarnings("removal") + class DeprecationForRemovalTests { + + private static final TestCompiler TEST_COMPILER = TestCompiler.forSystem() + .withCompilerOptions("-Xlint:all", "-Xlint:-rawtypes", "-Werror"); + + @Test + @Disabled("Need to move to a separate method so that the warning can be suppressed") + void generateWhenTargetClassIsDeprecatedForRemoval() { + compileAndCheckWarnings(new RootBeanDefinition(DeprecatedForRemovalBean.class)); + } + + @Test + void generateWhenTargetConstructorIsDeprecatedForRemoval() { + compileAndCheckWarnings(new RootBeanDefinition(DeprecatedForRemovalConstructor.class)); + } + + @Test + void generateWhenTargetFactoryMethodIsDeprecatedForRemoval() { + BeanDefinition beanDefinition = BeanDefinitionBuilder + .rootBeanDefinition(String.class) + .setFactoryMethodOnBean("deprecatedString", "config").getBeanDefinition(); + beanFactory.registerBeanDefinition("config", BeanDefinitionBuilder + .genericBeanDefinition(DeprecatedForRemovalMemberConfiguration.class).getBeanDefinition()); + compileAndCheckWarnings(beanDefinition); + } + + @Test + void generateWhenTargetFactoryMethodParameterIsDeprecatedForRemoval() { + BeanDefinition beanDefinition = BeanDefinitionBuilder + .rootBeanDefinition(String.class) + .setFactoryMethodOnBean("deprecatedParameter", "config").getBeanDefinition(); + beanFactory.registerBeanDefinition("config", BeanDefinitionBuilder + .genericBeanDefinition(DeprecatedForRemovalMemberConfiguration.class).getBeanDefinition()); + beanFactory.registerBeanDefinition("parameter", new RootBeanDefinition(DeprecatedForRemovalBean.class)); + compileAndCheckWarnings(beanDefinition); + } + + private void compileAndCheckWarnings(BeanDefinition beanDefinition) { + assertThatNoException().isThrownBy(() -> compile(TEST_COMPILER, beanDefinition, + ((instanceSupplier, compiled) -> {}))); + } + + } + private ReflectionHints getReflectionHints() { return this.generationContext.getRuntimeHints().reflection(); } @@ -285,6 +396,11 @@ class InstanceSupplierCodeGeneratorTests { } private void compile(BeanDefinition beanDefinition, BiConsumer, Compiled> result) { + compile(TestCompiler.forSystem(), beanDefinition, result); + } + + private void compile(TestCompiler testCompiler, BeanDefinition beanDefinition, + BiConsumer, Compiled> result) { DefaultListableBeanFactory freshBeanFactory = new DefaultListableBeanFactory(this.beanFactory); freshBeanFactory.registerBeanDefinition("testBean", beanDefinition); @@ -306,8 +422,8 @@ class InstanceSupplierCodeGeneratorTests { .addStatement("return $L", generatedCode).build()); }); this.generationContext.writeGeneratedContent(); - TestCompiler.forSystem().with(this.generationContext).compile(compiled -> - result.accept((InstanceSupplier) compiled.getInstance(Supplier.class).get(), compiled)); + testCompiler.with(this.generationContext).compile(compiled -> result.accept( + (InstanceSupplier) compiled.getInstance(Supplier.class).get(), compiled)); } } diff --git a/spring-beans/src/testFixtures/java/org/springframework/beans/testfixture/beans/factory/generator/deprecation/DeprecatedBean.java b/spring-beans/src/testFixtures/java/org/springframework/beans/testfixture/beans/factory/generator/deprecation/DeprecatedBean.java new file mode 100644 index 00000000000..7cad80f3ffb --- /dev/null +++ b/spring-beans/src/testFixtures/java/org/springframework/beans/testfixture/beans/factory/generator/deprecation/DeprecatedBean.java @@ -0,0 +1,27 @@ +/* + * Copyright 2002-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.springframework.beans.testfixture.beans.factory.generator.deprecation; + +/** + * A sample bean that's fully deprecated. + * + * @author Stephane Nicoll + */ +@Deprecated +public class DeprecatedBean { +} + diff --git a/spring-beans/src/testFixtures/java/org/springframework/beans/testfixture/beans/factory/generator/deprecation/DeprecatedConstructor.java b/spring-beans/src/testFixtures/java/org/springframework/beans/testfixture/beans/factory/generator/deprecation/DeprecatedConstructor.java new file mode 100644 index 00000000000..ca4ca987cb3 --- /dev/null +++ b/spring-beans/src/testFixtures/java/org/springframework/beans/testfixture/beans/factory/generator/deprecation/DeprecatedConstructor.java @@ -0,0 +1,34 @@ +/* + * Copyright 2002-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.springframework.beans.testfixture.beans.factory.generator.deprecation; + +import org.springframework.core.env.Environment; + +/** + * A sample whose factory method (constructor) is deprecated. + * + * @author Stephane Nicoll + */ +public class DeprecatedConstructor { + + @Deprecated + public DeprecatedConstructor(Environment environment) { + + } + +} + diff --git a/spring-beans/src/testFixtures/java/org/springframework/beans/testfixture/beans/factory/generator/deprecation/DeprecatedForRemovalBean.java b/spring-beans/src/testFixtures/java/org/springframework/beans/testfixture/beans/factory/generator/deprecation/DeprecatedForRemovalBean.java new file mode 100644 index 00000000000..cb11be7017c --- /dev/null +++ b/spring-beans/src/testFixtures/java/org/springframework/beans/testfixture/beans/factory/generator/deprecation/DeprecatedForRemovalBean.java @@ -0,0 +1,27 @@ +/* + * Copyright 2002-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.springframework.beans.testfixture.beans.factory.generator.deprecation; + +/** + * A sample bean that's fully deprecated for removal. + * + * @author Stephane Nicoll + */ +@Deprecated(forRemoval = true) +public class DeprecatedForRemovalBean { +} + diff --git a/spring-beans/src/testFixtures/java/org/springframework/beans/testfixture/beans/factory/generator/deprecation/DeprecatedForRemovalConstructor.java b/spring-beans/src/testFixtures/java/org/springframework/beans/testfixture/beans/factory/generator/deprecation/DeprecatedForRemovalConstructor.java new file mode 100644 index 00000000000..7b4047e2e04 --- /dev/null +++ b/spring-beans/src/testFixtures/java/org/springframework/beans/testfixture/beans/factory/generator/deprecation/DeprecatedForRemovalConstructor.java @@ -0,0 +1,34 @@ +/* + * Copyright 2002-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.springframework.beans.testfixture.beans.factory.generator.deprecation; + +import org.springframework.core.env.Environment; + +/** + * A sample whose factory method (constructor) is deprecated for removal + * + * @author Stephane Nicoll + */ +public class DeprecatedForRemovalConstructor { + + @Deprecated(forRemoval = true) + public DeprecatedForRemovalConstructor(Environment environment) { + + } + +} + diff --git a/spring-beans/src/testFixtures/java/org/springframework/beans/testfixture/beans/factory/generator/deprecation/DeprecatedForRemovalMemberConfiguration.java b/spring-beans/src/testFixtures/java/org/springframework/beans/testfixture/beans/factory/generator/deprecation/DeprecatedForRemovalMemberConfiguration.java new file mode 100644 index 00000000000..577fbf83ac7 --- /dev/null +++ b/spring-beans/src/testFixtures/java/org/springframework/beans/testfixture/beans/factory/generator/deprecation/DeprecatedForRemovalMemberConfiguration.java @@ -0,0 +1,36 @@ +/* + * Copyright 2002-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.springframework.beans.testfixture.beans.factory.generator.deprecation; + +/** + * A class with deprecated members for removal to test various use cases. + * + * @author Stephane Nicoll + */ +public class DeprecatedForRemovalMemberConfiguration { + + @Deprecated(forRemoval = true) + public String deprecatedString() { + return "deprecated"; + } + + public String deprecatedParameter(DeprecatedForRemovalBean bean) { + return bean.toString(); + } + +} + diff --git a/spring-beans/src/testFixtures/java/org/springframework/beans/testfixture/beans/factory/generator/deprecation/DeprecatedMemberConfiguration.java b/spring-beans/src/testFixtures/java/org/springframework/beans/testfixture/beans/factory/generator/deprecation/DeprecatedMemberConfiguration.java new file mode 100644 index 00000000000..96ca7add31c --- /dev/null +++ b/spring-beans/src/testFixtures/java/org/springframework/beans/testfixture/beans/factory/generator/deprecation/DeprecatedMemberConfiguration.java @@ -0,0 +1,40 @@ +/* + * Copyright 2002-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.springframework.beans.testfixture.beans.factory.generator.deprecation; + +/** + * A class with deprecated members to test various use cases. + * + * @author Stephane Nicoll + */ +public class DeprecatedMemberConfiguration { + + @Deprecated + public String deprecatedString() { + return "deprecated"; + } + + public String deprecatedParameter(DeprecatedBean bean) { + return bean.toString(); + } + + public DeprecatedBean deprecatedReturnType() { + return new DeprecatedBean(); + } + +} +