Harmonize generated class name conventions
This commit moves the responsibility of naming classes to the GenerationContext. This was already largely the case before, except that the concept of a "mainTarget" and "featureNamePrefix" was specific to bean factory initialization contributors. ClassNameGenerator should now be instantiated with a default target and an optional feature name prefix. As a result, it does no longer generate class names in the "__" package. GeneratedClasses can now provide a new, unique, GeneratedClass or offer a container for retrieving the same GeneratedClass based on an identifier. This lets all contributors use this facility rather than creating JavaFile manually. This also means that ClassNameGenerator is no longer exposed. Because the naming conventions are now part of the GenerationContext, it is required to be able to retrieve a specialized version of it if a code generation round needs to use different naming conventions. A new withName method has been added to that effect. Closes gh-28585
This commit is contained in:
@@ -1,75 +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.generate;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
|
||||
import org.springframework.javapoet.ClassName;
|
||||
import org.springframework.javapoet.JavaFile;
|
||||
|
||||
/**
|
||||
* Generates new {@link GeneratedClass} instances.
|
||||
*
|
||||
* @author Phillip Webb
|
||||
* @since 6.0
|
||||
* @see GeneratedMethods
|
||||
*/
|
||||
public interface ClassGenerator {
|
||||
|
||||
/**
|
||||
* Get or generate a new {@link GeneratedClass} for a given java file
|
||||
* generator, target and feature name.
|
||||
* @param javaFileGenerator the java file generator
|
||||
* @param target the target of the newly generated class
|
||||
* @param featureName the name of the feature that the generated class
|
||||
* supports
|
||||
* @return a {@link GeneratedClass} instance
|
||||
*/
|
||||
GeneratedClass getOrGenerateClass(JavaFileGenerator javaFileGenerator,
|
||||
Class<?> target, String featureName);
|
||||
|
||||
|
||||
/**
|
||||
* Strategy used to generate the java file for the generated class.
|
||||
* Implementations of this interface are included as part of the key used to
|
||||
* identify classes that have already been created and as such should be
|
||||
* static final instances or implement a valid
|
||||
* {@code equals}/{@code hashCode}.
|
||||
*/
|
||||
@FunctionalInterface
|
||||
interface JavaFileGenerator {
|
||||
|
||||
/**
|
||||
* Generate the file {@link JavaFile} to be written.
|
||||
* @param className the class name of the file
|
||||
* @param methods the generated methods that must be included
|
||||
* @return the generated files
|
||||
*/
|
||||
JavaFile generateJavaFile(ClassName className, GeneratedMethods methods);
|
||||
|
||||
/**
|
||||
* Return method names that must not be generated.
|
||||
* @return the reserved method names
|
||||
*/
|
||||
default Collection<String> getReservedMethodNames() {
|
||||
return Collections.emptySet();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -27,10 +27,9 @@ import org.springframework.util.ClassUtils;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
/**
|
||||
* Generate unique class names based on an optional target {@link Class} and
|
||||
* a feature name. This class is stateful so the same instance should be used
|
||||
* for all name generation. Most commonly the class name generator is obtained
|
||||
* via a {@link GenerationContext}.
|
||||
* Generate unique class names based on target {@link Class} and a feature
|
||||
* name. This class is stateful so the same instance should be used for all
|
||||
* name generation.
|
||||
*
|
||||
* @author Phillip Webb
|
||||
* @author Stephane Nicoll
|
||||
@@ -40,38 +39,92 @@ public final class ClassNameGenerator {
|
||||
|
||||
private static final String SEPARATOR = "__";
|
||||
|
||||
private static final String AOT_PACKAGE = "__.";
|
||||
|
||||
private static final String AOT_FEATURE = "Aot";
|
||||
|
||||
private final Map<String, AtomicInteger> sequenceGenerator = new ConcurrentHashMap<>();
|
||||
private final Class<?> defaultTarget;
|
||||
|
||||
private final String featureNamePrefix;
|
||||
|
||||
private final Map<String, AtomicInteger> sequenceGenerator;
|
||||
|
||||
/**
|
||||
* Create a new instance using the specified {@code defaultTarget} and no
|
||||
* feature name prefix.
|
||||
* @param defaultTarget the default target class to use
|
||||
*/
|
||||
public ClassNameGenerator(Class<?> defaultTarget) {
|
||||
this(defaultTarget, "");
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new instance using the specified {@code defaultTarget} and
|
||||
* feature name prefix.
|
||||
* @param defaultTarget the default target class to use
|
||||
* @param featureNamePrefix the prefix to use to qualify feature names
|
||||
*/
|
||||
public ClassNameGenerator(Class<?> defaultTarget, String featureNamePrefix) {
|
||||
this(defaultTarget, featureNamePrefix, new ConcurrentHashMap<>());
|
||||
}
|
||||
|
||||
private ClassNameGenerator(Class<?> defaultTarget, String featureNamePrefix,
|
||||
Map<String, AtomicInteger> sequenceGenerator) {
|
||||
this.defaultTarget = defaultTarget;
|
||||
this.featureNamePrefix = (!StringUtils.hasText(featureNamePrefix) ? "" : featureNamePrefix);
|
||||
this.sequenceGenerator = sequenceGenerator;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Generate a unique {@link ClassName} based on the specified {@code target}
|
||||
* class and {@code featureName}. If a {@code target} is specified, the
|
||||
* generated class name is a suffixed version of it.
|
||||
* <p>For instance, a {@code com.example.Demo} target with an
|
||||
* {@code Initializer} feature name leads to a
|
||||
* {@code com.example.Demo__Initializer} generated class name. If such a
|
||||
* feature was already requested for this target, a counter is used to
|
||||
* ensure uniqueness.
|
||||
* <p>If there is no target, the {@code featureName} is used to generate the
|
||||
* class name in the {@value #AOT_PACKAGE} package.
|
||||
* Generate a unique {@link ClassName} based on the specified
|
||||
* {@code featureName} and {@code target}. If the {@code target} is
|
||||
* {@code null}, the configured main target of this instance is used.
|
||||
* <p>The class name is a suffixed version of the target. For instance, a
|
||||
* {@code com.example.Demo} target with an {@code Initializer} feature name
|
||||
* leads to a {@code com.example.Demo__Initializer} generated class name.
|
||||
* The feature name is qualified by the configured feature name prefix,
|
||||
* if any.
|
||||
* <p>Generated class names are unique. If such a feature was already
|
||||
* requested for this target, a counter is used to ensure uniqueness.
|
||||
* @param target the class the newly generated class relates to, or
|
||||
* {@code null} if there is not target
|
||||
* {@code null} to use the main target
|
||||
* @param featureName the name of the feature that the generated class
|
||||
* supports
|
||||
* @return a unique generated class name
|
||||
*/
|
||||
public ClassName generateClassName(@Nullable Class<?> target, String featureName) {
|
||||
return generateSequencedClassName(getClassName(target, featureName));
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a class name based on the specified {@code target} and
|
||||
* {@code featureName}. This uses the same algorithm as
|
||||
* {@link #generateClassName(Class, String)} but does not register
|
||||
* the class name, nor add a unique suffix to it if necessary.
|
||||
* @param target the class the newly generated class relates to, or
|
||||
* {@code null} to use the main target
|
||||
* @param featureName the name of the feature that the generated class
|
||||
* supports
|
||||
* @return the class name
|
||||
*/
|
||||
String getClassName(@Nullable Class<?> target, String featureName) {
|
||||
Assert.hasLength(featureName, "'featureName' must not be empty");
|
||||
featureName = clean(featureName);
|
||||
if (target != null) {
|
||||
return generateSequencedClassName(target.getName().replace("$", "_")
|
||||
+ SEPARATOR + StringUtils.capitalize(featureName));
|
||||
}
|
||||
return generateSequencedClassName(AOT_PACKAGE + featureName);
|
||||
Class<?> targetToUse = (target != null ? target : this.defaultTarget);
|
||||
String featureNameToUse = this.featureNamePrefix + featureName;
|
||||
return targetToUse.getName().replace("$", "_")
|
||||
+ SEPARATOR + StringUtils.capitalize(featureNameToUse);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a new {@link ClassNameGenerator} instance for the specified
|
||||
* feature name prefix, keeping track of all the class names generated
|
||||
* by this instance.
|
||||
* @param featureNamePrefix the feature name prefix to use
|
||||
* @return a new instance for the specified feature name prefix
|
||||
*/
|
||||
ClassNameGenerator usingFeatureNamePrefix(String featureNamePrefix) {
|
||||
return new ClassNameGenerator(this.defaultTarget, featureNamePrefix,
|
||||
this.sequenceGenerator);
|
||||
}
|
||||
|
||||
private String clean(String name) {
|
||||
|
||||
@@ -17,12 +17,15 @@
|
||||
package org.springframework.aot.generate;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
import org.springframework.aot.hint.RuntimeHints;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* Default implementation of {@link GenerationContext}.
|
||||
* Default {@link GenerationContext} implementation.
|
||||
*
|
||||
* @author Phillip Webb
|
||||
* @author Stephane Nicoll
|
||||
@@ -30,7 +33,7 @@ import org.springframework.util.Assert;
|
||||
*/
|
||||
public class DefaultGenerationContext implements GenerationContext {
|
||||
|
||||
private final ClassNameGenerator classNameGenerator;
|
||||
private final Map<String, AtomicInteger> sequenceGenerator;
|
||||
|
||||
private final GeneratedClasses generatedClasses;
|
||||
|
||||
@@ -41,39 +44,45 @@ public class DefaultGenerationContext implements GenerationContext {
|
||||
|
||||
/**
|
||||
* Create a new {@link DefaultGenerationContext} instance backed by the
|
||||
* specified {@code generatedFiles}.
|
||||
* specified {@link ClassNameGenerator} and {@link GeneratedFiles}.
|
||||
* @param classNameGenerator the naming convention to use for generated
|
||||
* class names
|
||||
* @param generatedFiles the generated files
|
||||
*/
|
||||
public DefaultGenerationContext(GeneratedFiles generatedFiles) {
|
||||
this(new ClassNameGenerator(), generatedFiles, new RuntimeHints());
|
||||
public DefaultGenerationContext(ClassNameGenerator classNameGenerator, GeneratedFiles generatedFiles) {
|
||||
this(new GeneratedClasses(classNameGenerator), generatedFiles, new RuntimeHints());
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new {@link DefaultGenerationContext} instance backed by the
|
||||
* specified items.
|
||||
* @param classNameGenerator the class name generator
|
||||
* @param generatedClasses the generated classes
|
||||
* @param generatedFiles the generated files
|
||||
* @param runtimeHints the runtime hints
|
||||
*/
|
||||
public DefaultGenerationContext(ClassNameGenerator classNameGenerator,
|
||||
public DefaultGenerationContext(GeneratedClasses generatedClasses,
|
||||
GeneratedFiles generatedFiles, RuntimeHints runtimeHints) {
|
||||
Assert.notNull(classNameGenerator, "'classNameGenerator' must not be null");
|
||||
Assert.notNull(generatedClasses, "'generatedClasses' must not be null");
|
||||
Assert.notNull(generatedFiles, "'generatedFiles' must not be null");
|
||||
Assert.notNull(runtimeHints, "'runtimeHints' must not be null");
|
||||
this.classNameGenerator = classNameGenerator;
|
||||
this.generatedClasses = new GeneratedClasses(classNameGenerator);
|
||||
this.sequenceGenerator = new ConcurrentHashMap<>();
|
||||
this.generatedClasses = generatedClasses;
|
||||
this.generatedFiles = generatedFiles;
|
||||
this.runtimeHints = runtimeHints;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public ClassNameGenerator getClassNameGenerator() {
|
||||
return this.classNameGenerator;
|
||||
private DefaultGenerationContext(DefaultGenerationContext existing, String name) {
|
||||
int sequence = existing.sequenceGenerator
|
||||
.computeIfAbsent(name, key -> new AtomicInteger()).getAndIncrement();
|
||||
String nameToUse = (sequence > 0 ? name + sequence : name);
|
||||
this.sequenceGenerator = existing.sequenceGenerator;
|
||||
this.generatedClasses = existing.generatedClasses.withName(nameToUse);
|
||||
this.generatedFiles = existing.generatedFiles;
|
||||
this.runtimeHints = existing.runtimeHints;
|
||||
}
|
||||
|
||||
@Override
|
||||
public GeneratedClasses getClassGenerator() {
|
||||
public GeneratedClasses getGeneratedClasses() {
|
||||
return this.generatedClasses;
|
||||
}
|
||||
|
||||
@@ -87,6 +96,11 @@ public class DefaultGenerationContext implements GenerationContext {
|
||||
return this.runtimeHints;
|
||||
}
|
||||
|
||||
@Override
|
||||
public GenerationContext withName(String name) {
|
||||
return new DefaultGenerationContext(this, name);
|
||||
}
|
||||
|
||||
/**
|
||||
* Write any generated content out to the generated files.
|
||||
*/
|
||||
|
||||
@@ -16,22 +16,24 @@
|
||||
|
||||
package org.springframework.aot.generate;
|
||||
|
||||
import org.springframework.aot.generate.ClassGenerator.JavaFileGenerator;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
import org.springframework.javapoet.ClassName;
|
||||
import org.springframework.javapoet.JavaFile;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.javapoet.TypeSpec;
|
||||
import org.springframework.javapoet.TypeSpec.Builder;
|
||||
|
||||
/**
|
||||
* A generated class.
|
||||
* A generated class is a container for generated methods.
|
||||
*
|
||||
* @author Phillip Webb
|
||||
* @author Stephane Nicoll
|
||||
* @since 6.0
|
||||
* @see GeneratedClasses
|
||||
* @see ClassGenerator
|
||||
*/
|
||||
public final class GeneratedClass {
|
||||
|
||||
private final JavaFileGenerator JavaFileGenerator;
|
||||
private final Consumer<Builder> typeSpecCustomizer;
|
||||
|
||||
private final ClassName name;
|
||||
|
||||
@@ -44,12 +46,10 @@ public final class GeneratedClass {
|
||||
* {@link GeneratedClasses}.
|
||||
* @param name the generated name
|
||||
*/
|
||||
GeneratedClass(JavaFileGenerator javaFileGenerator, ClassName name) {
|
||||
MethodNameGenerator methodNameGenerator = new MethodNameGenerator(
|
||||
javaFileGenerator.getReservedMethodNames());
|
||||
this.JavaFileGenerator = javaFileGenerator;
|
||||
GeneratedClass(Consumer<Builder> typeSpecCustomizer, ClassName name) {
|
||||
this.typeSpecCustomizer = typeSpecCustomizer;
|
||||
this.name = name;
|
||||
this.methods = new GeneratedMethods(methodNameGenerator);
|
||||
this.methods = new GeneratedMethods(new MethodNameGenerator());
|
||||
}
|
||||
|
||||
|
||||
@@ -70,15 +70,11 @@ public final class GeneratedClass {
|
||||
}
|
||||
|
||||
JavaFile generateJavaFile() {
|
||||
JavaFile javaFile = this.JavaFileGenerator.generateJavaFile(this.name,
|
||||
this.methods);
|
||||
Assert.state(this.name.packageName().equals(javaFile.packageName),
|
||||
() -> "Generated JavaFile should be in package '"
|
||||
+ this.name.packageName() + "'");
|
||||
Assert.state(this.name.simpleName().equals(javaFile.typeSpec.name),
|
||||
() -> "Generated JavaFile should be named '" + this.name.simpleName()
|
||||
+ "'");
|
||||
return javaFile;
|
||||
TypeSpec.Builder typeSpecBuilder = TypeSpec.classBuilder(this.name);
|
||||
this.typeSpecCustomizer.accept(typeSpecBuilder);
|
||||
this.methods.doWithMethodSpecs(typeSpecBuilder::addMethod);
|
||||
return JavaFile.builder(this.name.packageName(), typeSpecBuilder.build())
|
||||
.build();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -22,59 +22,143 @@ import java.util.Comparator;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
import org.springframework.javapoet.ClassName;
|
||||
import org.springframework.javapoet.TypeSpec;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* A managed collection of generated classes.
|
||||
* A managed collection of generated classes. This class is stateful so the
|
||||
* same instance should be used for all class generation.
|
||||
*
|
||||
* @author Phillip Webb
|
||||
* @author Stephane Nicoll
|
||||
* @since 6.0
|
||||
* @see GeneratedClass
|
||||
*/
|
||||
public class GeneratedClasses implements ClassGenerator {
|
||||
public class GeneratedClasses {
|
||||
|
||||
private final ClassNameGenerator classNameGenerator;
|
||||
|
||||
private final Map<Owner, GeneratedClass> classes = new ConcurrentHashMap<>();
|
||||
private final List<GeneratedClass> classes;
|
||||
|
||||
private final Map<Owner, GeneratedClass> classesByOwner;
|
||||
|
||||
/**
|
||||
* Create a new instance using the specified naming conventions.
|
||||
* @param classNameGenerator the class name generator to use
|
||||
*/
|
||||
public GeneratedClasses(ClassNameGenerator classNameGenerator) {
|
||||
Assert.notNull(classNameGenerator, "'classNameGenerator' must not be null");
|
||||
this.classNameGenerator = classNameGenerator;
|
||||
this(classNameGenerator, new ArrayList<>(), new ConcurrentHashMap<>());
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public GeneratedClass getOrGenerateClass(JavaFileGenerator javaFileGenerator,
|
||||
Class<?> target, String featureName) {
|
||||
|
||||
Assert.notNull(javaFileGenerator, "'javaFileGenerator' must not be null");
|
||||
Assert.notNull(target, "'target' must not be null");
|
||||
Assert.hasLength(featureName, "'featureName' must not be empty");
|
||||
Owner owner = new Owner(javaFileGenerator, target.getName(), featureName);
|
||||
return this.classes.computeIfAbsent(owner,
|
||||
key -> new GeneratedClass(javaFileGenerator,
|
||||
this.classNameGenerator.generateClassName(target, featureName)));
|
||||
private GeneratedClasses(ClassNameGenerator classNameGenerator,
|
||||
List<GeneratedClass> classes, Map<Owner, GeneratedClass> classesByOwner) {
|
||||
Assert.notNull(classNameGenerator, "'classNameGenerator' must not be null");
|
||||
this.classNameGenerator = classNameGenerator;
|
||||
this.classes = classes;
|
||||
this.classesByOwner = classesByOwner;
|
||||
}
|
||||
|
||||
/**
|
||||
* Write generated Spring {@code .factories} files to the given
|
||||
* Prepare a {@link GeneratedClass} for the specified {@code featureName}
|
||||
* targeting the specified {@code component}.
|
||||
* @param featureName the name of the feature to associate with the generated class
|
||||
* @param component the target component
|
||||
* @return a {@link Builder} for further configuration
|
||||
*/
|
||||
public Builder forFeatureComponent(String featureName, Class<?> component) {
|
||||
Assert.hasLength(featureName, "'featureName' must not be empty");
|
||||
Assert.notNull(component, "'component' must not be null");
|
||||
return new Builder(featureName, component);
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepare a {@link GeneratedClass} for the specified {@code featureName}
|
||||
* and no particular component. This should be used for high-level code
|
||||
* generation that are widely applicable and for entry points.
|
||||
* @param featureName the name of the feature to associate with the generated class
|
||||
* @return a {@link Builder} for further configuration
|
||||
*/
|
||||
public Builder forFeature(String featureName) {
|
||||
Assert.hasLength(featureName, "'featureName' must not be empty");
|
||||
return new Builder(featureName, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Write the {@link GeneratedClass generated classes} using the given
|
||||
* {@link GeneratedFiles} instance.
|
||||
* @param generatedFiles where to write the generated files
|
||||
* @param generatedFiles where to write the generated classes
|
||||
* @throws IOException on IO error
|
||||
*/
|
||||
public void writeTo(GeneratedFiles generatedFiles) throws IOException {
|
||||
Assert.notNull(generatedFiles, "'generatedFiles' must not be null");
|
||||
List<GeneratedClass> generatedClasses = new ArrayList<>(this.classes.values());
|
||||
List<GeneratedClass> generatedClasses = new ArrayList<>(this.classes);
|
||||
generatedClasses.sort(Comparator.comparing(GeneratedClass::getName));
|
||||
for (GeneratedClass generatedClass : generatedClasses) {
|
||||
generatedFiles.addSourceFile(generatedClass.generateJavaFile());
|
||||
}
|
||||
}
|
||||
|
||||
private record Owner(JavaFileGenerator javaFileGenerator, String target,
|
||||
String featureName) {
|
||||
GeneratedClasses withName(String name) {
|
||||
return new GeneratedClasses(this.classNameGenerator.usingFeatureNamePrefix(name),
|
||||
this.classes, this.classesByOwner);
|
||||
}
|
||||
|
||||
private record Owner(String id, String className) {
|
||||
|
||||
}
|
||||
|
||||
public class Builder {
|
||||
|
||||
private final String featureName;
|
||||
|
||||
@Nullable
|
||||
private final Class<?> target;
|
||||
|
||||
|
||||
Builder(String featureName, @Nullable Class<?> target) {
|
||||
this.target = target;
|
||||
this.featureName = featureName;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate a new {@link GeneratedClass} using the specified type
|
||||
* customizer.
|
||||
* @param typeSpecCustomizer a customizer for the {@link TypeSpec.Builder}
|
||||
* @return a new {@link GeneratedClass}
|
||||
*/
|
||||
public GeneratedClass generate(Consumer<TypeSpec.Builder> typeSpecCustomizer) {
|
||||
Assert.notNull(typeSpecCustomizer, "'typeSpecCustomizer' must not be null");
|
||||
return createGeneratedClass(typeSpecCustomizer);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Get or generate a new {@link GeneratedClass} for the specified {@code id}.
|
||||
* @param id a unique identifier
|
||||
* @param typeSpecCustomizer a customizer for the {@link TypeSpec.Builder}
|
||||
* @return a {@link GeneratedClass} instance
|
||||
*/
|
||||
public GeneratedClass getOrGenerate(String id,
|
||||
Consumer<TypeSpec.Builder> typeSpecCustomizer) {
|
||||
Assert.hasLength(id, "'id' must not be empty");
|
||||
Assert.notNull(typeSpecCustomizer, "'typeSpecCustomizer' must not be null");
|
||||
Owner owner = new Owner(id, GeneratedClasses.this.classNameGenerator
|
||||
.getClassName(this.target, this.featureName));
|
||||
return GeneratedClasses.this.classesByOwner.computeIfAbsent(owner,
|
||||
key -> createGeneratedClass(typeSpecCustomizer));
|
||||
}
|
||||
|
||||
private GeneratedClass createGeneratedClass(Consumer<TypeSpec.Builder> typeSpecCustomizer) {
|
||||
ClassName className = GeneratedClasses.this.classNameGenerator
|
||||
.generateClassName(this.target, this.featureName);
|
||||
GeneratedClass generatedClass = new GeneratedClass(typeSpecCustomizer, className);
|
||||
GeneratedClasses.this.classes.add(generatedClass);
|
||||
return generatedClass;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -24,38 +24,31 @@ import org.springframework.aot.hint.SerializationHints;
|
||||
|
||||
/**
|
||||
* Central interface used for code generation.
|
||||
* <p>
|
||||
* A generation context provides:
|
||||
*
|
||||
* <p>A generation context provides:
|
||||
* <ul>
|
||||
* <li>Support for {@link #getClassNameGenerator() class name generation}.</li>
|
||||
* <li>Central management of all {@link #getGeneratedFiles() generated
|
||||
* files}.</li>
|
||||
* <li>Support for the recording of {@link #getRuntimeHints() runtime
|
||||
* hints}.</li>
|
||||
* <li>Management of all {@link #getGeneratedClasses()} generated classes},
|
||||
* including naming convention support.</li>
|
||||
* <li>Central management of all {@link #getGeneratedFiles() generated files}.</li>
|
||||
* <li>Support for the recording of {@link #getRuntimeHints() runtime hints}.</li>
|
||||
* </ul>
|
||||
*
|
||||
* <p>If a dedicated round of code generation is required while processing, it
|
||||
* is possible to create a specialized context using {@link #withName(String)}.
|
||||
*
|
||||
* @author Phillip Webb
|
||||
* @author Stephane Nicoll
|
||||
* @since 6.0
|
||||
*/
|
||||
public interface GenerationContext {
|
||||
|
||||
/**
|
||||
* Return the {@link ClassNameGenerator} being used by the context. Allows
|
||||
* new class names to be generated before they are added to the
|
||||
* {@link #getGeneratedFiles() generated files}.
|
||||
* @return the class name generator
|
||||
* @see #getGeneratedFiles()
|
||||
*/
|
||||
ClassNameGenerator getClassNameGenerator();
|
||||
|
||||
/**
|
||||
* Return the {@link GeneratedClasses} being used by the context. Allows a
|
||||
* single generated class to be shared across multiple AOT processors. All
|
||||
* generated classes are written at the end of AOT processing.
|
||||
* @return the generated classes
|
||||
*/
|
||||
ClassGenerator getClassGenerator();
|
||||
GeneratedClasses getGeneratedClasses();
|
||||
|
||||
/**
|
||||
* Return the {@link GeneratedFiles} being used by the context. Used to
|
||||
@@ -73,4 +66,14 @@ public interface GenerationContext {
|
||||
*/
|
||||
RuntimeHints getRuntimeHints();
|
||||
|
||||
/**
|
||||
* Return a new {@link GenerationContext} instance using the specified
|
||||
* name to qualify generated assets for a dedicated round of code
|
||||
* generation. If this name is already in use, a unique sequence is added
|
||||
* to ensure the name is unique.
|
||||
* @param name the name to use
|
||||
* @return a specialized {@link GenerationContext} for the specified name
|
||||
*/
|
||||
GenerationContext withName(String name);
|
||||
|
||||
}
|
||||
|
||||
@@ -32,12 +32,26 @@ import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException
|
||||
*/
|
||||
class ClassNameGeneratorTests {
|
||||
|
||||
private final ClassNameGenerator generator = new ClassNameGenerator();
|
||||
private final ClassNameGenerator generator = new ClassNameGenerator(Object.class);
|
||||
|
||||
@Test
|
||||
void generateClassNameWhenTargetClassIsNullUsesAotPackage() {
|
||||
ClassName generated = this.generator.generateClassName((Class<?>) null, "test");
|
||||
assertThat(generated).hasToString("__.Test");
|
||||
void generateClassNameWhenTargetClassIsNullUsesMainTarget() {
|
||||
ClassName generated = this.generator.generateClassName(null, "test");
|
||||
assertThat(generated).hasToString("java.lang.Object__Test");
|
||||
}
|
||||
|
||||
@Test
|
||||
void generateClassNameUseFeatureNamePrefix() {
|
||||
ClassName generated = new ClassNameGenerator(Object.class, "One")
|
||||
.generateClassName(InputStream.class, "test");
|
||||
assertThat(generated).hasToString("java.io.InputStream__OneTest");
|
||||
}
|
||||
|
||||
@Test
|
||||
void generateClassNameWithNoTextFeatureNamePrefix() {
|
||||
ClassName generated = new ClassNameGenerator(Object.class, " ")
|
||||
.generateClassName(InputStream.class, "test");
|
||||
assertThat(generated).hasToString("java.io.InputStream__Test");
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -59,8 +73,7 @@ class ClassNameGeneratorTests {
|
||||
|
||||
@Test
|
||||
void generateClassNameWithClassWhenLowercaseFeatureNameGeneratesName() {
|
||||
ClassName generated = this.generator.generateClassName(InputStream.class,
|
||||
"bytes");
|
||||
ClassName generated = this.generator.generateClassName(InputStream.class, "bytes");
|
||||
assertThat(generated).hasToString("java.io.InputStream__Bytes");
|
||||
}
|
||||
|
||||
@@ -68,7 +81,7 @@ class ClassNameGeneratorTests {
|
||||
void generateClassNameWithClassWhenInnerClassGeneratesName() {
|
||||
ClassName generated = this.generator.generateClassName(TestBean.class, "EventListener");
|
||||
assertThat(generated)
|
||||
.hasToString("org.springframework.aot.generate.ClassNameGeneratorTests_TestBean__EventListener");
|
||||
.hasToString("org.springframework.aot.generate.ClassNameGeneratorTests_TestBean__EventListener");
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -81,6 +94,15 @@ class ClassNameGeneratorTests {
|
||||
assertThat(generated3).hasToString("java.io.InputStream__Bytes2");
|
||||
}
|
||||
|
||||
@Test
|
||||
void getClassNameWhenMultipleCallsReturnsSameName() {
|
||||
String name1 = this.generator.getClassName(InputStream.class, "bytes");
|
||||
String name2 = this.generator.getClassName(InputStream.class, "bytes");
|
||||
String name3 = this.generator.getClassName(InputStream.class, "bytes");
|
||||
assertThat(name1).hasToString("java.io.InputStream__Bytes")
|
||||
.isEqualTo(name2).isEqualTo(name3);
|
||||
}
|
||||
|
||||
static class TestBean {
|
||||
|
||||
}
|
||||
|
||||
@@ -16,9 +16,14 @@
|
||||
|
||||
package org.springframework.aot.generate;
|
||||
|
||||
import java.util.function.Consumer;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import org.springframework.aot.generate.GeneratedFiles.Kind;
|
||||
import org.springframework.aot.hint.RuntimeHints;
|
||||
import org.springframework.core.testfixture.aot.generate.TestTarget;
|
||||
import org.springframework.javapoet.TypeSpec.Builder;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
|
||||
@@ -31,9 +36,12 @@ import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException
|
||||
*/
|
||||
class DefaultGenerationContextTests {
|
||||
|
||||
private final ClassNameGenerator classNameGenerator = new ClassNameGenerator();
|
||||
private static final Consumer<Builder> typeSpecCustomizer = type -> {};
|
||||
|
||||
private final GeneratedFiles generatedFiles = new InMemoryGeneratedFiles();
|
||||
private final GeneratedClasses generatedClasses = new GeneratedClasses(
|
||||
new ClassNameGenerator(TestTarget.class));
|
||||
|
||||
private final InMemoryGeneratedFiles generatedFiles = new InMemoryGeneratedFiles();
|
||||
|
||||
private final RuntimeHints runtimeHints = new RuntimeHints();
|
||||
|
||||
@@ -41,9 +49,7 @@ class DefaultGenerationContextTests {
|
||||
@Test
|
||||
void createWithOnlyGeneratedFilesCreatesContext() {
|
||||
DefaultGenerationContext context = new DefaultGenerationContext(
|
||||
this.generatedFiles);
|
||||
assertThat(context.getClassNameGenerator())
|
||||
.isInstanceOf(ClassNameGenerator.class);
|
||||
new ClassNameGenerator(TestTarget.class), this.generatedFiles);
|
||||
assertThat(context.getGeneratedFiles()).isSameAs(this.generatedFiles);
|
||||
assertThat(context.getRuntimeHints()).isInstanceOf(RuntimeHints.class);
|
||||
}
|
||||
@@ -51,24 +57,23 @@ class DefaultGenerationContextTests {
|
||||
@Test
|
||||
void createCreatesContext() {
|
||||
DefaultGenerationContext context = new DefaultGenerationContext(
|
||||
this.classNameGenerator, this.generatedFiles, this.runtimeHints);
|
||||
assertThat(context.getClassNameGenerator()).isNotNull();
|
||||
this.generatedClasses, this.generatedFiles, this.runtimeHints);
|
||||
assertThat(context.getGeneratedFiles()).isNotNull();
|
||||
assertThat(context.getRuntimeHints()).isNotNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
void createWhenClassNameGeneratorIsNullThrowsException() {
|
||||
void createWhenGeneratedClassesIsNullThrowsException() {
|
||||
assertThatIllegalArgumentException()
|
||||
.isThrownBy(() -> new DefaultGenerationContext(null, this.generatedFiles,
|
||||
this.runtimeHints))
|
||||
.withMessage("'classNameGenerator' must not be null");
|
||||
.withMessage("'generatedClasses' must not be null");
|
||||
}
|
||||
|
||||
@Test
|
||||
void createWhenGeneratedFilesIsNullThrowsException() {
|
||||
assertThatIllegalArgumentException()
|
||||
.isThrownBy(() -> new DefaultGenerationContext(this.classNameGenerator,
|
||||
.isThrownBy(() -> new DefaultGenerationContext(this.generatedClasses,
|
||||
null, this.runtimeHints))
|
||||
.withMessage("'generatedFiles' must not be null");
|
||||
}
|
||||
@@ -76,30 +81,71 @@ class DefaultGenerationContextTests {
|
||||
@Test
|
||||
void createWhenRuntimeHintsIsNullThrowsException() {
|
||||
assertThatIllegalArgumentException()
|
||||
.isThrownBy(() -> new DefaultGenerationContext(this.classNameGenerator,
|
||||
.isThrownBy(() -> new DefaultGenerationContext(this.generatedClasses,
|
||||
this.generatedFiles, null))
|
||||
.withMessage("'runtimeHints' must not be null");
|
||||
}
|
||||
|
||||
@Test
|
||||
void getClassNameGeneratorReturnsClassNameGenerator() {
|
||||
void getGeneratedClassesReturnsClassNameGenerator() {
|
||||
DefaultGenerationContext context = new DefaultGenerationContext(
|
||||
this.classNameGenerator, this.generatedFiles, this.runtimeHints);
|
||||
assertThat(context.getClassNameGenerator()).isSameAs(this.classNameGenerator);
|
||||
this.generatedClasses, this.generatedFiles, this.runtimeHints);
|
||||
assertThat(context.getGeneratedClasses()).isSameAs(this.generatedClasses);
|
||||
}
|
||||
|
||||
@Test
|
||||
void getGeneratedFilesReturnsGeneratedFiles() {
|
||||
DefaultGenerationContext context = new DefaultGenerationContext(
|
||||
this.classNameGenerator, this.generatedFiles, this.runtimeHints);
|
||||
this.generatedClasses, this.generatedFiles, this.runtimeHints);
|
||||
assertThat(context.getGeneratedFiles()).isSameAs(this.generatedFiles);
|
||||
}
|
||||
|
||||
@Test
|
||||
void getRuntimeHintsReturnsRuntimeHints() {
|
||||
DefaultGenerationContext context = new DefaultGenerationContext(
|
||||
this.classNameGenerator, this.generatedFiles, this.runtimeHints);
|
||||
this.generatedClasses, this.generatedFiles, this.runtimeHints);
|
||||
assertThat(context.getRuntimeHints()).isSameAs(this.runtimeHints);
|
||||
}
|
||||
|
||||
@Test
|
||||
void withNameUpdateNamingConvention() {
|
||||
DefaultGenerationContext context = new DefaultGenerationContext(
|
||||
new ClassNameGenerator(TestTarget.class), this.generatedFiles);
|
||||
GenerationContext anotherContext = context.withName("Another");
|
||||
GeneratedClass generatedClass = anotherContext.getGeneratedClasses()
|
||||
.forFeature("Test").generate(typeSpecCustomizer);
|
||||
assertThat(generatedClass.getName().simpleName()).endsWith("__AnotherTest");
|
||||
}
|
||||
|
||||
@Test
|
||||
void withNameKeepTrackOfAllGeneratedFiles() {
|
||||
DefaultGenerationContext context = new DefaultGenerationContext(
|
||||
new ClassNameGenerator(TestTarget.class), this.generatedFiles);
|
||||
context.getGeneratedClasses().forFeature("Test").generate(typeSpecCustomizer);
|
||||
GenerationContext anotherContext = context.withName("Another");
|
||||
assertThat(anotherContext.getGeneratedClasses()).isNotSameAs(context.getGeneratedClasses());
|
||||
assertThat(anotherContext.getGeneratedFiles()).isSameAs(context.getGeneratedFiles());
|
||||
assertThat(anotherContext.getRuntimeHints()).isSameAs(context.getRuntimeHints());
|
||||
anotherContext.getGeneratedClasses().forFeature("Test").generate(typeSpecCustomizer);
|
||||
context.writeGeneratedContent();
|
||||
assertThat(this.generatedFiles.getGeneratedFiles(Kind.SOURCE)).hasSize(2);
|
||||
}
|
||||
|
||||
@Test
|
||||
void withNameGenerateUniqueName() {
|
||||
DefaultGenerationContext context = new DefaultGenerationContext(
|
||||
new ClassNameGenerator(Object.class), this.generatedFiles);
|
||||
context.withName("Test").getGeneratedClasses()
|
||||
.forFeature("Feature").generate(typeSpecCustomizer);
|
||||
context.withName("Test").getGeneratedClasses()
|
||||
.forFeature("Feature").generate(typeSpecCustomizer);
|
||||
context.withName("Test").getGeneratedClasses()
|
||||
.forFeature("Feature").generate(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");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -16,77 +16,43 @@
|
||||
|
||||
package org.springframework.aot.generate;
|
||||
|
||||
import java.util.function.Consumer;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import org.springframework.javapoet.ClassName;
|
||||
import org.springframework.javapoet.JavaFile;
|
||||
import org.springframework.javapoet.TypeSpec;
|
||||
import org.springframework.javapoet.TypeSpec.Builder;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.Assertions.assertThatIllegalStateException;
|
||||
|
||||
/**
|
||||
* Tests for {@link GeneratedClass}.
|
||||
*
|
||||
* @author Phillip Webb
|
||||
* @author Stephane Nicoll
|
||||
*/
|
||||
class GeneratedClassTests {
|
||||
|
||||
@Test
|
||||
void getNameReturnsName() {
|
||||
ClassName name = ClassName.bestGuess("com.example.Test");
|
||||
GeneratedClass generatedClass = new GeneratedClass(this::generateJavaFile, name);
|
||||
GeneratedClass generatedClass = new GeneratedClass(emptyTypeSpec(), name);
|
||||
assertThat(generatedClass.getName()).isSameAs(name);
|
||||
}
|
||||
|
||||
@Test
|
||||
void generateJavaFileSuppliesGeneratedMethods() {
|
||||
void generateJavaFileIncludesGeneratedMethods() {
|
||||
ClassName name = ClassName.bestGuess("com.example.Test");
|
||||
GeneratedClass generatedClass = new GeneratedClass(this::generateJavaFile, name);
|
||||
GeneratedClass generatedClass = new GeneratedClass(emptyTypeSpec(), name);
|
||||
MethodGenerator methodGenerator = generatedClass.getMethodGenerator();
|
||||
methodGenerator.generateMethod("test")
|
||||
.using(builder -> builder.addJavadoc("Test Method"));
|
||||
assertThat(generatedClass.generateJavaFile().toString()).contains("Test Method");
|
||||
}
|
||||
|
||||
@Test
|
||||
void generateJavaFileWhenHasBadPackageThrowsException() {
|
||||
ClassName name = ClassName.bestGuess("com.example.Test");
|
||||
GeneratedClass generatedClass = new GeneratedClass(
|
||||
this::generateBadPackageJavaFile, name);
|
||||
assertThatIllegalStateException()
|
||||
.isThrownBy(
|
||||
() -> assertThat(generatedClass.generateJavaFile().toString()))
|
||||
.withMessageContaining("should be in package");
|
||||
}
|
||||
|
||||
@Test
|
||||
void generateJavaFileWhenHasBadNameThrowsException() {
|
||||
ClassName name = ClassName.bestGuess("com.example.Test");
|
||||
GeneratedClass generatedClass = new GeneratedClass(this::generateBadNameJavaFile,
|
||||
name);
|
||||
assertThatIllegalStateException()
|
||||
.isThrownBy(
|
||||
() -> assertThat(generatedClass.generateJavaFile().toString()))
|
||||
.withMessageContaining("should be named");
|
||||
}
|
||||
|
||||
private JavaFile generateJavaFile(ClassName className, GeneratedMethods methods) {
|
||||
TypeSpec.Builder classBuilder = TypeSpec.classBuilder(className);
|
||||
methods.doWithMethodSpecs(classBuilder::addMethod);
|
||||
return JavaFile.builder(className.packageName(), classBuilder.build()).build();
|
||||
}
|
||||
|
||||
private JavaFile generateBadPackageJavaFile(ClassName className,
|
||||
GeneratedMethods methods) {
|
||||
TypeSpec.Builder classBuilder = TypeSpec.classBuilder(className);
|
||||
return JavaFile.builder("naughty", classBuilder.build()).build();
|
||||
}
|
||||
|
||||
private JavaFile generateBadNameJavaFile(ClassName className,
|
||||
GeneratedMethods methods) {
|
||||
TypeSpec.Builder classBuilder = TypeSpec.classBuilder("Naughty");
|
||||
return JavaFile.builder(className.packageName(), classBuilder.build()).build();
|
||||
private Consumer<Builder> emptyTypeSpec() {
|
||||
return type -> {};
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -16,27 +16,34 @@
|
||||
|
||||
package org.springframework.aot.generate;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import org.springframework.aot.generate.ClassGenerator.JavaFileGenerator;
|
||||
import org.springframework.javapoet.ClassName;
|
||||
import org.springframework.javapoet.JavaFile;
|
||||
import org.springframework.aot.generate.GeneratedFiles.Kind;
|
||||
import org.springframework.javapoet.TypeSpec;
|
||||
import org.springframework.javapoet.TypeSpec.Builder;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.verifyNoInteractions;
|
||||
|
||||
/**
|
||||
* Tests for {@link GeneratedClasses}.
|
||||
*
|
||||
* @author Phillip Webb
|
||||
* @author Stephane Nicoll
|
||||
*/
|
||||
class GeneratedClassesTests {
|
||||
|
||||
private GeneratedClasses generatedClasses = new GeneratedClasses(
|
||||
new ClassNameGenerator());
|
||||
private static final Consumer<TypeSpec.Builder> emptyTypeCustomizer = type -> {};
|
||||
|
||||
private static final JavaFileGenerator JAVA_FILE_GENERATOR = GeneratedClassesTests::generateJavaFile;
|
||||
private final GeneratedClasses generatedClasses = new GeneratedClasses(
|
||||
new ClassNameGenerator(Object.class));
|
||||
|
||||
@Test
|
||||
void createWhenClassNameGeneratorIsNullThrowsException() {
|
||||
@@ -45,61 +52,118 @@ class GeneratedClassesTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
void getOrGenerateWithClassTargetWhenJavaFileGeneratorIsNullThrowsException() {
|
||||
void forFeatureComponentWhenTargetIsNullThrowsException() {
|
||||
assertThatIllegalArgumentException()
|
||||
.isThrownBy(() -> this.generatedClasses.getOrGenerateClass(null,
|
||||
TestTarget.class, "test"))
|
||||
.withMessage("'javaFileGenerator' must not be null");
|
||||
.isThrownBy(() -> this.generatedClasses.forFeatureComponent("test", null))
|
||||
.withMessage("'component' must not be null");
|
||||
}
|
||||
|
||||
@Test
|
||||
void getOrGenerateWithClassTargetWhenTargetIsNullThrowsException() {
|
||||
void forFeatureComponentWhenFeatureNameIsEmptyThrowsException() {
|
||||
assertThatIllegalArgumentException()
|
||||
.isThrownBy(() -> this.generatedClasses
|
||||
.getOrGenerateClass(JAVA_FILE_GENERATOR, (Class<?>) null, "test"))
|
||||
.withMessage("'target' must not be null");
|
||||
}
|
||||
|
||||
@Test
|
||||
void getOrGenerateWithClassTargetWhenFeatureIsNullThrowsException() {
|
||||
assertThatIllegalArgumentException()
|
||||
.isThrownBy(() -> this.generatedClasses
|
||||
.getOrGenerateClass(JAVA_FILE_GENERATOR, TestTarget.class, null))
|
||||
.isThrownBy(() -> this.generatedClasses.forFeatureComponent("", TestComponent.class))
|
||||
.withMessage("'featureName' must not be empty");
|
||||
}
|
||||
|
||||
@Test
|
||||
void getOrGenerateWhenNewReturnsGeneratedMethod() {
|
||||
void forFeatureWhenFeatureNameIsEmptyThrowsException() {
|
||||
assertThatIllegalArgumentException()
|
||||
.isThrownBy(() -> this.generatedClasses.forFeature(""))
|
||||
.withMessage("'featureName' must not be empty");
|
||||
}
|
||||
|
||||
@Test
|
||||
void generateWhenTypeSpecCustomizerIsNullThrowsException() {
|
||||
assertThatIllegalArgumentException()
|
||||
.isThrownBy(() -> this.generatedClasses
|
||||
.forFeatureComponent("test", TestComponent.class).generate(null))
|
||||
.withMessage("'typeSpecCustomizer' must not be null");
|
||||
}
|
||||
|
||||
@Test
|
||||
void forFeatureUsesDefaultTarget() {
|
||||
GeneratedClass generatedClass = this.generatedClasses
|
||||
.forFeature("Test").generate(emptyTypeCustomizer);
|
||||
assertThat(generatedClass.getName()).hasToString("java.lang.Object__Test");
|
||||
}
|
||||
|
||||
@Test
|
||||
void forFeatureComponentUsesComponent() {
|
||||
GeneratedClass generatedClass = this.generatedClasses
|
||||
.forFeatureComponent("Test", TestComponent.class).generate(emptyTypeCustomizer);
|
||||
assertThat(generatedClass.getName().toString()).endsWith("TestComponent__Test");
|
||||
}
|
||||
|
||||
@Test
|
||||
void generateReturnsDifferentInstances() {
|
||||
Consumer<Builder> typeCustomizer = mockTypeCustomizer();
|
||||
GeneratedClass generatedClass1 = this.generatedClasses
|
||||
.getOrGenerateClass(JAVA_FILE_GENERATOR, TestTarget.class, "one");
|
||||
.forFeatureComponent("one", TestComponent.class).generate(typeCustomizer);
|
||||
GeneratedClass generatedClass2 = this.generatedClasses
|
||||
.getOrGenerateClass(JAVA_FILE_GENERATOR, TestTarget.class, "two");
|
||||
.forFeatureComponent("one", TestComponent.class).generate(typeCustomizer);
|
||||
assertThat(generatedClass1).isNotSameAs(generatedClass2);
|
||||
assertThat(generatedClass1.getName().simpleName()).endsWith("__One");
|
||||
assertThat(generatedClass2.getName().simpleName()).endsWith("__One1");
|
||||
}
|
||||
|
||||
@Test
|
||||
void getOrGenerateWhenNewReturnsGeneratedMethod() {
|
||||
Consumer<Builder> typeCustomizer = mockTypeCustomizer();
|
||||
GeneratedClass generatedClass1 = this.generatedClasses
|
||||
.forFeatureComponent("one", TestComponent.class).getOrGenerate("facet", typeCustomizer);
|
||||
GeneratedClass generatedClass2 = this.generatedClasses
|
||||
.forFeatureComponent("two", TestComponent.class).getOrGenerate("facet", typeCustomizer);
|
||||
assertThat(generatedClass1).isNotNull().isNotEqualTo(generatedClass2);
|
||||
assertThat(generatedClass2).isNotNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
void getOrGenerateWhenRepeatReturnsSameGeneratedMethod() {
|
||||
GeneratedClasses generated = this.generatedClasses;
|
||||
GeneratedClass generatedClass1 = generated.getOrGenerateClass(JAVA_FILE_GENERATOR,
|
||||
TestTarget.class, "one");
|
||||
GeneratedClass generatedClass2 = generated.getOrGenerateClass(JAVA_FILE_GENERATOR,
|
||||
TestTarget.class, "one");
|
||||
GeneratedClass generatedClass3 = generated.getOrGenerateClass(JAVA_FILE_GENERATOR,
|
||||
TestTarget.class, "one");
|
||||
GeneratedClass generatedClass4 = generated.getOrGenerateClass(JAVA_FILE_GENERATOR,
|
||||
TestTarget.class, "two");
|
||||
Consumer<Builder> typeCustomizer = mockTypeCustomizer();
|
||||
GeneratedClass generatedClass1 = this.generatedClasses
|
||||
.forFeatureComponent("one", TestComponent.class).getOrGenerate("facet", typeCustomizer);
|
||||
GeneratedClass generatedClass2 = this.generatedClasses
|
||||
.forFeatureComponent("one", TestComponent.class).getOrGenerate("facet", typeCustomizer);
|
||||
GeneratedClass generatedClass3 = this.generatedClasses
|
||||
.forFeatureComponent("one", TestComponent.class).getOrGenerate("facet", typeCustomizer);
|
||||
assertThat(generatedClass1).isNotNull().isSameAs(generatedClass2)
|
||||
.isSameAs(generatedClass3).isNotSameAs(generatedClass4);
|
||||
.isSameAs(generatedClass3);
|
||||
verifyNoInteractions(typeCustomizer);
|
||||
generatedClass1.generateJavaFile();
|
||||
verify(typeCustomizer).accept(any());
|
||||
}
|
||||
|
||||
static JavaFile generateJavaFile(ClassName className,
|
||||
GeneratedMethods generatedMethods) {
|
||||
TypeSpec typeSpec = TypeSpec.classBuilder(className).addJavadoc("Test").build();
|
||||
return JavaFile.builder(className.packageName(), typeSpec).build();
|
||||
@Test
|
||||
@SuppressWarnings("unchecked")
|
||||
void writeToInvokeTypeSpecCustomizer() throws IOException {
|
||||
Consumer<TypeSpec.Builder> typeSpecCustomizer = mock(Consumer.class);
|
||||
this.generatedClasses.forFeatureComponent("one", TestComponent.class)
|
||||
.generate(typeSpecCustomizer);
|
||||
verifyNoInteractions(typeSpecCustomizer);
|
||||
InMemoryGeneratedFiles generatedFiles = new InMemoryGeneratedFiles();
|
||||
this.generatedClasses.writeTo(generatedFiles);
|
||||
verify(typeSpecCustomizer).accept(any());
|
||||
assertThat(generatedFiles.getGeneratedFiles(Kind.SOURCE)).hasSize(1);
|
||||
}
|
||||
|
||||
private static class TestTarget {
|
||||
@Test
|
||||
void withNameUpdatesNamingConventions() {
|
||||
GeneratedClass generatedClass1 = this.generatedClasses
|
||||
.forFeatureComponent("one", TestComponent.class).generate(emptyTypeCustomizer);
|
||||
GeneratedClass generatedClass2 = this.generatedClasses.withName("Another")
|
||||
.forFeatureComponent("one", TestComponent.class).generate(emptyTypeCustomizer);
|
||||
assertThat(generatedClass1.getName().toString()).endsWith("TestComponent__One");
|
||||
assertThat(generatedClass2.getName().toString()).endsWith("TestComponent__AnotherOne");
|
||||
}
|
||||
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private Consumer<TypeSpec.Builder> mockTypeCustomizer() {
|
||||
return mock(Consumer.class);
|
||||
}
|
||||
|
||||
|
||||
private static class TestComponent {
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,40 @@
|
||||
/*
|
||||
* 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.core.testfixture.aot.generate;
|
||||
|
||||
import org.springframework.aot.generate.ClassNameGenerator;
|
||||
import org.springframework.aot.generate.DefaultGenerationContext;
|
||||
import org.springframework.aot.generate.GeneratedFiles;
|
||||
import org.springframework.aot.generate.GenerationContext;
|
||||
import org.springframework.aot.generate.InMemoryGeneratedFiles;
|
||||
|
||||
/**
|
||||
* Test {@link GenerationContext} implementation that uses
|
||||
* {@link TestTarget} as the main target.
|
||||
*
|
||||
* @author Stephane Nicoll
|
||||
*/
|
||||
public class TestGenerationContext extends DefaultGenerationContext {
|
||||
|
||||
public TestGenerationContext(GeneratedFiles generatedFiles) {
|
||||
super(new ClassNameGenerator(TestTarget.class), generatedFiles);
|
||||
}
|
||||
|
||||
public TestGenerationContext() {
|
||||
this(new InMemoryGeneratedFiles());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
/*
|
||||
* 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.core.testfixture.aot.generate;
|
||||
|
||||
/**
|
||||
* A target used by tests of code generation.
|
||||
*
|
||||
* @author Stephane Nicoll
|
||||
*/
|
||||
public class TestTarget {
|
||||
}
|
||||
Reference in New Issue
Block a user