Skip to content

Commit

Permalink
Allow a MethodReference to be produced from a GeneratedMethod
Browse files Browse the repository at this point in the history
This commit updates GeneratedMethod and its underlying infrastructure
to be able to produce a MethodReference. This simplifies the need when
such a reference needs to be created manually and reuses more of what
MethodReference has to offer.

See spring-projectsgh-29005
  • Loading branch information
snicoll committed Sep 7, 2022
1 parent 4cc91e4 commit 0934319
Show file tree
Hide file tree
Showing 14 changed files with 101 additions and 43 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -163,8 +163,8 @@ public CodeBlock generateInstanceSupplierCode(GenerationContext generationContex
method.addStatement("return ($T) factory.getObject()",
beanClass);
});
return CodeBlock.of("$T.of($T::$L)", InstanceSupplier.class,
beanRegistrationCode.getClassName(), generatedMethod.getName());
return CodeBlock.of("$T.of($L)", InstanceSupplier.class,
generatedMethod.toMethodReference().toCodeBlock());
}

}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,6 @@
import org.springframework.aot.generate.GeneratedClass;
import org.springframework.aot.generate.GeneratedMethod;
import org.springframework.aot.generate.GenerationContext;
import org.springframework.aot.generate.MethodReference;
import org.springframework.aot.hint.ExecutableMode;
import org.springframework.aot.hint.RuntimeHints;
import org.springframework.beans.BeanUtils;
Expand Down Expand Up @@ -944,8 +943,7 @@ public void applyTo(GenerationContext generationContext,
method.returns(this.target);
method.addCode(generateMethodCode(generationContext.getRuntimeHints()));
});
beanRegistrationCode.addInstancePostProcessor(
MethodReference.ofStatic(generatedClass.getName(), generateMethod.getName()));
beanRegistrationCode.addInstancePostProcessor(generateMethod.toMethodReference());

if (this.candidateResolver != null) {
registerHints(generationContext.getRuntimeHints());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -107,16 +107,14 @@ MethodReference generateBeanDefinitionMethod(GenerationContext generationContext
GeneratedMethod generatedMethod = generateBeanDefinitionMethod(
generationContext, generatedClass.getName(), generatedMethods,
codeFragments, Modifier.PUBLIC);
return MethodReference.ofStatic(generatedClass.getName(),
generatedMethod.getName());
return generatedMethod.toMethodReference();
}
GeneratedMethods generatedMethods = beanRegistrationsCode.getMethods()
.withPrefix(getName());
GeneratedMethod generatedMethod = generateBeanDefinitionMethod(generationContext,
beanRegistrationsCode.getClassName(), generatedMethods, codeFragments,
Modifier.PRIVATE);
return MethodReference.ofStatic(beanRegistrationsCode.getClassName(),
generatedMethod.getName());
return generatedMethod.toMethodReference();
}

private BeanRegistrationCodeFragments getCodeFragments(GenerationContext generationContext,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -65,8 +65,7 @@ public void applyTo(GenerationContext generationContext,
BeanRegistrationsCodeGenerator codeGenerator = new BeanRegistrationsCodeGenerator(generatedClass);
GeneratedMethod generatedMethod = codeGenerator.getMethods().add("registerBeanDefinitions", method ->
generateRegisterMethod(method, generationContext, codeGenerator));
beanFactoryInitializationCode.addInitializer(
MethodReference.of(generatedClass.getName(), generatedMethod.getName()));
beanFactoryInitializationCode.addInitializer(generatedMethod.toMethodReference());
}

private void generateRegisterMethod(MethodSpec.Builder method,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -296,8 +296,8 @@ private CodeBlock generateNewInstanceCodeForMethod(boolean dependsOnBean,
REGISTERED_BEAN_PARAMETER_NAME, declaringClass, factoryMethodName, args);
}

private CodeBlock generateReturnStatement(GeneratedMethod getInstanceMethod) {
return CodeBlock.of("$T.$L()", this.className, getInstanceMethod.getName());
private CodeBlock generateReturnStatement(GeneratedMethod generatedMethod) {
return generatedMethod.toMethodReference().toInvokeCodeBlock();
}

private CodeBlock generateWithGeneratorCode(boolean hasArguments, CodeBlock newInstance) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -129,8 +129,7 @@ void generateBeanDefinitionMethodWhenHasInstancePostProcessorGeneratesMethod() {
.addParameter(RegisteredBean.class, "registeredBean")
.addParameter(TestBean.class, "testBean")
.returns(TestBean.class).addCode("return new $T($S);", TestBean.class, "postprocessed"));
beanRegistrationCode.addInstancePostProcessor(MethodReference.ofStatic(
beanRegistrationCode.getClassName(), generatedMethod.getName()));
beanRegistrationCode.addInstancePostProcessor(generatedMethod.toMethodReference());
};
List<BeanRegistrationAotContribution> aotContributions = Collections
.singletonList(aotContribution);
Expand Down Expand Up @@ -167,8 +166,7 @@ void generateBeanDefinitionMethodWhenHasInstancePostProcessorAndFactoryMethodGen
.addParameter(RegisteredBean.class, "registeredBean")
.addParameter(TestBean.class, "testBean")
.returns(TestBean.class).addCode("return new $T($S);", TestBean.class, "postprocessed"));
beanRegistrationCode.addInstancePostProcessor(MethodReference.ofStatic(
beanRegistrationCode.getClassName(), generatedMethod.getName()));
beanRegistrationCode.addInstancePostProcessor(generatedMethod.toMethodReference());
};
List<BeanRegistrationAotContribution> aotContributions = Collections
.singletonList(aotContribution);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,6 @@
import org.springframework.aop.framework.autoproxy.AutoProxyUtils;
import org.springframework.aot.generate.GeneratedMethod;
import org.springframework.aot.generate.GenerationContext;
import org.springframework.aot.generate.MethodReference;
import org.springframework.aot.hint.ResourceHints;
import org.springframework.aot.hint.TypeReference;
import org.springframework.beans.PropertyValues;
Expand Down Expand Up @@ -536,7 +535,7 @@ public void applyTo(GenerationContext generationContext,
.add("addImportAwareBeanPostProcessors", method ->
generateAddPostProcessorMethod(method, mappings));
beanFactoryInitializationCode
.addInitializer(MethodReference.of(generatedMethod.getName()));
.addInitializer(generatedMethod.toMethodReference());
ResourceHints hints = generationContext.getRuntimeHints().resources();
mappings.forEach(
(target, from) -> hints.registerType(TypeReference.of(from)));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ public final class GeneratedClass {
GeneratedClass(ClassName name, Consumer<TypeSpec.Builder> type) {
this.name = name;
this.type = type;
this.methods = new GeneratedMethods(this::generateSequencedMethodName);
this.methods = new GeneratedMethods(name, this::generateSequencedMethodName);
}


Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,18 +18,24 @@

import java.util.function.Consumer;

import javax.lang.model.element.Modifier;

import org.springframework.javapoet.ClassName;
import org.springframework.javapoet.MethodSpec;
import org.springframework.util.Assert;

/**
* A generated method.
*
* @author Phillip Webb
* @author Stephane Nicoll
* @since 6.0
* @see GeneratedMethods
*/
public final class GeneratedMethod {

private final ClassName className;

private final String name;

private final MethodSpec methodSpec;
Expand All @@ -39,12 +45,14 @@ public final class GeneratedMethod {
* Create a new {@link GeneratedMethod} instance with the given name. This
* constructor is package-private since names should only be generated via
* {@link GeneratedMethods}.
* @param className the declaring class of the method
* @param name the generated method name
* @param method consumer to generate the method
*/
GeneratedMethod(String name, Consumer<MethodSpec.Builder> method) {
GeneratedMethod(ClassName className, String name, Consumer<MethodSpec.Builder> method) {
this.className = className;
this.name = name;
MethodSpec.Builder builder = MethodSpec.methodBuilder(getName());
MethodSpec.Builder builder = MethodSpec.methodBuilder(this.name);
method.accept(builder);
this.methodSpec = builder.build();
Assert.state(this.name.equals(this.methodSpec.name),
Expand All @@ -60,6 +68,16 @@ public String getName() {
return this.name;
}

/**
* Return a {@link MethodReference} to this generated method.
* @return a method reference
*/
public MethodReference toMethodReference() {
return (this.methodSpec.modifiers.contains(Modifier.STATIC)
? MethodReference.ofStatic(this.className, this.name)
: MethodReference.of(this.className, this.name));
}

/**
* Return the {@link MethodSpec} for this generated method.
* @return the method spec
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
import java.util.function.Function;
import java.util.stream.Stream;

import org.springframework.javapoet.ClassName;
import org.springframework.javapoet.MethodSpec;
import org.springframework.javapoet.MethodSpec.Builder;
import org.springframework.util.Assert;
Expand All @@ -30,11 +31,14 @@
* A managed collection of generated methods.
*
* @author Phillip Webb
* @author Stephane Nicoll
* @since 6.0
* @see GeneratedMethod
*/
public class GeneratedMethods {

private final ClassName className;

private final Function<MethodName, String> methodNameGenerator;

private final MethodName prefix;
Expand All @@ -44,18 +48,22 @@ public class GeneratedMethods {
/**
* Create a new {@link GeneratedMethods} using the specified method name
* generator.
* @param className the declaring class name
* @param methodNameGenerator the method name generator
*/
GeneratedMethods(Function<MethodName, String> methodNameGenerator) {
GeneratedMethods(ClassName className, Function<MethodName, String> methodNameGenerator) {
Assert.notNull(className, "'className' must not be null");
Assert.notNull(methodNameGenerator, "'methodNameGenerator' must not be null");
this.className = className;
this.methodNameGenerator = methodNameGenerator;
this.prefix = MethodName.NONE;
this.generatedMethods = new ArrayList<>();
}

private GeneratedMethods(Function<MethodName, String> methodNameGenerator,
private GeneratedMethods(ClassName className, Function<MethodName, String> methodNameGenerator,
MethodName prefix, List<GeneratedMethod> generatedMethods) {

this.className = className;
this.methodNameGenerator = methodNameGenerator;
this.prefix = prefix;
this.generatedMethods = generatedMethods;
Expand All @@ -82,15 +90,16 @@ public GeneratedMethod add(String[] suggestedNameParts, Consumer<Builder> method
Assert.notNull(suggestedNameParts, "'suggestedNameParts' must not be null");
Assert.notNull(method, "'method' must not be null");
String generatedName = this.methodNameGenerator.apply(this.prefix.and(suggestedNameParts));
GeneratedMethod generatedMethod = new GeneratedMethod(generatedName, method);
GeneratedMethod generatedMethod = new GeneratedMethod(this.className, generatedName, method);
this.generatedMethods.add(generatedMethod);
return generatedMethod;
}


public GeneratedMethods withPrefix(String prefix) {
Assert.notNull(prefix, "'prefix' must not be null");
return new GeneratedMethods(this.methodNameGenerator, this.prefix.and(prefix), this.generatedMethods);
return new GeneratedMethods(this.className, this.methodNameGenerator,
this.prefix.and(prefix), this.generatedMethods);
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,12 @@

import java.util.function.Consumer;

import javax.lang.model.element.Modifier;

import org.junit.jupiter.api.Test;

import org.springframework.javapoet.ClassName;
import org.springframework.javapoet.CodeBlock;
import org.springframework.javapoet.MethodSpec;

import static org.assertj.core.api.Assertions.assertThat;
Expand All @@ -29,30 +33,55 @@
* Tests for {@link GeneratedMethod}.
*
* @author Phillip Webb
* @author Stephane Nicoll
*/
class GeneratedMethodTests {

private static final Consumer<MethodSpec.Builder> methodSpecCustomizer = method -> {};
private static final ClassName TEST_CLASS_NAME = ClassName.get("com.example", "Test");

private static final Consumer<MethodSpec.Builder> emptyMethod = method -> {};

private static final String NAME = "spring";

@Test
void getNameReturnsName() {
GeneratedMethod generatedMethod = new GeneratedMethod(NAME, methodSpecCustomizer);
GeneratedMethod generatedMethod = new GeneratedMethod(TEST_CLASS_NAME, NAME, emptyMethod);
assertThat(generatedMethod.getName()).isSameAs(NAME);
}

@Test
void generateMethodSpecReturnsMethodSpec() {
GeneratedMethod generatedMethod = new GeneratedMethod(NAME, method -> method.addJavadoc("Test"));
GeneratedMethod generatedMethod = create(method -> method.addJavadoc("Test"));
assertThat(generatedMethod.getMethodSpec().javadoc).asString().contains("Test");
}

@Test
void generateMethodSpecWhenMethodNameIsChangedThrowsException() {
assertThatIllegalStateException().isThrownBy(() ->
new GeneratedMethod(NAME, method -> method.setName("badname")).getMethodSpec())
.withMessage("'method' consumer must not change the generated method name");
create(method -> method.setName("badname")).getMethodSpec())
.withMessage("'method' consumer must not change the generated method name");
}

@Test
void toMethodReferenceWithInstanceMethod() {
GeneratedMethod generatedMethod = create(emptyMethod);
MethodReference methodReference = generatedMethod.toMethodReference();
assertThat(methodReference).isNotNull();
assertThat(methodReference.toInvokeCodeBlock("test"))
.isEqualTo(CodeBlock.of("test.spring()"));
}

@Test
void toMethodReferenceWithStaticMethod() {
GeneratedMethod generatedMethod = create(method -> method.addModifiers(Modifier.STATIC));
MethodReference methodReference = generatedMethod.toMethodReference();
assertThat(methodReference).isNotNull();
assertThat(methodReference.toInvokeCodeBlock())
.isEqualTo(CodeBlock.of("com.example.Test.spring()"));
}

private GeneratedMethod create(Consumer<MethodSpec.Builder> method) {
return new GeneratedMethod(TEST_CLASS_NAME, NAME, method);
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@

import org.junit.jupiter.api.Test;

import org.springframework.javapoet.ClassName;
import org.springframework.javapoet.MethodSpec;

import static org.assertj.core.api.Assertions.assertThat;
Expand All @@ -32,46 +33,57 @@
* Tests for {@link GeneratedMethods}.
*
* @author Phillip Webb
* @author Stephane Nicoll
*/
class GeneratedMethodsTests {

private static final ClassName TEST_CLASS_NAME = ClassName.get("com.example", "Test");

private static final Consumer<MethodSpec.Builder> methodSpecCustomizer = method -> {};

private final GeneratedMethods methods = new GeneratedMethods(MethodName::toString);
private final GeneratedMethods methods = new GeneratedMethods(TEST_CLASS_NAME, MethodName::toString);

@Test
void createWhenClassNameIsNullThrowsException() {
assertThatIllegalArgumentException().isThrownBy(() ->
new GeneratedMethods(null, MethodName::toString))
.withMessage("'className' must not be null");
}

@Test
void createWhenMethodNameGeneratorIsNullThrowsException() {
assertThatIllegalArgumentException().isThrownBy(() -> new GeneratedMethods(null))
assertThatIllegalArgumentException().isThrownBy(() ->
new GeneratedMethods(TEST_CLASS_NAME, null))
.withMessage("'methodNameGenerator' must not be null");
}

@Test
void createWithExistingGeneratorUsesGenerator() {
Function<MethodName, String> generator = name -> "__" + name.toString();
GeneratedMethods methods = new GeneratedMethods(generator);
GeneratedMethods methods = new GeneratedMethods(TEST_CLASS_NAME, generator);
assertThat(methods.add("test", methodSpecCustomizer).getName()).hasToString("__test");
}

@Test
void addWithStringNameWhenSuggestedMethodIsNullThrowsException() {
assertThatIllegalArgumentException().isThrownBy(() ->
this.methods.add((String) null, methodSpecCustomizer))
.withMessage("'suggestedName' must not be null");
this.methods.add((String) null, methodSpecCustomizer))
.withMessage("'suggestedName' must not be null");
}

@Test
void addWithStringNameWhenMethodIsNullThrowsException() {
assertThatIllegalArgumentException().isThrownBy(() ->
this.methods.add("test", null))
.withMessage("'method' must not be null");
this.methods.add("test", null))
.withMessage("'method' must not be null");
}

@Test
void addAddsMethod() {
this.methods.add("springBeans", methodSpecCustomizer);
this.methods.add("springContext", methodSpecCustomizer);
assertThat(this.methods.stream().map(GeneratedMethod::getName).map(Object::toString))
.containsExactly("springBeans", "springContext");
.containsExactly("springBeans", "springContext");
}

@Test
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,7 @@ public CodeBlock generateInstanceSupplierCode(GenerationContext generationContex
List.class, toCodeBlock(persistenceManagedTypes.getManagedPackages()));
method.addStatement("return $T.of($L, $L)", beanType, "managedClassNames", "managedPackages");
});
return CodeBlock.of("() -> $T.$L()", beanRegistrationCode.getClassName(), generatedMethod.getName());
return generatedMethod.toMethodReference().toCodeBlock();
}

private CodeBlock toCodeBlock(List<String> values) {
Expand Down
Loading

0 comments on commit 0934319

Please sign in to comment.