Update ClassNameGenerator to work with a ClassName target

This commit updates ClassNameGenerator so that it uses a ClassName for
its default target. This makes sure that a target that has been
generated can be used.

See gh-29027
This commit is contained in:
Stephane Nicoll
2022-09-22 14:22:17 +02:00
parent 1707a22c18
commit 8ef850ff91
9 changed files with 99 additions and 81 deletions

View File

@@ -33,7 +33,6 @@ import org.springframework.aot.generate.GenerationContext;
import org.springframework.aot.generate.MethodReference;
import org.springframework.aot.generate.MethodReference.ArgumentCodeGenerator;
import org.springframework.aot.test.generate.TestGenerationContext;
import org.springframework.aot.test.generate.TestTarget;
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
import org.springframework.beans.factory.support.RegisteredBean;
import org.springframework.beans.factory.support.RootBeanDefinition;
@@ -97,7 +96,7 @@ class BeanRegistrationsAotContributionTests {
@Test
void applyToWhenHasNameGeneratesPrefixedFeatureName() {
this.generationContext = new TestGenerationContext(
new ClassNameGenerator(TestTarget.class, "Management"));
new ClassNameGenerator(TestGenerationContext.TEST_TARGET, "Management"));
this.beanFactoryInitializationCode = new MockBeanFactoryInitializationCode(this.generationContext);
Map<String, BeanDefinitionMethodGenerator> registrations = new LinkedHashMap<>();
RegisteredBean registeredBean = registerBean(

View File

@@ -23,6 +23,7 @@ import org.springframework.aot.generate.DefaultGenerationContext;
import org.springframework.aot.generate.GenerationContext;
import org.springframework.aot.generate.InMemoryGeneratedFiles;
import org.springframework.core.test.tools.TestCompiler;
import org.springframework.javapoet.ClassName;
/**
* {@link GenerationContext} test implementation that uses
@@ -35,6 +36,11 @@ import org.springframework.core.test.tools.TestCompiler;
*/
public class TestGenerationContext extends DefaultGenerationContext implements UnaryOperator<TestCompiler> {
/**
* The default test target {@link ClassName}.
*/
public static final ClassName TEST_TARGET = ClassName.get("com.example", "TestTarget");
/**
* Create an instance using the specified {@link ClassNameGenerator}.
* @param classNameGenerator the class name generator to use
@@ -47,15 +53,15 @@ public class TestGenerationContext extends DefaultGenerationContext implements U
* Create an instance using the specified {@code target}.
* @param target the default target class to use
*/
public TestGenerationContext(Class<?> target) {
public TestGenerationContext(ClassName target) {
this(new ClassNameGenerator(target));
}
/**
* Create an instance using {@link TestTarget} as the {@code target}.
* Create an instance using {@link #TEST_TARGET} as the {@code target}.
*/
public TestGenerationContext() {
this(TestTarget.class);
this(TEST_TARGET);
}

View File

@@ -1,26 +0,0 @@
/*
* Copyright 2002-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.aot.test.generate;
/**
* A <em>default target class</em> used by tests of code generation.
*
* @author Stephane Nicoll
* @since 6.0
*/
public class TestTarget {
}

View File

@@ -27,8 +27,8 @@ import org.springframework.util.ClassUtils;
import org.springframework.util.StringUtils;
/**
* Generate unique class names based on a target {@link Class} and a feature
* name.
* Generate unique class names based on a target {@link ClassName} and a
* feature name.
*
* <p>This class is stateful, so the same instance should be used for all name
* generation.
@@ -43,7 +43,7 @@ public final class ClassNameGenerator {
private static final String AOT_FEATURE = "Aot";
private final Class<?> defaultTarget;
private final ClassName defaultTarget;
private final String featureNamePrefix;
@@ -55,7 +55,7 @@ public final class ClassNameGenerator {
* feature name prefix.
* @param defaultTarget the default target class to use
*/
public ClassNameGenerator(Class<?> defaultTarget) {
public ClassNameGenerator(ClassName defaultTarget) {
this(defaultTarget, "");
}
@@ -65,11 +65,11 @@ public final class ClassNameGenerator {
* @param defaultTarget the default target class to use
* @param featureNamePrefix the prefix to use to qualify feature names
*/
public ClassNameGenerator(Class<?> defaultTarget, String featureNamePrefix) {
public ClassNameGenerator(ClassName defaultTarget, String featureNamePrefix) {
this(defaultTarget, featureNamePrefix, new ConcurrentHashMap<>());
}
private ClassNameGenerator(Class<?> defaultTarget, String featureNamePrefix,
private ClassNameGenerator(ClassName defaultTarget, String featureNamePrefix,
Map<String, AtomicInteger> sequenceGenerator) {
Assert.notNull(defaultTarget, "'defaultTarget' must not be null");
this.defaultTarget = defaultTarget;
@@ -98,16 +98,16 @@ public final class ClassNameGenerator {
* {@code null} to use the main target
* @return a unique generated class name
*/
public ClassName generateClassName(String featureName, @Nullable Class<?> target) {
public ClassName generateClassName(String featureName, @Nullable ClassName target) {
return generateSequencedClassName(getRootName(featureName, target));
}
private String getRootName(String featureName, @Nullable Class<?> target) {
private String getRootName(String featureName, @Nullable ClassName target) {
Assert.hasLength(featureName, "'featureName' must not be empty");
featureName = clean(featureName);
Class<?> targetToUse = (target != null ? target : this.defaultTarget);
ClassName targetToUse = (target != null ? target : this.defaultTarget);
String featureNameToUse = this.featureNamePrefix + featureName;
return targetToUse.getName().replace("$", "_") + SEPARATOR + StringUtils.capitalize(featureNameToUse);
return toName(targetToUse).replace("$", "_") + SEPARATOR + StringUtils.capitalize(featureNameToUse);
}
private String clean(String name) {
@@ -147,4 +147,8 @@ public final class ClassNameGenerator {
this.sequenceGenerator);
}
private static String toName(ClassName className) {
return GeneratedTypeReference.of(className).getName();
}
}

View File

@@ -99,7 +99,7 @@ public class GeneratedClasses {
* @return an existing or newly generated class
*/
public GeneratedClass getOrAddForFeatureComponent(String featureName,
Class<?> targetComponent, Consumer<TypeSpec.Builder> type) {
ClassName targetComponent, Consumer<TypeSpec.Builder> type) {
Assert.hasLength(featureName, "'featureName' must not be empty");
Assert.notNull(targetComponent, "'targetComponent' must not be null");
@@ -111,6 +111,24 @@ public class GeneratedClasses {
return generatedClass;
}
/**
* Get or add a generated class for the specified {@code featureName}
* targeting the specified {@code component}. If this method has previously
* been called with the given {@code featureName}/{@code target} the
* existing class will be returned, otherwise a new class will be generated,
* otherwise a new class will be generated.
* @param featureName the name of the feature to associate with the
* generated class
* @param targetComponent the target component
* @param type a {@link Consumer} used to build the type
* @return an existing or newly generated class
*/
public GeneratedClass getOrAddForFeatureComponent(String featureName,
Class<?> targetComponent, Consumer<TypeSpec.Builder> type) {
return getOrAddForFeatureComponent(featureName, ClassName.get(targetComponent), type);
}
/**
* Add a new generated class for the specified {@code featureName} and no
* particular component.
@@ -135,7 +153,7 @@ public class GeneratedClasses {
* @return the newly generated class
*/
public GeneratedClass addForFeatureComponent(String featureName,
Class<?> targetComponent, Consumer<TypeSpec.Builder> type) {
ClassName targetComponent, Consumer<TypeSpec.Builder> type) {
Assert.hasLength(featureName, "'featureName' must not be empty");
Assert.notNull(targetComponent, "'targetComponent' must not be null");
@@ -143,8 +161,23 @@ public class GeneratedClasses {
return createAndAddGeneratedClass(featureName, targetComponent, type);
}
/**
* Add a new generated class for the specified {@code featureName} targeting
* the specified {@code component}.
* @param featureName the name of the feature to associate with the
* generated class
* @param targetComponent the target component
* @param type a {@link Consumer} used to build the type
* @return the newly generated class
*/
public GeneratedClass addForFeatureComponent(String featureName,
Class<?> targetComponent, Consumer<TypeSpec.Builder> type) {
return addForFeatureComponent(featureName, ClassName.get(targetComponent), type);
}
private GeneratedClass createAndAddGeneratedClass(String featureName,
@Nullable Class<?> targetComponent, Consumer<TypeSpec.Builder> type) {
@Nullable ClassName targetComponent, Consumer<TypeSpec.Builder> type) {
ClassName className = this.classNameGenerator.generateClassName(featureName, targetComponent);
GeneratedClass generatedClass = new GeneratedClass(className, type);
@@ -171,7 +204,7 @@ public class GeneratedClasses {
this.classes, this.classesByOwner);
}
private record Owner(String featureNamePrefix, String featureName, @Nullable Class<?> target) {
private record Owner(String featureNamePrefix, String featureName, @Nullable ClassName target) {
}
}

View File

@@ -32,71 +32,69 @@ import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException
*/
class ClassNameGeneratorTests {
private final ClassNameGenerator generator = new ClassNameGenerator(Object.class);
private static final ClassName TEST_TARGET = ClassName.get("com.example", "Test");
private final ClassNameGenerator generator = new ClassNameGenerator(TEST_TARGET);
@Test
void generateClassNameWhenTargetClassIsNullUsesMainTarget() {
ClassName generated = this.generator.generateClassName("test", null);
assertThat(generated).hasToString("java.lang.Object__Test");
assertThat(generated).hasToString("com.example.Test__Test");
}
@Test
void generateClassNameUseFeatureNamePrefix() {
ClassName generated = new ClassNameGenerator(Object.class, "One")
.generateClassName("test", InputStream.class);
ClassName generated = new ClassNameGenerator(TEST_TARGET, "One")
.generateClassName("test", ClassName.get(InputStream.class));
assertThat(generated).hasToString("java.io.InputStream__OneTest");
}
@Test
void generateClassNameWithNoTextFeatureNamePrefix() {
ClassName generated = new ClassNameGenerator(Object.class, " ")
.generateClassName("test", InputStream.class);
ClassName generated = new ClassNameGenerator(TEST_TARGET, " ")
.generateClassName("test", ClassName.get(InputStream.class));
assertThat(generated).hasToString("java.io.InputStream__Test");
}
@Test
void generatedClassNameWhenFeatureIsEmptyThrowsException() {
assertThatIllegalArgumentException()
.isThrownBy(() -> this.generator.generateClassName("", InputStream.class))
.isThrownBy(() -> this.generator.generateClassName("", ClassName.get(InputStream.class)))
.withMessage("'featureName' must not be empty");
}
@Test
void generatedClassNameWhenFeatureIsNotAllLettersThrowsException() {
assertThat(this.generator.generateClassName("name!", InputStream.class))
assertThat(this.generator.generateClassName("name!", ClassName.get(InputStream.class)))
.hasToString("java.io.InputStream__Name");
assertThat(this.generator.generateClassName("1NameHere", InputStream.class))
assertThat(this.generator.generateClassName("1NameHere", ClassName.get(InputStream.class)))
.hasToString("java.io.InputStream__NameHere");
assertThat(this.generator.generateClassName("Y0pe", InputStream.class))
assertThat(this.generator.generateClassName("Y0pe", ClassName.get(InputStream.class)))
.hasToString("java.io.InputStream__YPe");
}
@Test
void generateClassNameWithClassWhenLowercaseFeatureNameGeneratesName() {
ClassName generated = this.generator.generateClassName("bytes", InputStream.class);
ClassName generated = this.generator.generateClassName("bytes", ClassName.get(InputStream.class));
assertThat(generated).hasToString("java.io.InputStream__Bytes");
}
@Test
void generateClassNameWithClassWhenInnerClassGeneratesName() {
ClassName generated = this.generator.generateClassName("EventListener", TestBean.class);
ClassName innerBean = ClassName.get("com.example", "Test", "InnerBean");
ClassName generated = this.generator.generateClassName("EventListener", innerBean);
assertThat(generated)
.hasToString("org.springframework.aot.generate.ClassNameGeneratorTests_TestBean__EventListener");
.hasToString("com.example.Test_InnerBean__EventListener");
}
@Test
void generateClassWithClassWhenMultipleCallsGeneratesSequencedName() {
ClassName generated1 = this.generator.generateClassName("bytes", InputStream.class);
ClassName generated2 = this.generator.generateClassName("bytes", InputStream.class);
ClassName generated3 = this.generator.generateClassName("bytes", InputStream.class);
ClassName generated1 = this.generator.generateClassName("bytes",ClassName.get(InputStream.class));
ClassName generated2 = this.generator.generateClassName("bytes", ClassName.get(InputStream.class));
ClassName generated3 = this.generator.generateClassName("bytes", ClassName.get(InputStream.class));
assertThat(generated1).hasToString("java.io.InputStream__Bytes");
assertThat(generated2).hasToString("java.io.InputStream__Bytes1");
assertThat(generated3).hasToString("java.io.InputStream__Bytes2");
}
static class TestBean {
}
}

View File

@@ -22,6 +22,7 @@ import org.junit.jupiter.api.Test;
import org.springframework.aot.generate.GeneratedFiles.Kind;
import org.springframework.aot.hint.RuntimeHints;
import org.springframework.javapoet.ClassName;
import org.springframework.javapoet.TypeSpec;
import static org.assertj.core.api.Assertions.assertThat;
@@ -35,10 +36,12 @@ import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException
*/
class DefaultGenerationContextTests {
private static final ClassName SAMPLE_TARGET = ClassName.get("com.example", "SampleTarget");
private static final Consumer<TypeSpec.Builder> typeSpecCustomizer = type -> {};
private final GeneratedClasses generatedClasses = new GeneratedClasses(
new ClassNameGenerator(SampleTarget.class));
new ClassNameGenerator(SAMPLE_TARGET));
private final InMemoryGeneratedFiles generatedFiles = new InMemoryGeneratedFiles();
@@ -48,7 +51,7 @@ class DefaultGenerationContextTests {
@Test
void createWithOnlyGeneratedFilesCreatesContext() {
DefaultGenerationContext context = new DefaultGenerationContext(
new ClassNameGenerator(SampleTarget.class), this.generatedFiles);
new ClassNameGenerator(SAMPLE_TARGET), this.generatedFiles);
assertThat(context.getGeneratedFiles()).isSameAs(this.generatedFiles);
assertThat(context.getRuntimeHints()).isInstanceOf(RuntimeHints.class);
}
@@ -109,7 +112,7 @@ class DefaultGenerationContextTests {
@Test
void withNameUpdateNamingConvention() {
DefaultGenerationContext context = new DefaultGenerationContext(
new ClassNameGenerator(SampleTarget.class), this.generatedFiles);
new ClassNameGenerator(SAMPLE_TARGET), this.generatedFiles);
GenerationContext anotherContext = context.withName("Another");
GeneratedClass generatedClass = anotherContext.getGeneratedClasses()
.addForFeature("Test", typeSpecCustomizer);
@@ -119,7 +122,7 @@ class DefaultGenerationContextTests {
@Test
void withNameKeepsTrackOfAllGeneratedFiles() {
DefaultGenerationContext context = new DefaultGenerationContext(
new ClassNameGenerator(SampleTarget.class), this.generatedFiles);
new ClassNameGenerator(SAMPLE_TARGET), this.generatedFiles);
context.getGeneratedClasses().addForFeature("Test", typeSpecCustomizer);
GenerationContext anotherContext = context.withName("Another");
assertThat(anotherContext.getGeneratedClasses()).isNotSameAs(context.getGeneratedClasses());
@@ -133,7 +136,7 @@ class DefaultGenerationContextTests {
@Test
void withNameGeneratesUniqueName() {
DefaultGenerationContext context = new DefaultGenerationContext(
new ClassNameGenerator(Object.class), this.generatedFiles);
new ClassNameGenerator(SAMPLE_TARGET), this.generatedFiles);
context.withName("Test").getGeneratedClasses()
.addForFeature("Feature", typeSpecCustomizer);
context.withName("Test").getGeneratedClasses()
@@ -142,11 +145,9 @@ class DefaultGenerationContextTests {
.addForFeature("Feature", typeSpecCustomizer);
context.writeGeneratedContent();
assertThat(this.generatedFiles.getGeneratedFiles(Kind.SOURCE)).containsOnlyKeys(
"java/lang/Object__TestFeature.java",
"java/lang/Object__Test1Feature.java",
"java/lang/Object__Test2Feature.java");
"com/example/SampleTarget__TestFeature.java",
"com/example/SampleTarget__Test1Feature.java",
"com/example/SampleTarget__Test2Feature.java");
}
static class SampleTarget {}
}

View File

@@ -22,6 +22,7 @@ import java.util.function.Consumer;
import org.junit.jupiter.api.Test;
import org.springframework.aot.generate.GeneratedFiles.Kind;
import org.springframework.javapoet.ClassName;
import org.springframework.javapoet.TypeSpec;
import static org.assertj.core.api.Assertions.assertThat;
@@ -42,7 +43,7 @@ class GeneratedClassesTests {
private static final Consumer<TypeSpec.Builder> emptyTypeCustomizer = type -> {};
private final GeneratedClasses generatedClasses = new GeneratedClasses(
new ClassNameGenerator(Object.class));
new ClassNameGenerator(ClassName.get("com.example", "Test")));
@Test
void createWhenClassNameGeneratorIsNullThrowsException() {
@@ -76,7 +77,7 @@ class GeneratedClassesTests {
@Test
void addForFeatureUsesDefaultTarget() {
GeneratedClass generatedClass = this.generatedClasses.addForFeature("Test", emptyTypeCustomizer);
assertThat(generatedClass.getName()).hasToString("java.lang.Object__Test");
assertThat(generatedClass.getName()).hasToString("com.example.Test__Test");
}
@Test

View File

@@ -234,7 +234,7 @@ public class TestContextAotGenerator {
}
DefaultGenerationContext createGenerationContext(Class<?> testClass) {
ClassNameGenerator classNameGenerator = new ClassNameGenerator(testClass);
ClassNameGenerator classNameGenerator = new ClassNameGenerator(ClassName.get(testClass));
DefaultGenerationContext generationContext =
new DefaultGenerationContext(classNameGenerator, this.generatedFiles, this.runtimeHints);
return generationContext.withName(nextTestContextId());
@@ -245,7 +245,8 @@ public class TestContextAotGenerator {
}
private void generateTestAotMappings(MultiValueMap<ClassName, Class<?>> initializerClassMappings) {
ClassNameGenerator classNameGenerator = new ClassNameGenerator(AotTestContextInitializers.class);
ClassNameGenerator classNameGenerator = new ClassNameGenerator(
ClassName.get(AotTestContextInitializers.class));
DefaultGenerationContext generationContext =
new DefaultGenerationContext(classNameGenerator, this.generatedFiles, this.runtimeHints);
GeneratedClasses generatedClasses = generationContext.getGeneratedClasses();
@@ -258,7 +259,8 @@ public class TestContextAotGenerator {
}
private void generateAotTestAttributes() {
ClassNameGenerator classNameGenerator = new ClassNameGenerator(AotTestAttributes.class);
ClassNameGenerator classNameGenerator = new ClassNameGenerator(
ClassName.get(AotTestAttributes.class));
DefaultGenerationContext generationContext =
new DefaultGenerationContext(classNameGenerator, this.generatedFiles, this.runtimeHints);
GeneratedClasses generatedClasses = generationContext.getGeneratedClasses();