Browse Source

Handle deprecation warning in AOT-generated code

Closes gh-29597
pull/31407/head
Stéphane Nicoll 2 years ago
parent
commit
96b32580ec
  1. 3
      spring-beans/src/main/java/org/springframework/beans/factory/aot/BeanDefinitionMethodGenerator.java
  2. 125
      spring-beans/src/main/java/org/springframework/beans/factory/aot/CodeWarnings.java
  3. 13
      spring-beans/src/main/java/org/springframework/beans/factory/aot/InstanceSupplierCodeGenerator.java
  4. 35
      spring-beans/src/test/java/org/springframework/beans/factory/aot/BeanDefinitionMethodGeneratorTests.java
  5. 123
      spring-beans/src/test/java/org/springframework/beans/factory/aot/CodeWarningsTests.java
  6. 213
      spring-beans/src/test/java/org/springframework/beans/factory/aot/InstanceSupplierCodeGeneratorTests.java
  7. 27
      spring-beans/src/testFixtures/java/org/springframework/beans/testfixture/beans/factory/generator/deprecation/DeprecatedBean.java
  8. 34
      spring-beans/src/testFixtures/java/org/springframework/beans/testfixture/beans/factory/generator/deprecation/DeprecatedConstructor.java
  9. 27
      spring-beans/src/testFixtures/java/org/springframework/beans/testfixture/beans/factory/generator/deprecation/DeprecatedForRemovalBean.java
  10. 34
      spring-beans/src/testFixtures/java/org/springframework/beans/testfixture/beans/factory/generator/deprecation/DeprecatedForRemovalConstructor.java
  11. 36
      spring-beans/src/testFixtures/java/org/springframework/beans/testfixture/beans/factory/generator/deprecation/DeprecatedForRemovalMemberConfiguration.java
  12. 40
      spring-beans/src/testFixtures/java/org/springframework/beans/testfixture/beans/factory/generator/deprecation/DeprecatedMemberConfiguration.java
  13. 60
      spring-core-test/src/main/java/org/springframework/core/test/tools/TestCompiler.java
  14. 81
      spring-core-test/src/test/java/org/springframework/core/test/tools/TestCompilerTests.java

3
spring-beans/src/main/java/org/springframework/beans/factory/aot/BeanDefinitionMethodGenerator.java

@ -164,11 +164,14 @@ class BeanDefinitionMethodGenerator { @@ -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));
});

125
spring-beans/src/main/java/org/springframework/beans/factory/aot/CodeWarnings.java

@ -0,0 +1,125 @@ @@ -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<String> 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<AnnotatedElement> 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<String> 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();
}
}

13
spring-beans/src/main/java/org/springframework/beans/factory/aot/InstanceSupplierCodeGenerator.java

@ -21,6 +21,7 @@ import java.lang.reflect.Executable; @@ -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 { @@ -189,11 +190,15 @@ public class InstanceSupplierCodeGenerator {
private CodeBlock generateCodeForInaccessibleConstructor(String beanName, Class<?> beanClass,
Constructor<?> constructor, boolean dependsOnBean, Consumer<ReflectionHints> 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 { @@ -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 { @@ -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();

35
spring-beans/src/test/java/org/springframework/beans/factory/aot/BeanDefinitionMethodGeneratorTests.java

@ -26,6 +26,7 @@ import java.util.function.Supplier; @@ -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; @@ -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; @@ -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 { @@ -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 { @@ -764,6 +793,10 @@ class BeanDefinitionMethodGeneratorTests {
}
private void compile(MethodReference method, BiConsumer<RootBeanDefinition, Compiled> result) {
compile(TestCompiler.forSystem(), method, result);
}
private void compile(TestCompiler testCompiler, MethodReference method, BiConsumer<RootBeanDefinition, Compiled> result) {
this.beanRegistrationsCode.getTypeBuilder().set(type -> {
CodeBlock methodInvocation = method.toInvokeCodeBlock(ArgumentCodeGenerator.none(),
this.beanRegistrationsCode.getClassName());
@ -775,7 +808,7 @@ class BeanDefinitionMethodGeneratorTests { @@ -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));
}

123
spring-beans/src/test/java/org/springframework/beans/factory/aot/CodeWarningsTests.java

@ -0,0 +1,123 @@ @@ -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<Builder> method,
Consumer<Compiled> 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);
}
}

213
spring-beans/src/test/java/org/springframework/beans/factory/aot/InstanceSupplierCodeGeneratorTests.java

@ -23,6 +23,8 @@ import java.util.function.Supplier; @@ -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 @@ -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; @@ -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}.
@ -68,18 +77,20 @@ class InstanceSupplierCodeGeneratorTests { @@ -68,18 +77,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 +102,9 @@ class InstanceSupplierCodeGeneratorTests { @@ -91,11 +102,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 +116,9 @@ class InstanceSupplierCodeGeneratorTests { @@ -107,10 +116,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 +132,10 @@ class InstanceSupplierCodeGeneratorTests { @@ -124,11 +132,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 +149,9 @@ class InstanceSupplierCodeGeneratorTests { @@ -142,10 +149,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 +164,8 @@ class InstanceSupplierCodeGeneratorTests { @@ -158,9 +164,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 +180,10 @@ class InstanceSupplierCodeGeneratorTests { @@ -175,11 +180,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 +199,10 @@ class InstanceSupplierCodeGeneratorTests { @@ -195,11 +199,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 +218,10 @@ class InstanceSupplierCodeGeneratorTests { @@ -215,11 +218,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 +238,12 @@ class InstanceSupplierCodeGeneratorTests { @@ -236,13 +238,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 +258,10 @@ class InstanceSupplierCodeGeneratorTests { @@ -257,11 +258,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 {");
@ -270,6 +270,108 @@ class InstanceSupplierCodeGeneratorTests { @@ -270,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();
}
@ -287,17 +389,20 @@ class InstanceSupplierCodeGeneratorTests { @@ -287,17 +389,20 @@ class InstanceSupplierCodeGeneratorTests {
}
@SuppressWarnings("unchecked")
private <T> T getBean(DefaultListableBeanFactory beanFactory,
BeanDefinition beanDefinition, InstanceSupplier<?> instanceSupplier) {
private <T> 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(BeanDefinition beanDefinition, BiConsumer<InstanceSupplier<?>, Compiled> result) {
compile(TestCompiler.forSystem(), beanDefinition, result);
}
private void compile(DefaultListableBeanFactory beanFactory, BeanDefinition beanDefinition,
private void compile(TestCompiler testCompiler, BeanDefinition beanDefinition,
BiConsumer<InstanceSupplier<?>, 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();
@ -317,8 +422,8 @@ class InstanceSupplierCodeGeneratorTests { @@ -317,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));
}
}

27
spring-beans/src/testFixtures/java/org/springframework/beans/testfixture/beans/factory/generator/deprecation/DeprecatedBean.java

@ -0,0 +1,27 @@ @@ -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 {
}

34
spring-beans/src/testFixtures/java/org/springframework/beans/testfixture/beans/factory/generator/deprecation/DeprecatedConstructor.java

@ -0,0 +1,34 @@ @@ -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) {
}
}

27
spring-beans/src/testFixtures/java/org/springframework/beans/testfixture/beans/factory/generator/deprecation/DeprecatedForRemovalBean.java

@ -0,0 +1,27 @@ @@ -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 {
}

34
spring-beans/src/testFixtures/java/org/springframework/beans/testfixture/beans/factory/generator/deprecation/DeprecatedForRemovalConstructor.java

@ -0,0 +1,34 @@ @@ -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) {
}
}

36
spring-beans/src/testFixtures/java/org/springframework/beans/testfixture/beans/factory/generator/deprecation/DeprecatedForRemovalMemberConfiguration.java

@ -0,0 +1,36 @@ @@ -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();
}
}

40
spring-beans/src/testFixtures/java/org/springframework/beans/testfixture/beans/factory/generator/deprecation/DeprecatedMemberConfiguration.java

@ -0,0 +1,40 @@ @@ -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();
}
}

60
spring-core-test/src/main/java/org/springframework/core/test/tools/TestCompiler.java

@ -24,6 +24,7 @@ import java.util.List; @@ -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; @@ -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 { @@ -59,10 +61,12 @@ public final class TestCompiler {
private final List<Processor> processors;
private final List<String> compilerOptions;
private TestCompiler(@Nullable ClassLoader classLoader, JavaCompiler compiler,
SourceFiles sourceFiles, ResourceFiles resourceFiles, ClassFiles classFiles,
List<Processor> processors) {
List<Processor> processors, List<String> compilerOptions) {
this.classLoader = classLoader;
this.compiler = compiler;
@ -70,6 +74,7 @@ public final class TestCompiler { @@ -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 { @@ -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 { @@ -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 { @@ -119,7 +124,7 @@ public final class TestCompiler {
public TestCompiler withSources(Iterable<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);
}
/**
@ -130,7 +135,7 @@ public final class TestCompiler { @@ -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 { @@ -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 { @@ -150,7 +156,8 @@ public final class TestCompiler {
*/
public TestCompiler withResources(Iterable<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);
}
/**
@ -160,7 +167,8 @@ public final class TestCompiler { @@ -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 { @@ -170,7 +178,8 @@ public final class TestCompiler {
*/
public TestCompiler withClasses(Iterable<ClassFile> 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 { @@ -182,7 +191,7 @@ public final class TestCompiler {
List<Processor> 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 { @@ -194,7 +203,32 @@ public final class TestCompiler {
List<Processor> 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<String> 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 { @@ -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);
}

81
spring-core-test/src/test/java/org/springframework/core/test/tools/TestCompilerTests.java

@ -1,5 +1,5 @@ @@ -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; @@ -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 { @@ -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<String> {
@Deprecated
public String get() {
return "Hello Deprecated";
}
}
""";
@Test
@SuppressWarnings("unchecked")
@ -119,6 +134,70 @@ class TestCompilerTests { @@ -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<String> 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<String> supplier = compiled.getInstance(Supplier.class,
"com.example.Hello");
assertThat(supplier.get()).isEqualTo("Hello Deprecated");
});
}
@Test
void withSourcesArrayAddsSource() {
SourceFile sourceFile = SourceFile.of(HELLO_WORLD);

Loading…
Cancel
Save