Migrate AOT tests to use GeneratedClasses and refine/polish AOT APIs
Migrate all AOT tests to make use of `GeneratedClasses` rather than directly generating Java files. This commit also refines and polishes AOT APIs to being greater consistency. Specifically: - The `MethodGenerator` interface has been removed in favor of working directly with `GeneratedMethods`. - The visibility of several constructors and methods has been reduced to package-private. - The `using(...)` and `builder` methods have been removed in favor of setting the `Consumer` callbacks directly as constructor arguments. - Variable names for builders are now named `type` or `method` depending on what they're building. Closes gh-28831
This commit is contained in:
@@ -47,6 +47,7 @@ public final class ClassNameGenerator {
|
||||
|
||||
private final Map<String, AtomicInteger> sequenceGenerator;
|
||||
|
||||
|
||||
/**
|
||||
* Create a new instance using the specified {@code defaultTarget} and no
|
||||
* feature name prefix.
|
||||
@@ -68,11 +69,15 @@ public final class ClassNameGenerator {
|
||||
|
||||
private ClassNameGenerator(Class<?> defaultTarget, String featureNamePrefix,
|
||||
Map<String, AtomicInteger> sequenceGenerator) {
|
||||
Assert.notNull(defaultTarget, "'defaultTarget' must not be null");
|
||||
this.defaultTarget = defaultTarget;
|
||||
this.featureNamePrefix = (!StringUtils.hasText(featureNamePrefix) ? "" : featureNamePrefix);
|
||||
this.sequenceGenerator = sequenceGenerator;
|
||||
}
|
||||
|
||||
String getFeatureNamePrefix() {
|
||||
return this.featureNamePrefix;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate a unique {@link ClassName} based on the specified
|
||||
@@ -85,46 +90,22 @@ public final class ClassNameGenerator {
|
||||
* 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} to use the main target
|
||||
* @param featureName the name of the feature that the generated class
|
||||
* supports
|
||||
* @param target the class the newly generated class relates to, or
|
||||
* {@code null} to use the main target
|
||||
* @return a unique generated class name
|
||||
*/
|
||||
public ClassName generateClassName(@Nullable Class<?> target, String featureName) {
|
||||
return generateSequencedClassName(getClassName(target, featureName));
|
||||
public ClassName generateClassName(String featureName, @Nullable Class<?> target) {
|
||||
return generateSequencedClassName(getRootName(featureName, target));
|
||||
}
|
||||
|
||||
/**
|
||||
* 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) {
|
||||
private String getRootName(String featureName, @Nullable Class<?> target) {
|
||||
Assert.hasLength(featureName, "'featureName' must not be empty");
|
||||
featureName = clean(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);
|
||||
return targetToUse.getName().replace("$", "_") + SEPARATOR + StringUtils.capitalize(featureNameToUse);
|
||||
}
|
||||
|
||||
private String clean(String name) {
|
||||
@@ -142,15 +123,26 @@ public final class ClassNameGenerator {
|
||||
}
|
||||
|
||||
private ClassName generateSequencedClassName(String name) {
|
||||
name = addSequence(name);
|
||||
int sequence = this.sequenceGenerator.computeIfAbsent(name, key ->
|
||||
new AtomicInteger()).getAndIncrement();
|
||||
if (sequence > 0) {
|
||||
name = name + sequence;
|
||||
}
|
||||
return ClassName.get(ClassUtils.getPackageName(name),
|
||||
ClassUtils.getShortName(name));
|
||||
}
|
||||
|
||||
private String addSequence(String name) {
|
||||
int sequence = this.sequenceGenerator
|
||||
.computeIfAbsent(name, key -> new AtomicInteger()).getAndIncrement();
|
||||
return (sequence > 0) ? name + sequence : name;
|
||||
|
||||
/**
|
||||
* 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 withFeatureNamePrefix(String featureNamePrefix) {
|
||||
return new ClassNameGenerator(this.defaultTarget, featureNamePrefix,
|
||||
this.sequenceGenerator);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -74,9 +74,9 @@ public class DefaultGenerationContext implements GenerationContext {
|
||||
private DefaultGenerationContext(DefaultGenerationContext existing, String name) {
|
||||
int sequence = existing.sequenceGenerator
|
||||
.computeIfAbsent(name, key -> new AtomicInteger()).getAndIncrement();
|
||||
String nameToUse = (sequence > 0 ? name + sequence : name);
|
||||
String featureName = (sequence > 0 ? name + sequence : name);
|
||||
this.sequenceGenerator = existing.sequenceGenerator;
|
||||
this.generatedClasses = existing.generatedClasses.withName(nameToUse);
|
||||
this.generatedClasses = existing.generatedClasses.withFeatureNamePrefix(featureName);
|
||||
this.generatedFiles = existing.generatedFiles;
|
||||
this.runtimeHints = existing.runtimeHints;
|
||||
}
|
||||
|
||||
@@ -16,15 +16,18 @@
|
||||
|
||||
package org.springframework.aot.generate;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
import org.springframework.javapoet.ClassName;
|
||||
import org.springframework.javapoet.JavaFile;
|
||||
import org.springframework.javapoet.TypeSpec;
|
||||
import org.springframework.javapoet.TypeSpec.Builder;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* A generated class is a container for generated methods.
|
||||
* A single generated class.
|
||||
*
|
||||
* @author Phillip Webb
|
||||
* @author Stephane Nicoll
|
||||
@@ -33,26 +36,49 @@ import org.springframework.javapoet.TypeSpec.Builder;
|
||||
*/
|
||||
public final class GeneratedClass {
|
||||
|
||||
private final Consumer<Builder> typeSpecCustomizer;
|
||||
|
||||
private final ClassName name;
|
||||
|
||||
private final GeneratedMethods methods;
|
||||
|
||||
private final Consumer<TypeSpec.Builder> type;
|
||||
|
||||
private final Map<MethodName, AtomicInteger> methodNameSequenceGenerator = new ConcurrentHashMap<>();
|
||||
|
||||
|
||||
/**
|
||||
* Create a new {@link GeneratedClass} instance with the given name. This
|
||||
* constructor is package-private since names should only be generated via a
|
||||
* {@link GeneratedClasses}.
|
||||
* @param name the generated name
|
||||
* @param type a {@link Consumer} used to build the type
|
||||
*/
|
||||
GeneratedClass(Consumer<Builder> typeSpecCustomizer, ClassName name) {
|
||||
this.typeSpecCustomizer = typeSpecCustomizer;
|
||||
GeneratedClass(ClassName name, Consumer<TypeSpec.Builder> type) {
|
||||
this.name = name;
|
||||
this.methods = new GeneratedMethods(new MethodNameGenerator());
|
||||
this.type = type;
|
||||
this.methods = new GeneratedMethods(this::generateSequencedMethodName);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Update this instance with a set of reserved method names that should not
|
||||
* be used for generated methods. Reserved names are often needed when a
|
||||
* generated class implements a specific interface.
|
||||
* @param reservedMethodNames the reserved method names
|
||||
*/
|
||||
public void reserveMethodNames(String... reservedMethodNames) {
|
||||
for (String reservedMethodName : reservedMethodNames) {
|
||||
String generatedName = generateSequencedMethodName(MethodName.of(reservedMethodNames));
|
||||
Assert.state(generatedName.equals(reservedMethodName),
|
||||
() -> String.format("Unable to reserve method name '%s'", reservedMethodName));
|
||||
}
|
||||
}
|
||||
|
||||
private String generateSequencedMethodName(MethodName name) {
|
||||
int sequence = this.methodNameSequenceGenerator
|
||||
.computeIfAbsent(name, key -> new AtomicInteger()).getAndIncrement();
|
||||
return (sequence > 0) ? name.toString() + sequence : name.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the name of the generated class.
|
||||
* @return the name of the generated class
|
||||
@@ -62,18 +88,28 @@ public final class GeneratedClass {
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the method generator that can be used for this generated class.
|
||||
* @return the method generator
|
||||
* Return generated methods for this instance.
|
||||
* @return the generated methods
|
||||
*/
|
||||
public MethodGenerator getMethodGenerator() {
|
||||
public GeneratedMethods getMethods() {
|
||||
return this.methods;
|
||||
}
|
||||
|
||||
JavaFile generateJavaFile() {
|
||||
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();
|
||||
TypeSpec.Builder type = getBuilder(this.type);
|
||||
this.methods.doWithMethodSpecs(type::addMethod);
|
||||
return JavaFile.builder(this.name.packageName(), type.build()).build();
|
||||
}
|
||||
|
||||
private TypeSpec.Builder getBuilder(Consumer<TypeSpec.Builder> type) {
|
||||
TypeSpec.Builder builder = TypeSpec.classBuilder(this.name);
|
||||
type.accept(builder);
|
||||
return builder;
|
||||
}
|
||||
|
||||
void assertSameType(Consumer<TypeSpec.Builder> type) {
|
||||
Assert.state(type == this.type || getBuilder(this.type).build().equals(getBuilder(type).build()),
|
||||
"'type' consumer generated different result");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -46,11 +46,12 @@ public class GeneratedClasses {
|
||||
|
||||
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) {
|
||||
GeneratedClasses(ClassNameGenerator classNameGenerator) {
|
||||
this(classNameGenerator, new ArrayList<>(), new ConcurrentHashMap<>());
|
||||
}
|
||||
|
||||
@@ -62,29 +63,92 @@ public class GeneratedClasses {
|
||||
this.classesByOwner = classesByOwner;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 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
|
||||
* Get or add a generated class for the specified {@code featureName} and no
|
||||
* particular component. If this method has previously been called with the
|
||||
* given {@code featureName} the existing class will be returned, otherwise
|
||||
* a new class will be generated.
|
||||
* @param featureName the name of the feature to associate with the
|
||||
* generated class
|
||||
* @param type a {@link Consumer} used to build the type
|
||||
* @return an existing or newly generated class
|
||||
*/
|
||||
public Builder forFeatureComponent(String featureName, Class<?> component) {
|
||||
public GeneratedClass getOrAddForFeature(String featureName,
|
||||
Consumer<TypeSpec.Builder> type) {
|
||||
|
||||
Assert.hasLength(featureName, "'featureName' must not be empty");
|
||||
Assert.notNull(component, "'component' must not be null");
|
||||
return new Builder(featureName, component);
|
||||
Assert.notNull(type, "'type' must not be null");
|
||||
Owner owner = new Owner(this.classNameGenerator.getFeatureNamePrefix(), featureName, null);
|
||||
GeneratedClass generatedClass = this.classesByOwner.computeIfAbsent(owner, key -> createAndAddGeneratedClass(featureName, null, type));
|
||||
generatedClass.assertSameType(type);
|
||||
return generatedClass;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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
|
||||
* 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 Builder forFeature(String featureName) {
|
||||
public GeneratedClass getOrAddForFeatureComponent(String featureName,
|
||||
Class<?> targetComponent, Consumer<TypeSpec.Builder> type) {
|
||||
|
||||
Assert.hasLength(featureName, "'featureName' must not be empty");
|
||||
return new Builder(featureName, null);
|
||||
Assert.notNull(targetComponent, "'targetComponent' must not be null");
|
||||
Assert.notNull(type, "'type' must not be null");
|
||||
Owner owner = new Owner(this.classNameGenerator.getFeatureNamePrefix(), featureName, targetComponent);
|
||||
GeneratedClass generatedClass = this.classesByOwner.computeIfAbsent(owner, key ->
|
||||
createAndAddGeneratedClass(featureName, targetComponent, type));
|
||||
generatedClass.assertSameType(type);
|
||||
return generatedClass;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a new generated class for the specified {@code featureName} and no
|
||||
* particular component.
|
||||
* @param featureName the name of the feature to associate with the
|
||||
* generated class
|
||||
* @param type a {@link Consumer} used to build the type
|
||||
* @return the newly generated class
|
||||
*/
|
||||
public GeneratedClass addForFeature(String featureName, Consumer<TypeSpec.Builder> type) {
|
||||
Assert.hasLength(featureName, "'featureName' must not be empty");
|
||||
Assert.notNull(type, "'type' must not be null");
|
||||
return createAndAddGeneratedClass(featureName, null, 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) {
|
||||
|
||||
Assert.hasLength(featureName, "'featureName' must not be empty");
|
||||
Assert.notNull(targetComponent, "'targetComponent' must not be null");
|
||||
Assert.notNull(type, "'type' must not be null");
|
||||
return createAndAddGeneratedClass(featureName, targetComponent, type);
|
||||
}
|
||||
|
||||
private GeneratedClass createAndAddGeneratedClass(String featureName,
|
||||
@Nullable Class<?> targetComponent, Consumer<TypeSpec.Builder> type) {
|
||||
|
||||
ClassName className = this.classNameGenerator.generateClassName(featureName, targetComponent);
|
||||
GeneratedClass generatedClass = new GeneratedClass(className, type);
|
||||
this.classes.add(generatedClass);
|
||||
return generatedClass;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -93,7 +157,7 @@ public class GeneratedClasses {
|
||||
* @param generatedFiles where to write the generated classes
|
||||
* @throws IOException on IO error
|
||||
*/
|
||||
public void writeTo(GeneratedFiles generatedFiles) throws IOException {
|
||||
void writeTo(GeneratedFiles generatedFiles) throws IOException {
|
||||
Assert.notNull(generatedFiles, "'generatedFiles' must not be null");
|
||||
List<GeneratedClass> generatedClasses = new ArrayList<>(this.classes);
|
||||
generatedClasses.sort(Comparator.comparing(GeneratedClass::getName));
|
||||
@@ -102,62 +166,12 @@ public class GeneratedClasses {
|
||||
}
|
||||
}
|
||||
|
||||
GeneratedClasses withName(String name) {
|
||||
return new GeneratedClasses(this.classNameGenerator.usingFeatureNamePrefix(name),
|
||||
GeneratedClasses withFeatureNamePrefix(String name) {
|
||||
return new GeneratedClasses(this.classNameGenerator.withFeatureNamePrefix(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;
|
||||
}
|
||||
private record Owner(String featureNamePrefix, String featureName, @Nullable Class<?> target) {
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -19,8 +19,6 @@ package org.springframework.aot.generate;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
import org.springframework.javapoet.MethodSpec;
|
||||
import org.springframework.javapoet.MethodSpec.Builder;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
@@ -29,24 +27,28 @@ import org.springframework.util.Assert;
|
||||
* @author Phillip Webb
|
||||
* @since 6.0
|
||||
* @see GeneratedMethods
|
||||
* @see MethodGenerator
|
||||
*/
|
||||
public final class GeneratedMethod {
|
||||
|
||||
private final String name;
|
||||
|
||||
@Nullable
|
||||
private MethodSpec spec;
|
||||
private final MethodSpec methodSpec;
|
||||
|
||||
|
||||
/**
|
||||
* 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 name the generated name
|
||||
* @param name the generated method name
|
||||
* @param method consumer to generate the method
|
||||
*/
|
||||
GeneratedMethod(String name) {
|
||||
GeneratedMethod(String name, Consumer<MethodSpec.Builder> method) {
|
||||
this.name = name;
|
||||
MethodSpec.Builder builder = MethodSpec.methodBuilder(getName());
|
||||
method.accept(builder);
|
||||
this.methodSpec = builder.build();
|
||||
Assert.state(this.name.equals(this.methodSpec.name),
|
||||
"'method' consumer must not change the generated method name");
|
||||
}
|
||||
|
||||
|
||||
@@ -64,35 +66,13 @@ public final class GeneratedMethod {
|
||||
* @throws IllegalStateException if one of the {@code generateBy(...)}
|
||||
* methods has not been called
|
||||
*/
|
||||
public MethodSpec getSpec() {
|
||||
Assert.state(this.spec != null,
|
||||
() -> "Method '%s' has no method spec defined".formatted(this.name));
|
||||
return this.spec;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate the method using the given consumer.
|
||||
* @param builder a consumer that will accept a method spec builder and
|
||||
* configure it as necessary
|
||||
* @return this instance
|
||||
*/
|
||||
public GeneratedMethod using(Consumer<MethodSpec.Builder> builder) {
|
||||
Builder builderToUse = MethodSpec.methodBuilder(this.name);
|
||||
builder.accept(builderToUse);
|
||||
MethodSpec spec = builderToUse.build();
|
||||
assertNameHasNotBeenChanged(spec);
|
||||
this.spec = spec;
|
||||
return this;
|
||||
}
|
||||
|
||||
private void assertNameHasNotBeenChanged(MethodSpec spec) {
|
||||
Assert.isTrue(this.name.equals(spec.name),
|
||||
() -> "'spec' must use the generated name '%s'".formatted(this.name));
|
||||
MethodSpec getMethodSpec() {
|
||||
return this.methodSpec;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return (this.spec != null) ? this.spec.toString() : this.name.toString();
|
||||
return this.name.toString();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -17,12 +17,13 @@
|
||||
package org.springframework.aot.generate;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.function.Function;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import org.springframework.javapoet.MethodSpec;
|
||||
import org.springframework.javapoet.MethodSpec.Builder;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
@@ -32,49 +33,63 @@ import org.springframework.util.Assert;
|
||||
* @since 6.0
|
||||
* @see GeneratedMethod
|
||||
*/
|
||||
public class GeneratedMethods implements Iterable<GeneratedMethod>, MethodGenerator {
|
||||
public class GeneratedMethods {
|
||||
|
||||
private final MethodNameGenerator methodNameGenerator;
|
||||
private final Function<MethodName, String> methodNameGenerator;
|
||||
|
||||
private final List<GeneratedMethod> generatedMethods = new ArrayList<>();
|
||||
private final MethodName prefix;
|
||||
|
||||
|
||||
/**
|
||||
* Create a new {@link GeneratedMethods} instance backed by a new
|
||||
* {@link MethodNameGenerator}.
|
||||
*/
|
||||
public GeneratedMethods() {
|
||||
this(new MethodNameGenerator());
|
||||
}
|
||||
private final List<GeneratedMethod> generatedMethods;
|
||||
|
||||
/**
|
||||
* Create a new {@link GeneratedMethods} instance backed by the given
|
||||
* {@link MethodNameGenerator}.
|
||||
* @param methodNameGenerator the method name generator
|
||||
*/
|
||||
public GeneratedMethods(MethodNameGenerator methodNameGenerator) {
|
||||
GeneratedMethods(Function<MethodName, String> methodNameGenerator) {
|
||||
Assert.notNull(methodNameGenerator, "'methodNameGenerator' must not be null");
|
||||
this.methodNameGenerator = methodNameGenerator;
|
||||
this.prefix = MethodName.NONE;
|
||||
this.generatedMethods = new ArrayList<>();
|
||||
}
|
||||
|
||||
private GeneratedMethods(Function<MethodName, String> methodNameGenerator,
|
||||
MethodName prefix, List<GeneratedMethod> generatedMethods) {
|
||||
|
||||
@Override
|
||||
public GeneratedMethod generateMethod(Object... methodNameParts) {
|
||||
return add(methodNameParts);
|
||||
this.methodNameGenerator = methodNameGenerator;
|
||||
this.prefix = prefix;
|
||||
this.generatedMethods = generatedMethods;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a new {@link GeneratedMethod}. The returned instance must define the
|
||||
* method spec by calling {@code using(builder -> ...)}.
|
||||
* @param methodNameParts the method name parts that should be used to
|
||||
* generate a unique method name
|
||||
* Add a new {@link GeneratedMethod}.
|
||||
* @param suggestedName the suggested name for the method
|
||||
* @param method a {@link Consumer} used to build method
|
||||
* @return the newly added {@link GeneratedMethod}
|
||||
*/
|
||||
public GeneratedMethod add(Object... methodNameParts) {
|
||||
GeneratedMethod method = new GeneratedMethod(
|
||||
this.methodNameGenerator.generateMethodName(methodNameParts));
|
||||
this.generatedMethods.add(method);
|
||||
return method;
|
||||
public GeneratedMethod add(String suggestedName, Consumer<Builder> method) {
|
||||
Assert.notNull(suggestedName, "'suggestedName' must not be null");
|
||||
return add(MethodName.of(suggestedName), method);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a new {@link GeneratedMethod}.
|
||||
* @param suggestedName the suggested name for the method
|
||||
* @param method a {@link Consumer} used to build the method
|
||||
* @return the newly added {@link GeneratedMethod}
|
||||
*/
|
||||
public GeneratedMethod add(MethodName suggestedName, Consumer<Builder> method) {
|
||||
Assert.notNull(suggestedName, "'suggestedName' must not be null");
|
||||
Assert.notNull(method, "'method' must not be null");
|
||||
String generatedName = this.methodNameGenerator.apply(this.prefix.and(suggestedName));
|
||||
GeneratedMethod generatedMethod = new GeneratedMethod(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);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -82,20 +97,11 @@ public class GeneratedMethods implements Iterable<GeneratedMethod>, MethodGenera
|
||||
* that have been added to this collection.
|
||||
* @param action the action to perform
|
||||
*/
|
||||
public void doWithMethodSpecs(Consumer<MethodSpec> action) {
|
||||
stream().map(GeneratedMethod::getSpec).forEach(action);
|
||||
void doWithMethodSpecs(Consumer<MethodSpec> action) {
|
||||
stream().map(GeneratedMethod::getMethodSpec).forEach(action);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Iterator<GeneratedMethod> iterator() {
|
||||
return this.generatedMethods.iterator();
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a {@link Stream} of all the methods in this collection.
|
||||
* @return a stream of {@link GeneratedMethod} instances
|
||||
*/
|
||||
public Stream<GeneratedMethod> stream() {
|
||||
Stream<GeneratedMethod> stream() {
|
||||
return this.generatedMethods.stream();
|
||||
}
|
||||
|
||||
|
||||
@@ -43,8 +43,7 @@ import org.springframework.aot.hint.SerializationHints;
|
||||
public interface GenerationContext {
|
||||
|
||||
/**
|
||||
* Return the {@link GeneratedClasses} being used by the context. Allows a
|
||||
* single generated class to be shared across multiple AOT processors. All
|
||||
* Return the {@link GeneratedClasses} being used by the context. All
|
||||
* generated classes are written at the end of AOT processing.
|
||||
* @return the generated classes
|
||||
*/
|
||||
|
||||
@@ -1,72 +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;
|
||||
|
||||
/**
|
||||
* Generates new {@link GeneratedMethod} instances.
|
||||
*
|
||||
* @author Phillip Webb
|
||||
* @since 6.0
|
||||
* @see GeneratedMethods
|
||||
*/
|
||||
@FunctionalInterface
|
||||
public interface MethodGenerator {
|
||||
|
||||
/**
|
||||
* Generate a new {@link GeneratedMethod}. The returned instance must define
|
||||
* the method spec by calling {@code using(builder -> ...)}.
|
||||
* @param methodNameParts the method name parts that should be used to
|
||||
* generate a unique method name
|
||||
* @return the newly added {@link GeneratedMethod}
|
||||
*/
|
||||
GeneratedMethod generateMethod(Object... methodNameParts);
|
||||
|
||||
/**
|
||||
* Return a new {@link MethodGenerator} instance that generates method with
|
||||
* additional implicit method name parts. The final generated name will be
|
||||
* of the following form:
|
||||
* <p>
|
||||
* <table border="1">
|
||||
* <tr>
|
||||
* <th>Original</th>
|
||||
* <th>Updated</th>
|
||||
* </tr>
|
||||
* <tr>
|
||||
* <td>run</td>
|
||||
* <td><name>Run</td>
|
||||
* </tr>
|
||||
* <tr>
|
||||
* <td>getValue</td>
|
||||
* <td>get<Name>Value</td>
|
||||
* </tr>
|
||||
* <tr>
|
||||
* <td>setValue</td>
|
||||
* <td>set<Name>Value</td>
|
||||
* </tr>
|
||||
* <tr>
|
||||
* <td>isEnabled</td>
|
||||
* <td>is<Name>Enabled</td>
|
||||
* </tr>
|
||||
* </table>
|
||||
* @param nameParts the implicit name parts
|
||||
* @return a new {@link MethodGenerator} instance
|
||||
*/
|
||||
default MethodGenerator withName(Object... nameParts) {
|
||||
return new MethodGeneratorWithName(this, nameParts);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,71 +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 org.springframework.util.ObjectUtils;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
/**
|
||||
* Internal class used to support {@link MethodGenerator#withName(Object...)}.
|
||||
*
|
||||
* @author Phillip Webb
|
||||
* @since 6.0
|
||||
*/
|
||||
class MethodGeneratorWithName implements MethodGenerator {
|
||||
|
||||
private static final String[] PREFIXES = { "get", "set", "is" };
|
||||
|
||||
private final MethodGenerator methodGenerator;
|
||||
|
||||
private final Object[] nameParts;
|
||||
|
||||
|
||||
MethodGeneratorWithName(MethodGenerator methodGenerator, Object[] nameParts) {
|
||||
this.methodGenerator = methodGenerator;
|
||||
this.nameParts = nameParts;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public GeneratedMethod generateMethod(Object... methodNameParts) {
|
||||
return this.methodGenerator.generateMethod(generateName(methodNameParts));
|
||||
}
|
||||
|
||||
private Object[] generateName(Object... methodNameParts) {
|
||||
String joined = MethodNameGenerator.join(methodNameParts);
|
||||
String prefix = getPrefix(joined);
|
||||
String suffix = joined.substring(prefix.length());
|
||||
Object[] result = this.nameParts;
|
||||
if (StringUtils.hasLength(prefix)) {
|
||||
result = ObjectUtils.addObjectToArray(result, prefix, 0);
|
||||
}
|
||||
if (StringUtils.hasLength(suffix)) {
|
||||
result = ObjectUtils.addObjectToArray(result, suffix);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private String getPrefix(String name) {
|
||||
for (String candidate : PREFIXES) {
|
||||
if (name.startsWith(candidate)) {
|
||||
return candidate;
|
||||
}
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,130 @@
|
||||
/*
|
||||
* 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.Arrays;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
/**
|
||||
* A camel-case method name that can be built from distinct parts.
|
||||
*
|
||||
* @author Phillip Webb
|
||||
* @since 6.0
|
||||
*/
|
||||
public final class MethodName {
|
||||
|
||||
private static final String[] PREFIXES = { "get", "set", "is" };
|
||||
|
||||
/**
|
||||
* An empty method name.
|
||||
*/
|
||||
public static final MethodName NONE = of();
|
||||
|
||||
private final String value;
|
||||
|
||||
|
||||
private MethodName(String value) {
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Create a new method name from the specific parts. The returned name will
|
||||
* be in camel-case and will only contain valid characters from the parts.
|
||||
* @param parts the parts the form the name
|
||||
* @return a method name instance
|
||||
*/
|
||||
public static MethodName of(String... parts) {
|
||||
Assert.notNull(parts, "'parts' must not be null");
|
||||
return new MethodName(join(parts));
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new method name by concatenating the specified name to this name.
|
||||
* @param name the name to concatenate
|
||||
* @return a new method name instance
|
||||
*/
|
||||
public MethodName and(MethodName name) {
|
||||
Assert.notNull(name, "'name' must not be null");
|
||||
return and(name.value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new method name by concatenating the specified parts to this name.
|
||||
* @param parts the parts to concatenate
|
||||
* @return a new method name instance
|
||||
*/
|
||||
public MethodName and(String... parts) {
|
||||
Assert.notNull(parts, "'parts' must not be null");
|
||||
String joined = join(parts);
|
||||
String prefix = getPrefix(joined);
|
||||
String suffix = joined.substring(prefix.length());
|
||||
return of(prefix, this.value, suffix);
|
||||
}
|
||||
|
||||
private String getPrefix(String name) {
|
||||
for (String candidate : PREFIXES) {
|
||||
if (name.startsWith(candidate)) {
|
||||
return candidate;
|
||||
}
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return this.value.hashCode();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (this == obj) {
|
||||
return true;
|
||||
}
|
||||
if ((obj == null) || (getClass() != obj.getClass())) {
|
||||
return false;
|
||||
}
|
||||
return this.value.equals(((MethodName) obj).value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return (!StringUtils.hasLength(this.value)) ? "$$aot" : this.value ;
|
||||
}
|
||||
|
||||
private static String join(String[] parts) {
|
||||
return StringUtils.uncapitalize(Arrays.stream(parts).map(MethodName::clean)
|
||||
.map(StringUtils::capitalize).collect(Collectors.joining()));
|
||||
}
|
||||
|
||||
private static String clean(String part) {
|
||||
char[] chars = (part != null) ? part.toCharArray() : new char[0];
|
||||
StringBuilder name = new StringBuilder(chars.length);
|
||||
boolean uppercase = false;
|
||||
for (char ch : chars) {
|
||||
char outputChar = (!uppercase) ? ch : Character.toUpperCase(ch);
|
||||
name.append((!Character.isLetter(ch)) ? "" : outputChar);
|
||||
uppercase = (ch == '.');
|
||||
}
|
||||
return name.toString();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,123 +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.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.ClassUtils;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
/**
|
||||
* Generates unique method names that can be used in ahead-of-time generated
|
||||
* source code. This class is stateful so one instance should be used per
|
||||
* generated type.
|
||||
*
|
||||
* @author Phillip Webb
|
||||
* @since 6.0
|
||||
*/
|
||||
public class MethodNameGenerator {
|
||||
|
||||
private final Map<String, AtomicInteger> sequenceGenerator = new ConcurrentHashMap<>();
|
||||
|
||||
|
||||
/**
|
||||
* Create a new {@link MethodNameGenerator} instance without any reserved
|
||||
* names.
|
||||
*/
|
||||
public MethodNameGenerator() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new {@link MethodNameGenerator} instance with the specified
|
||||
* reserved names.
|
||||
* @param reservedNames the method names to reserve
|
||||
*/
|
||||
public MethodNameGenerator(String... reservedNames) {
|
||||
this(List.of(reservedNames));
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new {@link MethodNameGenerator} instance with the specified
|
||||
* reserved names.
|
||||
* @param reservedNames the method names to reserve
|
||||
*/
|
||||
public MethodNameGenerator(Iterable<String> reservedNames) {
|
||||
Assert.notNull(reservedNames, "'reservedNames' must not be null");
|
||||
for (String reservedName : reservedNames) {
|
||||
addSequence(StringUtils.uncapitalize(reservedName));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Generate a new method name from the given parts.
|
||||
* @param parts the parts used to build the name.
|
||||
* @return the generated method name
|
||||
*/
|
||||
public String generateMethodName(Object... parts) {
|
||||
String generatedName = join(parts);
|
||||
return addSequence(generatedName.isEmpty() ? "$$aot" : generatedName);
|
||||
}
|
||||
|
||||
private String addSequence(String name) {
|
||||
int sequence = this.sequenceGenerator
|
||||
.computeIfAbsent(name, key -> new AtomicInteger()).getAndIncrement();
|
||||
return (sequence > 0) ? name + sequence : name;
|
||||
}
|
||||
|
||||
/**
|
||||
* Join the specified parts to create a valid camel case method name.
|
||||
* @param parts the parts to join
|
||||
* @return a method name from the joined parts.
|
||||
*/
|
||||
public static String join(Object... parts) {
|
||||
Stream<String> capitalizedPartNames = Arrays.stream(parts)
|
||||
.map(MethodNameGenerator::getPartName).map(StringUtils::capitalize);
|
||||
return StringUtils.uncapitalize(capitalizedPartNames.collect(Collectors.joining()));
|
||||
}
|
||||
|
||||
private static String getPartName(@Nullable Object part) {
|
||||
if (part == null) {
|
||||
return "";
|
||||
}
|
||||
if (part instanceof Class<?> clazz) {
|
||||
return clean(ClassUtils.getShortName(clazz));
|
||||
}
|
||||
return clean(part.toString());
|
||||
}
|
||||
|
||||
private static String clean(String string) {
|
||||
char[] chars = string.toCharArray();
|
||||
StringBuilder name = new StringBuilder(chars.length);
|
||||
boolean uppercase = false;
|
||||
for (char ch : chars) {
|
||||
char outputChar = (!uppercase) ? ch : Character.toUpperCase(ch);
|
||||
name.append((!Character.isLetter(ch)) ? "" : outputChar);
|
||||
uppercase = ch == '.';
|
||||
}
|
||||
return name.toString();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -36,72 +36,64 @@ class ClassNameGeneratorTests {
|
||||
|
||||
@Test
|
||||
void generateClassNameWhenTargetClassIsNullUsesMainTarget() {
|
||||
ClassName generated = this.generator.generateClassName(null, "test");
|
||||
ClassName generated = this.generator.generateClassName("test", null);
|
||||
assertThat(generated).hasToString("java.lang.Object__Test");
|
||||
}
|
||||
|
||||
@Test
|
||||
void generateClassNameUseFeatureNamePrefix() {
|
||||
ClassName generated = new ClassNameGenerator(Object.class, "One")
|
||||
.generateClassName(InputStream.class, "test");
|
||||
.generateClassName("test", InputStream.class);
|
||||
assertThat(generated).hasToString("java.io.InputStream__OneTest");
|
||||
}
|
||||
|
||||
@Test
|
||||
void generateClassNameWithNoTextFeatureNamePrefix() {
|
||||
ClassName generated = new ClassNameGenerator(Object.class, " ")
|
||||
.generateClassName(InputStream.class, "test");
|
||||
.generateClassName("test", InputStream.class);
|
||||
assertThat(generated).hasToString("java.io.InputStream__Test");
|
||||
}
|
||||
|
||||
@Test
|
||||
void generatedClassNameWhenFeatureIsEmptyThrowsException() {
|
||||
assertThatIllegalArgumentException()
|
||||
.isThrownBy(() -> this.generator.generateClassName(InputStream.class, ""))
|
||||
.isThrownBy(() -> this.generator.generateClassName("", InputStream.class))
|
||||
.withMessage("'featureName' must not be empty");
|
||||
}
|
||||
|
||||
@Test
|
||||
void generatedClassNameWhenFeatureIsNotAllLettersThrowsException() {
|
||||
assertThat(this.generator.generateClassName(InputStream.class, "name!"))
|
||||
assertThat(this.generator.generateClassName("name!", InputStream.class))
|
||||
.hasToString("java.io.InputStream__Name");
|
||||
assertThat(this.generator.generateClassName(InputStream.class, "1NameHere"))
|
||||
assertThat(this.generator.generateClassName("1NameHere", InputStream.class))
|
||||
.hasToString("java.io.InputStream__NameHere");
|
||||
assertThat(this.generator.generateClassName(InputStream.class, "Y0pe"))
|
||||
assertThat(this.generator.generateClassName("Y0pe", InputStream.class))
|
||||
.hasToString("java.io.InputStream__YPe");
|
||||
}
|
||||
|
||||
@Test
|
||||
void generateClassNameWithClassWhenLowercaseFeatureNameGeneratesName() {
|
||||
ClassName generated = this.generator.generateClassName(InputStream.class, "bytes");
|
||||
ClassName generated = this.generator.generateClassName("bytes", InputStream.class);
|
||||
assertThat(generated).hasToString("java.io.InputStream__Bytes");
|
||||
}
|
||||
|
||||
@Test
|
||||
void generateClassNameWithClassWhenInnerClassGeneratesName() {
|
||||
ClassName generated = this.generator.generateClassName(TestBean.class, "EventListener");
|
||||
ClassName generated = this.generator.generateClassName("EventListener", TestBean.class);
|
||||
assertThat(generated)
|
||||
.hasToString("org.springframework.aot.generate.ClassNameGeneratorTests_TestBean__EventListener");
|
||||
}
|
||||
|
||||
@Test
|
||||
void generateClassWithClassWhenMultipleCallsGeneratesSequencedName() {
|
||||
ClassName generated1 = this.generator.generateClassName(InputStream.class, "bytes");
|
||||
ClassName generated2 = this.generator.generateClassName(InputStream.class, "bytes");
|
||||
ClassName generated3 = this.generator.generateClassName(InputStream.class, "bytes");
|
||||
ClassName generated1 = this.generator.generateClassName("bytes", InputStream.class);
|
||||
ClassName generated2 = this.generator.generateClassName("bytes", InputStream.class);
|
||||
ClassName generated3 = this.generator.generateClassName("bytes", InputStream.class);
|
||||
assertThat(generated1).hasToString("java.io.InputStream__Bytes");
|
||||
assertThat(generated2).hasToString("java.io.InputStream__Bytes1");
|
||||
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 {
|
||||
|
||||
|
||||
@@ -23,7 +23,7 @@ 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 org.springframework.javapoet.TypeSpec;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
|
||||
@@ -36,7 +36,7 @@ import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException
|
||||
*/
|
||||
class DefaultGenerationContextTests {
|
||||
|
||||
private static final Consumer<Builder> typeSpecCustomizer = type -> {};
|
||||
private static final Consumer<TypeSpec.Builder> typeSpecCustomizer = type -> {};
|
||||
|
||||
private final GeneratedClasses generatedClasses = new GeneratedClasses(
|
||||
new ClassNameGenerator(TestTarget.class));
|
||||
@@ -113,34 +113,34 @@ class DefaultGenerationContextTests {
|
||||
new ClassNameGenerator(TestTarget.class), this.generatedFiles);
|
||||
GenerationContext anotherContext = context.withName("Another");
|
||||
GeneratedClass generatedClass = anotherContext.getGeneratedClasses()
|
||||
.forFeature("Test").generate(typeSpecCustomizer);
|
||||
.addForFeature("Test", typeSpecCustomizer);
|
||||
assertThat(generatedClass.getName().simpleName()).endsWith("__AnotherTest");
|
||||
}
|
||||
|
||||
@Test
|
||||
void withNameKeepTrackOfAllGeneratedFiles() {
|
||||
void withNameKeepsTrackOfAllGeneratedFiles() {
|
||||
DefaultGenerationContext context = new DefaultGenerationContext(
|
||||
new ClassNameGenerator(TestTarget.class), this.generatedFiles);
|
||||
context.getGeneratedClasses().forFeature("Test").generate(typeSpecCustomizer);
|
||||
context.getGeneratedClasses().addForFeature("Test", 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);
|
||||
anotherContext.getGeneratedClasses().addForFeature("Test", typeSpecCustomizer);
|
||||
context.writeGeneratedContent();
|
||||
assertThat(this.generatedFiles.getGeneratedFiles(Kind.SOURCE)).hasSize(2);
|
||||
}
|
||||
|
||||
@Test
|
||||
void withNameGenerateUniqueName() {
|
||||
void withNameGeneratesUniqueName() {
|
||||
DefaultGenerationContext context = new DefaultGenerationContext(
|
||||
new ClassNameGenerator(Object.class), this.generatedFiles);
|
||||
context.withName("Test").getGeneratedClasses()
|
||||
.forFeature("Feature").generate(typeSpecCustomizer);
|
||||
.addForFeature("Feature", typeSpecCustomizer);
|
||||
context.withName("Test").getGeneratedClasses()
|
||||
.forFeature("Feature").generate(typeSpecCustomizer);
|
||||
.addForFeature("Feature", typeSpecCustomizer);
|
||||
context.withName("Test").getGeneratedClasses()
|
||||
.forFeature("Feature").generate(typeSpecCustomizer);
|
||||
.addForFeature("Feature", typeSpecCustomizer);
|
||||
context.writeGeneratedContent();
|
||||
assertThat(this.generatedFiles.getGeneratedFiles(Kind.SOURCE)).containsOnlyKeys(
|
||||
"java/lang/Object__TestFeature.java",
|
||||
|
||||
@@ -21,9 +21,11 @@ import java.util.function.Consumer;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import org.springframework.javapoet.ClassName;
|
||||
import org.springframework.javapoet.TypeSpec.Builder;
|
||||
import org.springframework.javapoet.MethodSpec;
|
||||
import org.springframework.javapoet.TypeSpec;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.Assertions.assertThatIllegalStateException;
|
||||
|
||||
/**
|
||||
* Tests for {@link GeneratedClass}.
|
||||
@@ -33,26 +35,49 @@ import static org.assertj.core.api.Assertions.assertThat;
|
||||
*/
|
||||
class GeneratedClassTests {
|
||||
|
||||
private static final Consumer<TypeSpec.Builder> emptyTypeCustomizer = type -> {};
|
||||
|
||||
private static final Consumer<MethodSpec.Builder> emptyMethodCustomizer = method -> {};
|
||||
|
||||
@Test
|
||||
void getNameReturnsName() {
|
||||
ClassName name = ClassName.bestGuess("com.example.Test");
|
||||
GeneratedClass generatedClass = new GeneratedClass(emptyTypeSpec(), name);
|
||||
GeneratedClass generatedClass = new GeneratedClass(name, emptyTypeCustomizer);
|
||||
assertThat(generatedClass.getName()).isSameAs(name);
|
||||
}
|
||||
|
||||
@Test
|
||||
void reserveMethodNamesWhenNameUsedThrowsException() {
|
||||
ClassName name = ClassName.bestGuess("com.example.Test");
|
||||
GeneratedClass generatedClass = new GeneratedClass(name, emptyTypeCustomizer);
|
||||
generatedClass.getMethods().add("apply", emptyMethodCustomizer);
|
||||
assertThatIllegalStateException()
|
||||
.isThrownBy(() -> generatedClass.reserveMethodNames("apply"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void reserveMethodNamesReservesNames() {
|
||||
ClassName name = ClassName.bestGuess("com.example.Test");
|
||||
GeneratedClass generatedClass = new GeneratedClass(name, emptyTypeCustomizer);
|
||||
generatedClass.reserveMethodNames("apply");
|
||||
GeneratedMethod generatedMethod = generatedClass.getMethods().add("apply", emptyMethodCustomizer);
|
||||
assertThat(generatedMethod.getName()).isEqualTo("apply1");
|
||||
}
|
||||
|
||||
@Test
|
||||
void generateMethodNameWhenAllEmptyPartsGeneratesSetName() {
|
||||
ClassName name = ClassName.bestGuess("com.example.Test");
|
||||
GeneratedClass generatedClass = new GeneratedClass(name, emptyTypeCustomizer);
|
||||
GeneratedMethod generatedMethod = generatedClass.getMethods().add("123", emptyMethodCustomizer);
|
||||
assertThat(generatedMethod.getName()).isEqualTo("$$aot");
|
||||
}
|
||||
|
||||
@Test
|
||||
void generateJavaFileIncludesGeneratedMethods() {
|
||||
ClassName name = ClassName.bestGuess("com.example.Test");
|
||||
GeneratedClass generatedClass = new GeneratedClass(emptyTypeSpec(), name);
|
||||
MethodGenerator methodGenerator = generatedClass.getMethodGenerator();
|
||||
methodGenerator.generateMethod("test")
|
||||
.using(builder -> builder.addJavadoc("Test Method"));
|
||||
GeneratedClass generatedClass = new GeneratedClass(name, emptyTypeCustomizer);
|
||||
generatedClass.getMethods().add("test", method -> method.addJavadoc("Test Method"));
|
||||
assertThat(generatedClass.generateJavaFile().toString()).contains("Test Method");
|
||||
}
|
||||
|
||||
|
||||
private Consumer<Builder> emptyTypeSpec() {
|
||||
return type -> {};
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -23,7 +23,6 @@ import org.junit.jupiter.api.Test;
|
||||
|
||||
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;
|
||||
@@ -52,93 +51,112 @@ class GeneratedClassesTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
void forFeatureComponentWhenTargetIsNullThrowsException() {
|
||||
void addForFeatureComponentWhenFeatureNameIsEmptyThrowsException() {
|
||||
assertThatIllegalArgumentException()
|
||||
.isThrownBy(() -> this.generatedClasses.forFeatureComponent("test", null))
|
||||
.withMessage("'component' must not be null");
|
||||
}
|
||||
|
||||
@Test
|
||||
void forFeatureComponentWhenFeatureNameIsEmptyThrowsException() {
|
||||
assertThatIllegalArgumentException()
|
||||
.isThrownBy(() -> this.generatedClasses.forFeatureComponent("", TestComponent.class))
|
||||
.isThrownBy(() -> this.generatedClasses.addForFeatureComponent("",
|
||||
TestComponent.class, emptyTypeCustomizer))
|
||||
.withMessage("'featureName' must not be empty");
|
||||
}
|
||||
|
||||
@Test
|
||||
void forFeatureWhenFeatureNameIsEmptyThrowsException() {
|
||||
void addForFeatureWhenFeatureNameIsEmptyThrowsException() {
|
||||
assertThatIllegalArgumentException()
|
||||
.isThrownBy(() -> this.generatedClasses.forFeature(""))
|
||||
.isThrownBy(() -> this.generatedClasses.addForFeature("", emptyTypeCustomizer))
|
||||
.withMessage("'featureName' must not be empty");
|
||||
}
|
||||
|
||||
@Test
|
||||
void generateWhenTypeSpecCustomizerIsNullThrowsException() {
|
||||
void addForFeatureComponentWhenTypeSpecCustomizerIsNullThrowsException() {
|
||||
assertThatIllegalArgumentException()
|
||||
.isThrownBy(() -> this.generatedClasses
|
||||
.forFeatureComponent("test", TestComponent.class).generate(null))
|
||||
.withMessage("'typeSpecCustomizer' must not be null");
|
||||
.addForFeatureComponent("test", TestComponent.class, null))
|
||||
.withMessage("'type' must not be null");
|
||||
}
|
||||
|
||||
@Test
|
||||
void forFeatureUsesDefaultTarget() {
|
||||
GeneratedClass generatedClass = this.generatedClasses
|
||||
.forFeature("Test").generate(emptyTypeCustomizer);
|
||||
void addForFeatureUsesDefaultTarget() {
|
||||
GeneratedClass generatedClass = this.generatedClasses.addForFeature("Test", emptyTypeCustomizer);
|
||||
assertThat(generatedClass.getName()).hasToString("java.lang.Object__Test");
|
||||
}
|
||||
|
||||
@Test
|
||||
void forFeatureComponentUsesComponent() {
|
||||
void addForFeatureComponentUsesTarget() {
|
||||
GeneratedClass generatedClass = this.generatedClasses
|
||||
.forFeatureComponent("Test", TestComponent.class).generate(emptyTypeCustomizer);
|
||||
.addForFeatureComponent("Test", TestComponent.class, emptyTypeCustomizer);
|
||||
assertThat(generatedClass.getName().toString()).endsWith("TestComponent__Test");
|
||||
}
|
||||
|
||||
@Test
|
||||
void generateReturnsDifferentInstances() {
|
||||
Consumer<Builder> typeCustomizer = mockTypeCustomizer();
|
||||
void addForFeatureComponentWithSameNameReturnsDifferentInstances() {
|
||||
GeneratedClass generatedClass1 = this.generatedClasses
|
||||
.forFeatureComponent("one", TestComponent.class).generate(typeCustomizer);
|
||||
.addForFeatureComponent("one", TestComponent.class, emptyTypeCustomizer);
|
||||
GeneratedClass generatedClass2 = this.generatedClasses
|
||||
.forFeatureComponent("one", TestComponent.class).generate(typeCustomizer);
|
||||
.addForFeatureComponent("one", TestComponent.class, emptyTypeCustomizer);
|
||||
assertThat(generatedClass1).isNotSameAs(generatedClass2);
|
||||
assertThat(generatedClass1.getName().simpleName()).endsWith("__One");
|
||||
assertThat(generatedClass2.getName().simpleName()).endsWith("__One1");
|
||||
}
|
||||
|
||||
@Test
|
||||
void getOrGenerateWhenNewReturnsGeneratedMethod() {
|
||||
Consumer<Builder> typeCustomizer = mockTypeCustomizer();
|
||||
void getOrAddForFeatureComponentWhenNewReturnsGeneratedMethod() {
|
||||
GeneratedClass generatedClass1 = this.generatedClasses
|
||||
.forFeatureComponent("one", TestComponent.class).getOrGenerate("facet", typeCustomizer);
|
||||
.getOrAddForFeatureComponent("one", TestComponent.class, emptyTypeCustomizer);
|
||||
GeneratedClass generatedClass2 = this.generatedClasses
|
||||
.forFeatureComponent("two", TestComponent.class).getOrGenerate("facet", typeCustomizer);
|
||||
.getOrAddForFeatureComponent("two", TestComponent.class, emptyTypeCustomizer);
|
||||
assertThat(generatedClass1).isNotNull().isNotEqualTo(generatedClass2);
|
||||
assertThat(generatedClass2).isNotNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
void getOrGenerateWhenRepeatReturnsSameGeneratedMethod() {
|
||||
Consumer<Builder> typeCustomizer = mockTypeCustomizer();
|
||||
void getOrAddForFeatureWhenNewReturnsGeneratedMethod() {
|
||||
GeneratedClass generatedClass1 = this.generatedClasses
|
||||
.forFeatureComponent("one", TestComponent.class).getOrGenerate("facet", typeCustomizer);
|
||||
.getOrAddForFeature("one", emptyTypeCustomizer);
|
||||
GeneratedClass generatedClass2 = this.generatedClasses
|
||||
.forFeatureComponent("one", TestComponent.class).getOrGenerate("facet", typeCustomizer);
|
||||
.getOrAddForFeature("two", emptyTypeCustomizer);
|
||||
assertThat(generatedClass1).isNotNull().isNotEqualTo(generatedClass2);
|
||||
assertThat(generatedClass2).isNotNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
void getOrAddForFeatureComponentWhenRepeatReturnsSameGeneratedMethod() {
|
||||
GeneratedClass generatedClass1 = this.generatedClasses
|
||||
.getOrAddForFeatureComponent("one", TestComponent.class, emptyTypeCustomizer);
|
||||
GeneratedClass generatedClass2 = this.generatedClasses
|
||||
.getOrAddForFeatureComponent("one", TestComponent.class, emptyTypeCustomizer);
|
||||
GeneratedClass generatedClass3 = this.generatedClasses
|
||||
.forFeatureComponent("one", TestComponent.class).getOrGenerate("facet", typeCustomizer);
|
||||
.getOrAddForFeatureComponent("one", TestComponent.class, emptyTypeCustomizer);
|
||||
assertThat(generatedClass1).isNotNull().isSameAs(generatedClass2)
|
||||
.isSameAs(generatedClass3);
|
||||
verifyNoInteractions(typeCustomizer);
|
||||
generatedClass1.generateJavaFile();
|
||||
verify(typeCustomizer).accept(any());
|
||||
}
|
||||
|
||||
@Test
|
||||
void getOrAddForFeatureWhenRepeatReturnsSameGeneratedMethod() {
|
||||
GeneratedClass generatedClass1 = this.generatedClasses
|
||||
.getOrAddForFeature("one", emptyTypeCustomizer);
|
||||
GeneratedClass generatedClass2 = this.generatedClasses
|
||||
.getOrAddForFeature("one", emptyTypeCustomizer);
|
||||
GeneratedClass generatedClass3 = this.generatedClasses
|
||||
.getOrAddForFeature("one", emptyTypeCustomizer);
|
||||
assertThat(generatedClass1).isNotNull().isSameAs(generatedClass2)
|
||||
.isSameAs(generatedClass3);
|
||||
}
|
||||
|
||||
@Test
|
||||
void getOrAddForFeatureComponentWhenHasFeatureNamePrefix() {
|
||||
GeneratedClasses prefixed = this.generatedClasses.withFeatureNamePrefix("prefix");
|
||||
GeneratedClass generatedClass1 = this.generatedClasses.getOrAddForFeatureComponent("one", TestComponent.class, emptyTypeCustomizer);
|
||||
GeneratedClass generatedClass2 = this.generatedClasses.getOrAddForFeatureComponent("one", TestComponent.class, emptyTypeCustomizer);
|
||||
GeneratedClass generatedClass3 = prefixed.getOrAddForFeatureComponent("one", TestComponent.class, emptyTypeCustomizer);
|
||||
GeneratedClass generatedClass4 = prefixed.getOrAddForFeatureComponent("one", TestComponent.class, emptyTypeCustomizer);
|
||||
assertThat(generatedClass1).isSameAs(generatedClass2).isNotSameAs(generatedClass3);
|
||||
assertThat(generatedClass3).isSameAs(generatedClass4);
|
||||
}
|
||||
|
||||
@Test
|
||||
@SuppressWarnings("unchecked")
|
||||
void writeToInvokeTypeSpecCustomizer() throws IOException {
|
||||
Consumer<TypeSpec.Builder> typeSpecCustomizer = mock(Consumer.class);
|
||||
this.generatedClasses.forFeatureComponent("one", TestComponent.class)
|
||||
.generate(typeSpecCustomizer);
|
||||
this.generatedClasses.addForFeatureComponent("one", TestComponent.class, typeSpecCustomizer);
|
||||
verifyNoInteractions(typeSpecCustomizer);
|
||||
InMemoryGeneratedFiles generatedFiles = new InMemoryGeneratedFiles();
|
||||
this.generatedClasses.writeTo(generatedFiles);
|
||||
@@ -149,20 +167,14 @@ class GeneratedClassesTests {
|
||||
@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);
|
||||
.addForFeatureComponent("one", TestComponent.class, emptyTypeCustomizer);
|
||||
GeneratedClass generatedClass2 = this.generatedClasses.withFeatureNamePrefix("Another")
|
||||
.addForFeatureComponent("one", TestComponent.class, 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 {
|
||||
|
||||
}
|
||||
|
||||
@@ -16,12 +16,13 @@
|
||||
|
||||
package org.springframework.aot.generate;
|
||||
|
||||
import javax.lang.model.element.Modifier;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import org.springframework.javapoet.MethodSpec;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
|
||||
import static org.assertj.core.api.Assertions.assertThatIllegalStateException;
|
||||
|
||||
/**
|
||||
@@ -31,42 +32,27 @@ import static org.assertj.core.api.Assertions.assertThatIllegalStateException;
|
||||
*/
|
||||
class GeneratedMethodTests {
|
||||
|
||||
private static final Consumer<MethodSpec.Builder> methodSpecCustomizer = method -> {};
|
||||
|
||||
private static final String NAME = "spring";
|
||||
|
||||
@Test
|
||||
void getNameReturnsName() {
|
||||
GeneratedMethod method = new GeneratedMethod(NAME);
|
||||
assertThat(method.getName()).isSameAs(NAME);
|
||||
GeneratedMethod generatedMethod = new GeneratedMethod(NAME, methodSpecCustomizer);
|
||||
assertThat(generatedMethod.getName()).isSameAs(NAME);
|
||||
}
|
||||
|
||||
@Test
|
||||
void getSpecReturnsSpec() {
|
||||
GeneratedMethod method = new GeneratedMethod(NAME);
|
||||
method.using(builder -> builder.addJavadoc("Test"));
|
||||
assertThat(method.getSpec().javadoc).asString().contains("Test");
|
||||
void generateMethodSpecReturnsMethodSpec() {
|
||||
GeneratedMethod generatedMethod = new GeneratedMethod(NAME, method -> method.addJavadoc("Test"));
|
||||
assertThat(generatedMethod.getMethodSpec().javadoc).asString().contains("Test");
|
||||
}
|
||||
|
||||
@Test
|
||||
void getSpecReturnsSpecWhenNoSpecDefinedThrowsException() {
|
||||
GeneratedMethod method = new GeneratedMethod(NAME);
|
||||
assertThatIllegalStateException().isThrownBy(() -> method.getSpec())
|
||||
.withMessage("Method 'spring' has no method spec defined");
|
||||
}
|
||||
|
||||
@Test
|
||||
void usingAddsSpec() {
|
||||
GeneratedMethod method = new GeneratedMethod(NAME);
|
||||
method.using(builder -> builder.addModifiers(Modifier.PUBLIC));
|
||||
assertThat(method.getSpec()).asString()
|
||||
.isEqualToIgnoringNewLines("public void spring() {}");
|
||||
}
|
||||
|
||||
@Test
|
||||
void usingWhenBuilderChanagesNameThrowsException() {
|
||||
GeneratedMethod method = new GeneratedMethod(NAME);
|
||||
assertThatIllegalArgumentException()
|
||||
.isThrownBy(() -> method.using(builder -> builder.setName("badname")))
|
||||
.withMessage("'spec' must use the generated name 'spring'");
|
||||
void generateMethodSpecWhenMethodNameIsChangedThrowsException() {
|
||||
assertThatIllegalStateException().isThrownBy(() ->
|
||||
new GeneratedMethod(NAME, method -> method.setName("badname")).getMethodSpec())
|
||||
.withMessage("'method' consumer must not change the generated method name");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -17,8 +17,9 @@
|
||||
package org.springframework.aot.generate;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.function.Function;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
@@ -26,7 +27,6 @@ import org.springframework.javapoet.MethodSpec;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
|
||||
import static org.assertj.core.api.Assertions.assertThatIllegalStateException;
|
||||
|
||||
/**
|
||||
* Tests for {@link GeneratedMethods}.
|
||||
@@ -35,7 +35,9 @@ import static org.assertj.core.api.Assertions.assertThatIllegalStateException;
|
||||
*/
|
||||
class GeneratedMethodsTests {
|
||||
|
||||
private final GeneratedMethods methods = new GeneratedMethods();
|
||||
private static final Consumer<MethodSpec.Builder> methodSpecCustomizer = method -> {};
|
||||
|
||||
private final GeneratedMethods methods = new GeneratedMethods(MethodName::toString);
|
||||
|
||||
@Test
|
||||
void createWhenMethodNameGeneratorIsNullThrowsException() {
|
||||
@@ -45,56 +47,82 @@ class GeneratedMethodsTests {
|
||||
|
||||
@Test
|
||||
void createWithExistingGeneratorUsesGenerator() {
|
||||
MethodNameGenerator generator = new MethodNameGenerator();
|
||||
generator.generateMethodName("test");
|
||||
Function<MethodName, String> generator = name -> "__" + name.toString();
|
||||
GeneratedMethods methods = new GeneratedMethods(generator);
|
||||
assertThat(methods.add("test").getName()).hasToString("test1");
|
||||
assertThat(methods.add("test", methodSpecCustomizer).getName()).hasToString("__test");
|
||||
}
|
||||
|
||||
@Test
|
||||
void addWithMethodNameWhenSuggestedMethodIsNullThrowsException() {
|
||||
assertThatIllegalArgumentException().isThrownBy(() ->
|
||||
this.methods.add((MethodName) null, methodSpecCustomizer))
|
||||
.withMessage("'suggestedName' must not be null");
|
||||
}
|
||||
|
||||
@Test
|
||||
void addWithMethodNameWhenMethodIsNullThrowsException() {
|
||||
assertThatIllegalArgumentException().isThrownBy(() ->
|
||||
this.methods.add(MethodName.of("test"), null))
|
||||
.withMessage("'method' must not be null");
|
||||
}
|
||||
|
||||
@Test
|
||||
void addWithStringNameWhenSuggestedMethodIsNullThrowsException() {
|
||||
assertThatIllegalArgumentException().isThrownBy(() ->
|
||||
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");
|
||||
}
|
||||
|
||||
@Test
|
||||
void addAddsMethod() {
|
||||
this.methods.add("spring", "beans").using(this::build);
|
||||
this.methods.add("spring", "context").using(this::build);
|
||||
assertThat(
|
||||
this.methods.stream().map(GeneratedMethod::getName).map(Object::toString))
|
||||
this.methods.add("springBeans", methodSpecCustomizer);
|
||||
this.methods.add("springContext", methodSpecCustomizer);
|
||||
assertThat(this.methods.stream().map(GeneratedMethod::getName).map(Object::toString))
|
||||
.containsExactly("springBeans", "springContext");
|
||||
}
|
||||
|
||||
@Test
|
||||
void withPrefixWhenGeneratingGetMethodUsesPrefix() {
|
||||
GeneratedMethod generateMethod = this.methods.withPrefix("myBean")
|
||||
.add("getTest", methodSpecCustomizer);
|
||||
assertThat(generateMethod.getName()).hasToString("getMyBeanTest");
|
||||
}
|
||||
|
||||
@Test
|
||||
void withPrefixWhenGeneratingSetMethodUsesPrefix() {
|
||||
GeneratedMethod generateMethod = this.methods.withPrefix("myBean")
|
||||
.add("setTest", methodSpecCustomizer);
|
||||
assertThat(generateMethod.getName()).hasToString("setMyBeanTest");
|
||||
}
|
||||
|
||||
@Test
|
||||
void withPrefixWhenGeneratingIsMethodUsesPrefix() {
|
||||
GeneratedMethod generateMethod = this.methods.withPrefix("myBean")
|
||||
.add("isTest", methodSpecCustomizer);
|
||||
assertThat(generateMethod.getName()).hasToString("isMyBeanTest");
|
||||
}
|
||||
|
||||
@Test
|
||||
void withPrefixWhenGeneratingOtherMethodUsesPrefix() {
|
||||
GeneratedMethod generateMethod = this.methods.withPrefix("myBean")
|
||||
.add("test", methodSpecCustomizer);
|
||||
assertThat(generateMethod.getName()).hasToString("myBeanTest");
|
||||
}
|
||||
|
||||
@Test
|
||||
void doWithMethodSpecsAcceptsMethodSpecs() {
|
||||
this.methods.add("spring", "beans").using(this::build);
|
||||
this.methods.add("spring", "context").using(this::build);
|
||||
this.methods.add("springBeans", methodSpecCustomizer);
|
||||
this.methods.add("springContext", methodSpecCustomizer);
|
||||
List<String> names = new ArrayList<>();
|
||||
this.methods.doWithMethodSpecs(spec -> names.add(spec.name));
|
||||
this.methods.doWithMethodSpecs(methodSpec -> names.add(methodSpec.name));
|
||||
assertThat(names).containsExactly("springBeans", "springContext");
|
||||
}
|
||||
|
||||
@Test
|
||||
void doWithMethodSpecsWhenMethodHasNotHadSpecDefinedThrowsException() {
|
||||
this.methods.add("spring");
|
||||
assertThatIllegalStateException()
|
||||
.isThrownBy(() -> this.methods.doWithMethodSpecs(spec -> {
|
||||
})).withMessage("Method 'spring' has no method spec defined");
|
||||
}
|
||||
|
||||
@Test
|
||||
void iteratorIteratesMethods() {
|
||||
this.methods.add("spring", "beans").using(this::build);
|
||||
this.methods.add("spring", "context").using(this::build);
|
||||
Iterator<GeneratedMethod> iterator = this.methods.iterator();
|
||||
assertThat(iterator.next().getName()).hasToString("springBeans");
|
||||
assertThat(iterator.next().getName()).hasToString("springContext");
|
||||
assertThat(iterator.hasNext()).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
void streamStreamsMethods() {
|
||||
this.methods.add("spring", "beans").using(this::build);
|
||||
this.methods.add("spring", "context").using(this::build);
|
||||
assertThat(this.methods.stream()).hasSize(2);
|
||||
}
|
||||
|
||||
private void build(MethodSpec.Builder builder) {
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,61 +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 org.junit.jupiter.api.Test;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
/**
|
||||
* Tests {@link MethodGeneratorWithName}.
|
||||
*
|
||||
* @author Phillip Webb
|
||||
* @since 6.0
|
||||
*/
|
||||
class MethodGeneratorWithNameTests {
|
||||
|
||||
private final GeneratedMethods generatedMethods = new GeneratedMethods();
|
||||
|
||||
@Test
|
||||
void withNameWhenGeneratingGetMethod() {
|
||||
GeneratedMethod generateMethod = generatedMethods.withName("my", "bean")
|
||||
.generateMethod("get", "test");
|
||||
assertThat(generateMethod.getName()).hasToString("getMyBeanTest");
|
||||
}
|
||||
|
||||
@Test
|
||||
void withNameWhenGeneratingSetMethod() {
|
||||
GeneratedMethod generateMethod = generatedMethods.withName("my", "bean")
|
||||
.generateMethod("set", "test");
|
||||
assertThat(generateMethod.getName()).hasToString("setMyBeanTest");
|
||||
}
|
||||
|
||||
@Test
|
||||
void withNameWhenGeneratingIsMethod() {
|
||||
GeneratedMethod generateMethod = generatedMethods.withName("my", "bean")
|
||||
.generateMethod("is", "test");
|
||||
assertThat(generateMethod.getName()).hasToString("isMyBeanTest");
|
||||
}
|
||||
|
||||
@Test
|
||||
void withNameWhenGeneratingOtherMethod() {
|
||||
GeneratedMethod generateMethod = generatedMethods.withName("my", "bean")
|
||||
.generateMethod("test");
|
||||
assertThat(generateMethod.getName()).hasToString("myBeanTest");
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,93 +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.io.InputStream;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
/**
|
||||
* Tests for {@link MethodNameGenerator}.
|
||||
*
|
||||
* @author Phillip Webb
|
||||
*/
|
||||
class MethodNameGeneratorTests {
|
||||
|
||||
private final MethodNameGenerator generator = new MethodNameGenerator();
|
||||
|
||||
@Test
|
||||
void createWithReservedNamesReservesNames() {
|
||||
MethodNameGenerator generator = new MethodNameGenerator("testName");
|
||||
assertThat(generator.generateMethodName("test", "name")).hasToString("testName1");
|
||||
}
|
||||
|
||||
@Test
|
||||
void generateMethodNameGeneratesName() {
|
||||
String generated = this.generator.generateMethodName("register", "myBean",
|
||||
"bean");
|
||||
assertThat(generated).isEqualTo("registerMyBeanBean");
|
||||
}
|
||||
|
||||
@Test
|
||||
void generateMethodNameWhenHasNonLettersGeneratesName() {
|
||||
String generated = this.generator.generateMethodName("register", "myBean123",
|
||||
"bean");
|
||||
assertThat(generated).isEqualTo("registerMyBeanBean");
|
||||
}
|
||||
|
||||
@Test
|
||||
void generateMethodNameWhenHasDotsGeneratesCamelCaseName() {
|
||||
String generated = this.generator.generateMethodName("register",
|
||||
"org.springframework.example.bean");
|
||||
assertThat(generated).isEqualTo("registerOrgSpringframeworkExampleBean");
|
||||
}
|
||||
|
||||
@Test
|
||||
void generateMethodNameWhenMultipleCallsGeneratesSequencedName() {
|
||||
String generated1 = this.generator.generateMethodName("register", "myBean123",
|
||||
"bean");
|
||||
String generated2 = this.generator.generateMethodName("register", "myBean!",
|
||||
"bean");
|
||||
String generated3 = this.generator.generateMethodName("register", "myBean%%",
|
||||
"bean");
|
||||
assertThat(generated1).isEqualTo("registerMyBeanBean");
|
||||
assertThat(generated2).isEqualTo("registerMyBeanBean1");
|
||||
assertThat(generated3).isEqualTo("registerMyBeanBean2");
|
||||
}
|
||||
|
||||
@Test
|
||||
void generateMethodNameWhenAllEmptyPartsGeneratesSetName() {
|
||||
String generated = this.generator.generateMethodName("123");
|
||||
assertThat(generated).isEqualTo("$$aot");
|
||||
}
|
||||
|
||||
@Test
|
||||
void joinReturnsJoinedName() {
|
||||
assertThat(MethodNameGenerator.join("get", "bean", "factory"))
|
||||
.isEqualTo("getBeanFactory");
|
||||
assertThat(MethodNameGenerator.join("get", null, "factory"))
|
||||
.isEqualTo("getFactory");
|
||||
assertThat(MethodNameGenerator.join(null, null)).isEqualTo("");
|
||||
assertThat(MethodNameGenerator.join("", null)).isEqualTo("");
|
||||
assertThat(MethodNameGenerator.join("get", InputStream.class))
|
||||
.isEqualTo("getInputStream");
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,92 @@
|
||||
/*
|
||||
* 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 org.junit.jupiter.api.Test;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
|
||||
|
||||
/**
|
||||
* Tests for {@link MethodName}.
|
||||
*
|
||||
* @author Phillip Webb
|
||||
*/
|
||||
class MethodNameTests {
|
||||
|
||||
@Test
|
||||
void ofWhenPartsIsNullThrowsException() {
|
||||
assertThatIllegalArgumentException()
|
||||
.isThrownBy(() -> MethodName.of((String[]) null))
|
||||
.withMessage("'parts' must not be null");
|
||||
}
|
||||
|
||||
@Test
|
||||
void ofReturnsMethodName() {
|
||||
assertThat(MethodName.of("get", "bean", "factory")).hasToString("getBeanFactory");
|
||||
assertThat(MethodName.of("get", null, "factory")).hasToString("getFactory");
|
||||
assertThat(MethodName.of(null, null)).hasToString("$$aot");
|
||||
assertThat(MethodName.of("", null)).hasToString("$$aot");
|
||||
assertThat(MethodName.of("get", "InputStream")).hasToString("getInputStream");
|
||||
assertThat(MethodName.of("register", "myBean123", "bean")).hasToString("registerMyBeanBean");
|
||||
assertThat(MethodName.of("register", "org.springframework.example.bean"))
|
||||
.hasToString("registerOrgSpringframeworkExampleBean");
|
||||
}
|
||||
|
||||
@Test
|
||||
void andPartsWhenPartsIsNullThrowsException() {
|
||||
MethodName name = MethodName.of("myBean");
|
||||
assertThatIllegalArgumentException()
|
||||
.isThrownBy(() -> name.and(((String[]) null)))
|
||||
.withMessage("'parts' must not be null");
|
||||
}
|
||||
|
||||
@Test
|
||||
void andPartsReturnsMethodName() {
|
||||
MethodName name = MethodName.of("myBean");
|
||||
assertThat(name.and("test")).hasToString("myBeanTest");
|
||||
assertThat(name.and("test", null)).hasToString("myBeanTest");
|
||||
assertThat(name.and("getName")).hasToString("getMyBeanName");
|
||||
assertThat(name.and("setName")).hasToString("setMyBeanName");
|
||||
assertThat(name.and("isDoingOk")).hasToString("isMyBeanDoingOk");
|
||||
assertThat(name.and("this", "that", "the", "other")).hasToString("myBeanThisThatTheOther");
|
||||
}
|
||||
|
||||
@Test
|
||||
void andNameWhenPartsIsNullThrowsException() {
|
||||
MethodName name = MethodName.of("myBean");
|
||||
assertThatIllegalArgumentException()
|
||||
.isThrownBy(() -> name.and(((MethodName) null)))
|
||||
.withMessage("'name' must not be null");
|
||||
}
|
||||
|
||||
@Test
|
||||
void andNameReturnsMethodName() {
|
||||
MethodName name = MethodName.of("myBean");
|
||||
assertThat(name.and(MethodName.of("test"))).hasToString("myBeanTest");
|
||||
}
|
||||
|
||||
@Test
|
||||
void hashCodeAndEquals() {
|
||||
MethodName name1 = MethodName.of("myBean");
|
||||
MethodName name2 = MethodName.of("my", "bean");
|
||||
MethodName name3 = MethodName.of("myOtherBean");
|
||||
assertThat(name1.hashCode()).isEqualTo(name2.hashCode());
|
||||
assertThat(name1).isEqualTo(name1).isEqualTo(name2).isNotEqualTo(name3);
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user