mirror of
https://gitee.com/dcren/initializr.git
synced 2025-04-05 17:38:06 +08:00
Overhaul annotation support for code generation
This commit improves the annotation support so that it handles more attribute types. An AnnotationContainer is introduced that permits to further configure an annotation, in particular attribute values can be added, and an attribute can be removed. This commit adds a number of deprecations: * Annotation#getName has been deprecated in favor of Annotation#getClasName. * Annotable#annotate has been deprecated in favor of Annotable#annotations()#add. * Annotable#getAnnotations has been deprecated in favor of Annotable#annotations()#values. * Annotation#name has been deprecated in favor of Annotation#of. * Annotation.Builder#attribute has been deprecated in favor of Annotation.Builder#set. In particular, the type of the attribute does not need to be specified as it is infered from the specified values. Use Annotation.Builder#add or Annotation.Builder#set depending on your needs. Closes gh-1424
This commit is contained in:
parent
1d9e6b5b7b
commit
eb618d5dd7
@ -18,7 +18,7 @@ package io.spring.initializr.generator.spring.code;
|
||||
|
||||
import io.spring.initializr.generator.condition.ConditionalOnPackaging;
|
||||
import io.spring.initializr.generator.condition.ConditionalOnPlatformVersion;
|
||||
import io.spring.initializr.generator.language.Annotation;
|
||||
import io.spring.initializr.generator.language.ClassName;
|
||||
import io.spring.initializr.generator.language.TypeDeclaration;
|
||||
import io.spring.initializr.generator.packaging.war.WarPackaging;
|
||||
import io.spring.initializr.generator.project.ProjectDescription;
|
||||
@ -38,14 +38,14 @@ public class SourceCodeProjectGenerationConfiguration {
|
||||
|
||||
@Bean
|
||||
public MainApplicationTypeCustomizer<TypeDeclaration> springBootApplicationAnnotator() {
|
||||
return (typeDeclaration) -> typeDeclaration
|
||||
.annotate(Annotation.name("org.springframework.boot.autoconfigure.SpringBootApplication"));
|
||||
return (typeDeclaration) -> typeDeclaration.annotations()
|
||||
.add(ClassName.of("org.springframework.boot.autoconfigure.SpringBootApplication"));
|
||||
}
|
||||
|
||||
@Bean
|
||||
public TestApplicationTypeCustomizer<TypeDeclaration> junitJupiterSpringBootTestTypeCustomizer() {
|
||||
return (typeDeclaration) -> typeDeclaration
|
||||
.annotate(Annotation.name("org.springframework.boot.test.context.SpringBootTest"));
|
||||
return (typeDeclaration) -> typeDeclaration.annotations()
|
||||
.add(ClassName.of("org.springframework.boot.test.context.SpringBootTest"));
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -22,7 +22,7 @@ import io.spring.initializr.generator.buildsystem.Build;
|
||||
import io.spring.initializr.generator.buildsystem.maven.MavenBuildSystem;
|
||||
import io.spring.initializr.generator.condition.ConditionalOnBuildSystem;
|
||||
import io.spring.initializr.generator.condition.ConditionalOnPackaging;
|
||||
import io.spring.initializr.generator.language.Annotation;
|
||||
import io.spring.initializr.generator.language.ClassName;
|
||||
import io.spring.initializr.generator.language.CodeBlock;
|
||||
import io.spring.initializr.generator.language.Parameter;
|
||||
import io.spring.initializr.generator.language.groovy.GroovyMethodDeclaration;
|
||||
@ -66,7 +66,7 @@ class GroovyProjectGenerationDefaultContributorsConfiguration {
|
||||
GroovyMethodDeclaration method = GroovyMethodDeclaration.method("contextLoads")
|
||||
.returning("void")
|
||||
.body(CodeBlock.of(""));
|
||||
method.annotate(Annotation.name("org.junit.jupiter.api.Test"));
|
||||
method.annotations().add(ClassName.of("org.junit.jupiter.api.Test"));
|
||||
typeDeclaration.addMethodDeclaration(method);
|
||||
};
|
||||
}
|
||||
@ -93,7 +93,7 @@ class GroovyProjectGenerationDefaultContributorsConfiguration {
|
||||
.parameters(
|
||||
new Parameter("org.springframework.boot.builder.SpringApplicationBuilder", "application"))
|
||||
.body(CodeBlock.ofStatement("application.sources($L)", description.getApplicationName()));
|
||||
configure.annotate(Annotation.name("java.lang.Override"));
|
||||
configure.annotations().add(ClassName.of(Override.class));
|
||||
typeDeclaration.addMethodDeclaration(configure);
|
||||
};
|
||||
}
|
||||
|
@ -19,7 +19,7 @@ package io.spring.initializr.generator.spring.code.java;
|
||||
import java.lang.reflect.Modifier;
|
||||
|
||||
import io.spring.initializr.generator.condition.ConditionalOnPackaging;
|
||||
import io.spring.initializr.generator.language.Annotation;
|
||||
import io.spring.initializr.generator.language.ClassName;
|
||||
import io.spring.initializr.generator.language.CodeBlock;
|
||||
import io.spring.initializr.generator.language.Parameter;
|
||||
import io.spring.initializr.generator.language.java.JavaMethodDeclaration;
|
||||
@ -61,7 +61,7 @@ class JavaProjectGenerationDefaultContributorsConfiguration {
|
||||
JavaMethodDeclaration method = JavaMethodDeclaration.method("contextLoads")
|
||||
.returning("void")
|
||||
.body(CodeBlock.of(""));
|
||||
method.annotate(Annotation.name("org.junit.jupiter.api.Test"));
|
||||
method.annotations().add(ClassName.of("org.junit.jupiter.api.Test"));
|
||||
typeDeclaration.addMethodDeclaration(method);
|
||||
};
|
||||
}
|
||||
@ -85,7 +85,7 @@ class JavaProjectGenerationDefaultContributorsConfiguration {
|
||||
new Parameter("org.springframework.boot.builder.SpringApplicationBuilder", "application"))
|
||||
.body(CodeBlock.ofStatement("return application.sources($L.class)",
|
||||
description.getApplicationName()));
|
||||
configure.annotate(Annotation.name("java.lang.Override"));
|
||||
configure.annotations().add(ClassName.of(Override.class));
|
||||
typeDeclaration.addMethodDeclaration(configure);
|
||||
};
|
||||
}
|
||||
|
@ -22,7 +22,7 @@ import io.spring.initializr.generator.buildsystem.maven.MavenBuildSystem;
|
||||
import io.spring.initializr.generator.condition.ConditionalOnBuildSystem;
|
||||
import io.spring.initializr.generator.condition.ConditionalOnPackaging;
|
||||
import io.spring.initializr.generator.condition.ConditionalOnPlatformVersion;
|
||||
import io.spring.initializr.generator.language.Annotation;
|
||||
import io.spring.initializr.generator.language.ClassName;
|
||||
import io.spring.initializr.generator.language.CodeBlock;
|
||||
import io.spring.initializr.generator.language.Parameter;
|
||||
import io.spring.initializr.generator.language.kotlin.KotlinCompilationUnit;
|
||||
@ -55,7 +55,7 @@ class KotlinProjectGenerationDefaultContributorsConfiguration {
|
||||
return (typeDeclaration) -> {
|
||||
KotlinFunctionDeclaration function = KotlinFunctionDeclaration.function("contextLoads")
|
||||
.body(CodeBlock.of(""));
|
||||
function.annotate(Annotation.name("org.junit.jupiter.api.Test"));
|
||||
function.annotations().add(ClassName.of("org.junit.jupiter.api.Test"));
|
||||
typeDeclaration.addFunctionDeclaration(function);
|
||||
};
|
||||
}
|
||||
|
@ -16,6 +16,7 @@
|
||||
|
||||
package io.spring.initializr.generator.spring.code;
|
||||
|
||||
import io.spring.initializr.generator.language.ClassName;
|
||||
import io.spring.initializr.generator.language.CompilationUnit;
|
||||
import io.spring.initializr.generator.language.SourceCode;
|
||||
import io.spring.initializr.generator.language.TypeDeclaration;
|
||||
@ -50,18 +51,17 @@ class SourceCodeProjectGenerationConfigurationTests {
|
||||
bean.customize(type);
|
||||
return type;
|
||||
});
|
||||
assertThat(declaration.getAnnotations()).hasSize(1);
|
||||
assertThat(declaration.getAnnotations()).singleElement()
|
||||
.satisfies((annotation) -> assertThat(annotation.getName())
|
||||
.isEqualTo("org.springframework.boot.autoconfigure.SpringBootApplication"));
|
||||
assertThat(declaration.annotations().values()).singleElement()
|
||||
.satisfies((annotation) -> assertThat(annotation.getClassName())
|
||||
.isEqualTo(ClassName.of("org.springframework.boot.autoconfigure.SpringBootApplication")));
|
||||
}
|
||||
|
||||
@Test
|
||||
void addsACustomizerThatAppliesTestAnnotationsOnTestClassWithJunit5() {
|
||||
TypeDeclaration declaration = generateTestTypeDeclaration("2.2.0.RELEASE");
|
||||
assertThat(declaration.getAnnotations()).hasSize(1);
|
||||
assertThat(declaration.getAnnotations().get(0).getName())
|
||||
.isEqualTo("org.springframework.boot.test.context.SpringBootTest");
|
||||
assertThat(declaration.annotations().values()).singleElement()
|
||||
.satisfies((annotation) -> assertThat(annotation.getClassName())
|
||||
.isEqualTo(ClassName.of("org.springframework.boot.test.context.SpringBootTest")));
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2012-2019 the original author or authors.
|
||||
* Copyright 2012-2023 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@ -22,11 +22,25 @@ import java.util.List;
|
||||
* A representation of something that can be annotated.
|
||||
*
|
||||
* @author Andy Wilkinson
|
||||
* @author Stephane Nicoll
|
||||
*/
|
||||
public interface Annotatable {
|
||||
|
||||
void annotate(Annotation annotation);
|
||||
/**
|
||||
* Return the {@link AnnotationContainer} to use to configure the annotations of this
|
||||
* element.
|
||||
* @return the annotation container
|
||||
*/
|
||||
AnnotationContainer annotations();
|
||||
|
||||
List<Annotation> getAnnotations();
|
||||
@Deprecated(since = "0.20.0", forRemoval = true)
|
||||
default void annotate(Annotation annotation) {
|
||||
annotations().add(annotation.getClassName(), (builder) -> builder.from(annotation));
|
||||
}
|
||||
|
||||
@Deprecated(since = "0.20.0", forRemoval = true)
|
||||
default List<Annotation> getAnnotations() {
|
||||
return annotations().values().toList();
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2012-2019 the original author or authors.
|
||||
* Copyright 2012-2023 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@ -16,13 +16,22 @@
|
||||
|
||||
package io.spring.initializr.generator.language;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import io.spring.initializr.generator.io.IndentingWriter;
|
||||
import io.spring.initializr.generator.language.CodeBlock.FormattingOptions;
|
||||
|
||||
import org.springframework.util.ClassUtils;
|
||||
|
||||
/**
|
||||
* An annotation.
|
||||
@ -32,53 +41,223 @@ import java.util.function.Consumer;
|
||||
*/
|
||||
public final class Annotation {
|
||||
|
||||
private final String name;
|
||||
private final ClassName className;
|
||||
|
||||
private final List<Attribute> attributes;
|
||||
|
||||
private final List<String> imports;
|
||||
|
||||
private Annotation(Builder builder) {
|
||||
this.name = builder.name;
|
||||
this.attributes = Collections.unmodifiableList(new ArrayList<>(builder.attributes.values()));
|
||||
this.className = builder.className;
|
||||
this.attributes = List.copyOf(builder.attributes.values());
|
||||
this.imports = List.copyOf(builder.imports);
|
||||
}
|
||||
|
||||
@Deprecated(since = "0.20.0", forRemoval = true)
|
||||
public String getName() {
|
||||
return this.name;
|
||||
return this.className.getName();
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the {@link ClassName} of the annotation.
|
||||
* @return the class name
|
||||
*/
|
||||
public ClassName getClassName() {
|
||||
return this.className;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the {@linkplain Attribute attributes} of the annotation or an empty list if
|
||||
* the annotation has no defined attribute.
|
||||
* @return the attributes
|
||||
*/
|
||||
public List<Attribute> getAttributes() {
|
||||
return this.attributes;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the imports this instance contributes.
|
||||
* @return the imports.
|
||||
*/
|
||||
public List<String> getImports() {
|
||||
return this.imports;
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize an annotation {@link Builder} for the specified class name.
|
||||
* @param className the class name of the annotation
|
||||
* @return a builder
|
||||
*/
|
||||
public static Builder of(ClassName className) {
|
||||
return new Builder(className);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create an annotation with the specified class name.
|
||||
* @param name the name of the annotation
|
||||
* @return an annotation with no attribute defined
|
||||
* @deprecated as of 0.20.0 in favor of {@link #of(ClassName)}
|
||||
*/
|
||||
@Deprecated(since = "0.20.0", forRemoval = true)
|
||||
public static Annotation name(String name) {
|
||||
return name(name, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create an annotation with the specified class name, customized by the specified
|
||||
* consumer.
|
||||
* @param name the name of the annotation
|
||||
* @param annotation a consumer of the builder
|
||||
* @return an annotation with no attribute defined
|
||||
* @deprecated as of 0.20.0 in favor of {@link #of(ClassName)}
|
||||
*/
|
||||
@Deprecated(since = "0.20.0", forRemoval = true)
|
||||
public static Annotation name(String name, Consumer<Builder> annotation) {
|
||||
Builder builder = new Builder(name);
|
||||
Builder builder = of(ClassName.of(name));
|
||||
if (annotation != null) {
|
||||
annotation.accept(builder);
|
||||
}
|
||||
return new Annotation(builder);
|
||||
}
|
||||
|
||||
/**
|
||||
* Write this annotation using the specified writer.
|
||||
* @param writer the writer to use
|
||||
* @param options the formatting options to use
|
||||
*/
|
||||
public void write(IndentingWriter writer, FormattingOptions options) {
|
||||
new AnnotationWriter(writer, options).write(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Builder for creating an {@link Annotation}.
|
||||
*/
|
||||
public static final class Builder {
|
||||
|
||||
private final String name;
|
||||
private final ClassName className;
|
||||
|
||||
private final Set<String> imports = new HashSet<>();
|
||||
|
||||
private final Map<String, Attribute> attributes = new LinkedHashMap<>();
|
||||
|
||||
private Builder(String name) {
|
||||
this.name = name;
|
||||
Builder(ClassName name) {
|
||||
this.className = name;
|
||||
if (!name.getPackageName().isEmpty()) {
|
||||
this.imports.add(name.getName());
|
||||
}
|
||||
}
|
||||
|
||||
Builder(Builder copy) {
|
||||
this.className = copy.className;
|
||||
this.imports.addAll(copy.imports);
|
||||
this.attributes.putAll(copy.attributes);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the attribute with the specified name and String representation of the
|
||||
* specified values.
|
||||
* @param name the name of the attribute
|
||||
* @param type the type of the property
|
||||
* @param values the values to associate with the attribute
|
||||
* @return this for method chaining
|
||||
* @deprecated as of 0.22.0 in favor of {@link #set(String, Object...)}
|
||||
*/
|
||||
@Deprecated(since = "0.22.0", forRemoval = true)
|
||||
public Builder attribute(String name, Class<?> type, String... values) {
|
||||
this.attributes.put(name, determineAttributeFromUserType(name, type, values));
|
||||
return this;
|
||||
}
|
||||
|
||||
@Deprecated
|
||||
private Attribute determineAttributeFromUserType(String name, Class<?> type, String... values) {
|
||||
if (ClassUtils.isPrimitiveOrWrapper(type)) {
|
||||
return createAttribute(name, AttributeType.PRIMITIVE,
|
||||
Arrays.stream(values).map(Object.class::cast).toArray());
|
||||
}
|
||||
else if (CharSequence.class.isAssignableFrom(type)) {
|
||||
return createAttribute(name, AttributeType.STRING, values);
|
||||
}
|
||||
else if (Class.class.isAssignableFrom(type)) {
|
||||
Object[] mappedValues = Arrays.stream(values).map(ClassName::of).toArray(Object[]::new);
|
||||
return createAttribute(name, AttributeType.CLASS, mappedValues);
|
||||
}
|
||||
else if (Enum.class.isAssignableFrom(type)) {
|
||||
Object[] mappedValues = Arrays.stream(values).map((code) -> {
|
||||
int i = code.lastIndexOf('.');
|
||||
return CodeBlock.of("$T.$L", code.substring(0, i), code.substring(i + 1));
|
||||
}).toArray(Object[]::new);
|
||||
return createAttribute(name, AttributeType.ENUM, mappedValues);
|
||||
}
|
||||
else {
|
||||
Object[] mappedValues = Arrays.stream(values).map(CodeBlock::of).toArray(Object[]::new);
|
||||
return createAttribute(name, AttributeType.CODE, mappedValues);
|
||||
}
|
||||
}
|
||||
|
||||
private Attribute createAttribute(String name, AttributeType attributeType, Object[] values) {
|
||||
Arrays.stream(values).map(attributeType::getImports).forEach(this.imports::addAll);
|
||||
return new Attribute(name, attributeType, values);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the attribute with the specified name with the specified values. If the
|
||||
* attribute exists, it is replaced by the specified values.
|
||||
* @param name the name of the attribute
|
||||
* @param values the values to associate with the attribute
|
||||
* @return this for method chaining
|
||||
*/
|
||||
public Builder set(String name, Object... values) {
|
||||
AttributeType type = determineAttributeType(values);
|
||||
this.attributes.put(name, new Attribute(name, type, values));
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add the specified values to the attribute with the specified name. If the
|
||||
* attribute does not exist, it is created with the specified values.
|
||||
* @param name the name of the attribute
|
||||
* @param values the values to add to the attribute
|
||||
* @return this for method chaining
|
||||
*/
|
||||
public Builder add(String name, Object... values) {
|
||||
AttributeType type = determineAttributeType(values);
|
||||
this.attributes.merge(name, new Attribute(name, type, values), this::append);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reset the state of the builder to the state of the specified annotation. This
|
||||
* effectively replaces all customizations by the specified annotation.
|
||||
* @param annotation the annotation to reset to
|
||||
* @return this for method chaining
|
||||
*/
|
||||
public Builder from(Annotation annotation) {
|
||||
if (!this.className.equals(annotation.className)) {
|
||||
throw new IllegalArgumentException();
|
||||
}
|
||||
this.imports.clear();
|
||||
this.imports.addAll(annotation.imports);
|
||||
this.attributes.clear();
|
||||
annotation.attributes.forEach((attribute) -> this.attributes.put(attribute.getName(), attribute));
|
||||
return this;
|
||||
}
|
||||
|
||||
private Attribute append(Attribute existing, Attribute additional) {
|
||||
AttributeType typeToUse = AttributeType.getMostSpecificType(existing.type, additional.type);
|
||||
return new Attribute(existing.name, typeToUse,
|
||||
Stream.concat(existing.values.stream(), additional.values.stream()).toArray());
|
||||
}
|
||||
|
||||
private AttributeType determineAttributeType(Object... values) {
|
||||
AttributeType attributeType = AttributeType.of(values);
|
||||
Arrays.stream(values).map(attributeType::getImports).forEach(this.imports::addAll);
|
||||
return attributeType;
|
||||
}
|
||||
|
||||
public Annotation build() {
|
||||
return new Annotation(this);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
@ -88,11 +267,11 @@ public final class Annotation {
|
||||
|
||||
private final String name;
|
||||
|
||||
private final Class<?> type;
|
||||
private final AttributeType type;
|
||||
|
||||
private final List<String> values;
|
||||
private final List<Object> values;
|
||||
|
||||
private Attribute(String name, Class<?> type, String... values) {
|
||||
private Attribute(String name, AttributeType type, Object... values) {
|
||||
this.name = name;
|
||||
this.type = type;
|
||||
this.values = Arrays.asList(values);
|
||||
@ -102,14 +281,192 @@ public final class Annotation {
|
||||
return this.name;
|
||||
}
|
||||
|
||||
public Class<?> getType() {
|
||||
public AttributeType getType() {
|
||||
return this.type;
|
||||
}
|
||||
|
||||
public List<String> getValues() {
|
||||
public List<Object> getValues() {
|
||||
return this.values;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private enum AttributeType {
|
||||
|
||||
PRIMITIVE,
|
||||
|
||||
STRING,
|
||||
|
||||
CLASS() {
|
||||
@Override
|
||||
protected Collection<String> getImports(Object value) {
|
||||
if (value instanceof Class type) {
|
||||
return List.of(type.getName());
|
||||
}
|
||||
else if (value instanceof ClassName name) {
|
||||
return List.of(name.getName());
|
||||
}
|
||||
return super.getImports(value);
|
||||
}
|
||||
},
|
||||
|
||||
ENUM() {
|
||||
@Override
|
||||
protected Collection<String> getImports(Object value) {
|
||||
if (value instanceof Enum enumeration) {
|
||||
return List.of(enumeration.getClass().getName());
|
||||
}
|
||||
return super.getImports(value);
|
||||
}
|
||||
},
|
||||
|
||||
ANNOTATION() {
|
||||
@Override
|
||||
protected Collection<String> getImports(Object value) {
|
||||
if (value instanceof Annotation annotation) {
|
||||
return annotation.getImports();
|
||||
}
|
||||
return super.getImports(value);
|
||||
}
|
||||
},
|
||||
|
||||
CODE {
|
||||
@Override
|
||||
protected boolean isCompatible(AttributeType attributeType) {
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
protected boolean isCompatible(AttributeType attributeType) {
|
||||
return this.equals(attributeType) || attributeType == AttributeType.CODE;
|
||||
}
|
||||
|
||||
protected Collection<String> getImports(Object value) {
|
||||
if (value instanceof CodeBlock codeBlock) {
|
||||
return codeBlock.getImports();
|
||||
}
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
static AttributeType getMostSpecificType(AttributeType left, AttributeType right) {
|
||||
if (!left.isCompatible(right)) {
|
||||
throw new IllegalArgumentException(
|
||||
"Incompatible type. '%s' is not compatible with '%s'".formatted(left, right));
|
||||
}
|
||||
return (left == CODE) ? right : left;
|
||||
}
|
||||
|
||||
static AttributeType of(Object... values) {
|
||||
List<AttributeType> types = Arrays.stream(values)
|
||||
.map(AttributeType::determineAttributeType)
|
||||
.filter((type) -> type != CODE)
|
||||
.distinct()
|
||||
.toList();
|
||||
if (types.size() > 1) {
|
||||
throw new IllegalArgumentException("Parameter value must not have mixed types, got ["
|
||||
+ types.stream().map(AttributeType::name).collect(Collectors.joining(", ")) + "]");
|
||||
}
|
||||
return (types.size() == 1) ? types.get(0) : CODE;
|
||||
}
|
||||
|
||||
private static AttributeType determineAttributeType(Object value) {
|
||||
if (ClassUtils.isPrimitiveOrWrapper(value.getClass())) {
|
||||
return PRIMITIVE;
|
||||
}
|
||||
else if (value instanceof CharSequence) {
|
||||
return STRING;
|
||||
}
|
||||
else if (value instanceof Class<?> || value instanceof ClassName) {
|
||||
return CLASS;
|
||||
}
|
||||
else if (value instanceof Enum<?>) {
|
||||
return ENUM;
|
||||
}
|
||||
else if (value instanceof Annotation) {
|
||||
return ANNOTATION;
|
||||
}
|
||||
else if (value instanceof CodeBlock) {
|
||||
return CODE;
|
||||
}
|
||||
else {
|
||||
throw new IllegalArgumentException(
|
||||
"Incompatible type. Found: '%s', required: primitive, String, Class, an Enum, an Annotation, or a CodeBlock"
|
||||
.formatted(value.getClass().getName()));
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private static class AnnotationWriter {
|
||||
|
||||
private final IndentingWriter writer;
|
||||
|
||||
private final FormattingOptions formattingOptions;
|
||||
|
||||
AnnotationWriter(IndentingWriter writer, FormattingOptions formattingOptions) {
|
||||
this.writer = writer;
|
||||
this.formattingOptions = formattingOptions;
|
||||
}
|
||||
|
||||
void write(Annotation annotation) {
|
||||
generateAnnotationCode(annotation).write(this.writer, this.formattingOptions);
|
||||
}
|
||||
|
||||
private CodeBlock generateAnnotationCode(Annotation annotation) {
|
||||
CodeBlock.Builder code = CodeBlock.builder();
|
||||
code.add("@$T", annotation.className);
|
||||
if (annotation.attributes.size() == 1 && annotation.attributes.get(0).getName().equals("value")) {
|
||||
code.add("($L)", generateAttributeValuesCode(annotation.attributes.get(0)));
|
||||
}
|
||||
else if (!annotation.attributes.isEmpty()) {
|
||||
CodeBlock attributes = annotation.attributes.stream()
|
||||
.map(this::generateAttributeCode)
|
||||
.collect(CodeBlock.joining(", "));
|
||||
code.add("($L)", attributes);
|
||||
}
|
||||
return code.build();
|
||||
}
|
||||
|
||||
private CodeBlock generateAttributeCode(Attribute attribute) {
|
||||
return CodeBlock.of("$L = $L", attribute.name, generateAttributeValuesCode(attribute));
|
||||
}
|
||||
|
||||
private CodeBlock generateAttributeValuesCode(Attribute attribute) {
|
||||
CodeBlock[] values = attribute.values.stream()
|
||||
.map((value) -> generateValueCode(attribute.type, value))
|
||||
.toArray(CodeBlock[]::new);
|
||||
return (values.length == 1) ? values[0] : this.formattingOptions.arrayOf(values);
|
||||
}
|
||||
|
||||
private CodeBlock generateValueCode(AttributeType attributeType, Object value) {
|
||||
Class<?> valueType = ClassUtils.resolvePrimitiveIfNecessary(value.getClass());
|
||||
// CodeBlock can be anything
|
||||
if (value instanceof CodeBlock codeBlock) {
|
||||
return codeBlock;
|
||||
}
|
||||
return switch (attributeType) {
|
||||
case PRIMITIVE -> {
|
||||
if (valueType == Character.class) {
|
||||
yield CodeBlock.of("'$L'", value);
|
||||
}
|
||||
else {
|
||||
yield CodeBlock.of("$L", value);
|
||||
}
|
||||
}
|
||||
case STRING -> CodeBlock.of("$S", value);
|
||||
case CLASS -> {
|
||||
ClassName className = (value instanceof Class clazz) ? ClassName.of(clazz) : (ClassName) value;
|
||||
yield this.formattingOptions.classReference(className);
|
||||
}
|
||||
case ENUM -> {
|
||||
Enum<?> enumValue = (Enum<?>) value;
|
||||
yield CodeBlock.of("$T.$L", enumValue.getClass(), enumValue.name());
|
||||
}
|
||||
case ANNOTATION -> generateAnnotationCode((Annotation) value);
|
||||
case CODE -> (CodeBlock) value;
|
||||
};
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -0,0 +1,106 @@
|
||||
/*
|
||||
* Copyright 2012-2023 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package io.spring.initializr.generator.language;
|
||||
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Map;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import io.spring.initializr.generator.language.Annotation.Builder;
|
||||
|
||||
/**
|
||||
* A container for {@linkplain Annotation annotations} defined on an annotated element.
|
||||
*
|
||||
* @author Stephane Nicoll
|
||||
*/
|
||||
public class AnnotationContainer {
|
||||
|
||||
private final Map<ClassName, Builder> annotations;
|
||||
|
||||
public AnnotationContainer() {
|
||||
this(new LinkedHashMap<>());
|
||||
}
|
||||
|
||||
private AnnotationContainer(Map<ClassName, Builder> annotations) {
|
||||
this.annotations = annotations;
|
||||
}
|
||||
|
||||
/**
|
||||
* Specify if this container is empty.
|
||||
* @return {@code true} if no annotation is registered
|
||||
*/
|
||||
public boolean isEmpty() {
|
||||
return this.annotations.isEmpty();
|
||||
}
|
||||
|
||||
/**
|
||||
* Specify if this container has a an annotation with the specified {@link ClassName}.
|
||||
* @param className the class name of an annotation
|
||||
* @return {@code true} if the annotation with the specified class name exists
|
||||
*/
|
||||
public boolean has(ClassName className) {
|
||||
return this.annotations.containsKey(className);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the {@link Annotation annotations}.
|
||||
* @return the annotations
|
||||
*/
|
||||
public Stream<Annotation> values() {
|
||||
return this.annotations.values().stream().map(Builder::build);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a single {@link Annotation} with the specified class name and {@link Consumer}
|
||||
* to customize it. If the annotation has already been added, the consumer can be used
|
||||
* to further tune attributes
|
||||
* @param className the class name of an annotation
|
||||
* @param annotation a {@link Consumer} to customize the {@link Annotation}
|
||||
*/
|
||||
public void add(ClassName className, Consumer<Builder> annotation) {
|
||||
Builder builder = this.annotations.computeIfAbsent(className, (key) -> new Builder(className));
|
||||
if (annotation != null) {
|
||||
annotation.accept(builder);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a single {@link Annotation} with the specified class name. Does nothing If the
|
||||
* annotation has already been added.
|
||||
* @param className the class name of an annotation
|
||||
*/
|
||||
public void add(ClassName className) {
|
||||
add(className, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove the annotation with the specified {@link ClassName}.
|
||||
* @param className the class name of the annotation
|
||||
* @return {@code true} if such an annotation exists, {@code false} otherwise
|
||||
*/
|
||||
public boolean remove(ClassName className) {
|
||||
return this.annotations.remove(className) != null;
|
||||
}
|
||||
|
||||
public AnnotationContainer deepCopy() {
|
||||
Map<ClassName, Builder> copy = new LinkedHashMap<>();
|
||||
this.annotations.forEach((className, builder) -> copy.put(className, new Builder(builder)));
|
||||
return new AnnotationContainer(copy);
|
||||
}
|
||||
|
||||
}
|
@ -17,7 +17,10 @@
|
||||
package io.spring.initializr.generator.language;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collector;
|
||||
import java.util.stream.StreamSupport;
|
||||
|
||||
import io.spring.initializr.generator.io.IndentingWriter;
|
||||
|
||||
@ -155,6 +158,31 @@ public final class CodeBlock {
|
||||
return new Builder().addStatement(format, args).build();
|
||||
}
|
||||
|
||||
/**
|
||||
* Joins {@code codeBlocks} into a single {@link CodeBlock}, each separated by
|
||||
* {@code separator}. For example, joining {@code String s}, {@code Object o} and
|
||||
* {@code int i} using {@code ", "} would produce {@code String s, Object o, int i}.
|
||||
* @param codeBlocks the code blocks to join
|
||||
* @param separator the separator to use
|
||||
* @return a code block joining the specified code blocks
|
||||
*/
|
||||
public static CodeBlock join(Iterable<CodeBlock> codeBlocks, String separator) {
|
||||
return StreamSupport.stream(codeBlocks.spliterator(), false).collect(joining(separator));
|
||||
}
|
||||
|
||||
/**
|
||||
* A {@link Collector} implementation that joins {@link CodeBlock} instances together
|
||||
* into one separated by {@code separator}. For example, joining {@code String s},
|
||||
* {@code Object o} and {@code int i} using {@code ", "} would produce
|
||||
* {@code String s, Object o, int i}.
|
||||
* @param separator the separator to use
|
||||
* @return a collector using the specified separator
|
||||
*/
|
||||
public static Collector<CodeBlock, ?, CodeBlock> joining(String separator) {
|
||||
return Collector.of(() -> new CodeBlockJoiner(separator, builder()), CodeBlockJoiner::add,
|
||||
CodeBlockJoiner::merge, CodeBlockJoiner::join);
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize a new builder.
|
||||
* @return a code block builder
|
||||
@ -309,6 +337,22 @@ public final class CodeBlock {
|
||||
*/
|
||||
String statementSeparator();
|
||||
|
||||
/**
|
||||
* Return the code that represents an array for the specified values.
|
||||
* @param values the values of the array
|
||||
* @return an array defining the specified values
|
||||
*/
|
||||
CodeBlock arrayOf(CodeBlock... values);
|
||||
|
||||
/**
|
||||
* Return the code that represents a reference to the specified {@link ClassName}.
|
||||
* For instance with java, a reference to {@code com.example.Test} would be
|
||||
* {@code Test.class}.
|
||||
* @param className the class name to handle
|
||||
* @return a class reference to the specified class name
|
||||
*/
|
||||
CodeBlock classReference(ClassName className);
|
||||
|
||||
}
|
||||
|
||||
private static class JavaFormattingOptions implements FormattingOptions {
|
||||
@ -318,6 +362,53 @@ public final class CodeBlock {
|
||||
return ";";
|
||||
}
|
||||
|
||||
@Override
|
||||
public CodeBlock arrayOf(CodeBlock... values) {
|
||||
return CodeBlock.of("{ $L }", CodeBlock.join(Arrays.asList(values), ", "));
|
||||
}
|
||||
|
||||
@Override
|
||||
public CodeBlock classReference(ClassName className) {
|
||||
return CodeBlock.of("$T.class", className);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private static final class CodeBlockJoiner {
|
||||
|
||||
private final String delimiter;
|
||||
|
||||
private final CodeBlock.Builder builder;
|
||||
|
||||
private boolean first = true;
|
||||
|
||||
CodeBlockJoiner(String delimiter, CodeBlock.Builder builder) {
|
||||
this.delimiter = delimiter;
|
||||
this.builder = builder;
|
||||
}
|
||||
|
||||
CodeBlockJoiner add(CodeBlock codeBlock) {
|
||||
if (!this.first) {
|
||||
this.builder.add(this.delimiter);
|
||||
}
|
||||
this.first = false;
|
||||
|
||||
this.builder.add(codeBlock);
|
||||
return this;
|
||||
}
|
||||
|
||||
CodeBlock.CodeBlockJoiner merge(CodeBlockJoiner other) {
|
||||
CodeBlock otherBlock = other.builder.build();
|
||||
if (!otherBlock.parts.isEmpty()) {
|
||||
add(otherBlock);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
CodeBlock join() {
|
||||
return this.builder.build();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2012-2019 the original author or authors.
|
||||
* Copyright 2012-2023 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@ -16,10 +16,6 @@
|
||||
|
||||
package io.spring.initializr.generator.language;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* A type declared in a {@link CompilationUnit}.
|
||||
*
|
||||
@ -27,7 +23,7 @@ import java.util.List;
|
||||
*/
|
||||
public class TypeDeclaration implements Annotatable {
|
||||
|
||||
private final List<Annotation> annotations = new ArrayList<>();
|
||||
private final AnnotationContainer annotations = new AnnotationContainer();
|
||||
|
||||
private final String name;
|
||||
|
||||
@ -42,13 +38,8 @@ public class TypeDeclaration implements Annotatable {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void annotate(Annotation annotation) {
|
||||
this.annotations.add(annotation);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Annotation> getAnnotations() {
|
||||
return Collections.unmodifiableList(this.annotations);
|
||||
public AnnotationContainer annotations() {
|
||||
return this.annotations;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2012-2019 the original author or authors.
|
||||
* Copyright 2012-2023 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@ -16,12 +16,8 @@
|
||||
|
||||
package io.spring.initializr.generator.language.groovy;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
import io.spring.initializr.generator.language.Annotatable;
|
||||
import io.spring.initializr.generator.language.Annotation;
|
||||
import io.spring.initializr.generator.language.AnnotationContainer;
|
||||
|
||||
/**
|
||||
* Declaration of a field written in Groovy.
|
||||
@ -30,7 +26,7 @@ import io.spring.initializr.generator.language.Annotation;
|
||||
*/
|
||||
public final class GroovyFieldDeclaration implements Annotatable {
|
||||
|
||||
private final List<Annotation> annotations = new ArrayList<>();
|
||||
private final AnnotationContainer annotations = new AnnotationContainer();
|
||||
|
||||
private final int modifiers;
|
||||
|
||||
@ -55,13 +51,8 @@ public final class GroovyFieldDeclaration implements Annotatable {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void annotate(Annotation annotation) {
|
||||
this.annotations.add(annotation);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Annotation> getAnnotations() {
|
||||
return Collections.unmodifiableList(this.annotations);
|
||||
public AnnotationContainer annotations() {
|
||||
return this.annotations;
|
||||
}
|
||||
|
||||
public int getModifiers() {
|
||||
|
@ -23,7 +23,7 @@ import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
import io.spring.initializr.generator.language.Annotatable;
|
||||
import io.spring.initializr.generator.language.Annotation;
|
||||
import io.spring.initializr.generator.language.AnnotationContainer;
|
||||
import io.spring.initializr.generator.language.CodeBlock;
|
||||
import io.spring.initializr.generator.language.Parameter;
|
||||
|
||||
@ -34,7 +34,7 @@ import io.spring.initializr.generator.language.Parameter;
|
||||
*/
|
||||
public final class GroovyMethodDeclaration implements Annotatable {
|
||||
|
||||
private final List<Annotation> annotations = new ArrayList<>();
|
||||
private final AnnotationContainer annotations = new AnnotationContainer();
|
||||
|
||||
private final String name;
|
||||
|
||||
@ -96,13 +96,8 @@ public final class GroovyMethodDeclaration implements Annotatable {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void annotate(Annotation annotation) {
|
||||
this.annotations.add(annotation);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Annotation> getAnnotations() {
|
||||
return Collections.unmodifiableList(this.annotations);
|
||||
public AnnotationContainer annotations() {
|
||||
return this.annotations;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -21,6 +21,7 @@ import java.lang.reflect.Modifier;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.LinkedHashMap;
|
||||
@ -38,6 +39,8 @@ import io.spring.initializr.generator.io.IndentingWriter;
|
||||
import io.spring.initializr.generator.io.IndentingWriterFactory;
|
||||
import io.spring.initializr.generator.language.Annotatable;
|
||||
import io.spring.initializr.generator.language.Annotation;
|
||||
import io.spring.initializr.generator.language.ClassName;
|
||||
import io.spring.initializr.generator.language.CodeBlock;
|
||||
import io.spring.initializr.generator.language.CodeBlock.FormattingOptions;
|
||||
import io.spring.initializr.generator.language.CompilationUnit;
|
||||
import io.spring.initializr.generator.language.Parameter;
|
||||
@ -142,48 +145,10 @@ public class GroovySourceCodeWriter implements SourceCodeWriter<GroovySourceCode
|
||||
}
|
||||
|
||||
private void writeAnnotations(IndentingWriter writer, Annotatable annotatable) {
|
||||
annotatable.getAnnotations().forEach((annotation) -> writeAnnotation(writer, annotation));
|
||||
}
|
||||
|
||||
private void writeAnnotation(IndentingWriter writer, Annotation annotation) {
|
||||
writer.print("@" + getUnqualifiedName(annotation.getName()));
|
||||
List<Annotation.Attribute> attributes = annotation.getAttributes();
|
||||
if (!attributes.isEmpty()) {
|
||||
writer.print("(");
|
||||
if (attributes.size() == 1 && attributes.get(0).getName().equals("value")) {
|
||||
writer.print(formatAnnotationAttribute(attributes.get(0)));
|
||||
}
|
||||
else {
|
||||
writer.print(attributes.stream()
|
||||
.map((attribute) -> attribute.getName() + " = " + formatAnnotationAttribute(attribute))
|
||||
.collect(Collectors.joining(", ")));
|
||||
}
|
||||
writer.print(")");
|
||||
}
|
||||
writer.println();
|
||||
}
|
||||
|
||||
private String formatAnnotationAttribute(Annotation.Attribute attribute) {
|
||||
List<String> values = attribute.getValues();
|
||||
if (attribute.getType().equals(Class.class)) {
|
||||
return formatValues(values, this::getUnqualifiedName);
|
||||
}
|
||||
if (Enum.class.isAssignableFrom(attribute.getType())) {
|
||||
return formatValues(values, (value) -> {
|
||||
String enumValue = value.substring(value.lastIndexOf(".") + 1);
|
||||
String enumClass = value.substring(0, value.lastIndexOf("."));
|
||||
return String.format("%s.%s", getUnqualifiedName(enumClass), enumValue);
|
||||
});
|
||||
}
|
||||
if (attribute.getType().equals(String.class)) {
|
||||
return formatValues(values, (value) -> String.format("\"%s\"", value));
|
||||
}
|
||||
return formatValues(values, (value) -> String.format("%s", value));
|
||||
}
|
||||
|
||||
private String formatValues(List<String> values, Function<String, String> formatter) {
|
||||
String result = values.stream().map(formatter).collect(Collectors.joining(", "));
|
||||
return (values.size() > 1) ? "[ " + result + " ]" : result;
|
||||
annotatable.annotations().values().forEach((annotation) -> {
|
||||
annotation.write(writer, FORMATTING_OPTIONS);
|
||||
writer.println();
|
||||
});
|
||||
}
|
||||
|
||||
private void writeFieldDeclaration(IndentingWriter writer, GroovyFieldDeclaration fieldDeclaration) {
|
||||
@ -261,14 +226,14 @@ public class GroovySourceCodeWriter implements SourceCodeWriter<GroovySourceCode
|
||||
List<String> imports = new ArrayList<>();
|
||||
for (GroovyTypeDeclaration typeDeclaration : compilationUnit.getTypeDeclarations()) {
|
||||
imports.add(typeDeclaration.getExtends());
|
||||
imports.addAll(appendImports(typeDeclaration.getAnnotations(), this::determineImports));
|
||||
imports.addAll(appendImports(typeDeclaration.annotations().values(), Annotation::getImports));
|
||||
for (GroovyFieldDeclaration fieldDeclaration : typeDeclaration.getFieldDeclarations()) {
|
||||
imports.add(fieldDeclaration.getReturnType());
|
||||
imports.addAll(appendImports(fieldDeclaration.getAnnotations(), this::determineImports));
|
||||
imports.addAll(appendImports(fieldDeclaration.annotations().values(), Annotation::getImports));
|
||||
}
|
||||
for (GroovyMethodDeclaration methodDeclaration : typeDeclaration.getMethodDeclarations()) {
|
||||
imports.add(methodDeclaration.getReturnType());
|
||||
imports.addAll(appendImports(methodDeclaration.getAnnotations(), this::determineImports));
|
||||
imports.addAll(appendImports(methodDeclaration.annotations().values(), Annotation::getImports));
|
||||
imports.addAll(appendImports(methodDeclaration.getParameters(),
|
||||
(parameter) -> Collections.singletonList(parameter.getType())));
|
||||
imports.addAll(methodDeclaration.getCode().getImports());
|
||||
@ -294,23 +259,6 @@ public class GroovySourceCodeWriter implements SourceCodeWriter<GroovySourceCode
|
||||
(methodInvocation) -> Collections.singleton(methodInvocation.getTarget())));
|
||||
}
|
||||
|
||||
private Collection<String> determineImports(Annotation annotation) {
|
||||
List<String> imports = new ArrayList<>();
|
||||
imports.add(annotation.getName());
|
||||
annotation.getAttributes().forEach((attribute) -> {
|
||||
if (attribute.getType() == Class.class) {
|
||||
imports.addAll(attribute.getValues());
|
||||
}
|
||||
if (Enum.class.isAssignableFrom(attribute.getType())) {
|
||||
imports.addAll(attribute.getValues()
|
||||
.stream()
|
||||
.map((value) -> value.substring(0, value.lastIndexOf(".")))
|
||||
.toList());
|
||||
}
|
||||
});
|
||||
return imports;
|
||||
}
|
||||
|
||||
private <T> List<String> appendImports(List<T> candidates, Function<T, Collection<String>> mapping) {
|
||||
return appendImports(candidates.stream(), mapping);
|
||||
}
|
||||
@ -341,6 +289,16 @@ public class GroovySourceCodeWriter implements SourceCodeWriter<GroovySourceCode
|
||||
return "";
|
||||
}
|
||||
|
||||
@Override
|
||||
public CodeBlock arrayOf(CodeBlock... values) {
|
||||
return CodeBlock.of("[ $L ]", CodeBlock.join(Arrays.asList(values), ", "));
|
||||
}
|
||||
|
||||
@Override
|
||||
public CodeBlock classReference(ClassName className) {
|
||||
return CodeBlock.of("$T", className);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2012-2019 the original author or authors.
|
||||
* Copyright 2012-2023 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@ -16,12 +16,8 @@
|
||||
|
||||
package io.spring.initializr.generator.language.java;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
import io.spring.initializr.generator.language.Annotatable;
|
||||
import io.spring.initializr.generator.language.Annotation;
|
||||
import io.spring.initializr.generator.language.AnnotationContainer;
|
||||
|
||||
/**
|
||||
* Declaration of a field written in Java.
|
||||
@ -30,7 +26,7 @@ import io.spring.initializr.generator.language.Annotation;
|
||||
*/
|
||||
public final class JavaFieldDeclaration implements Annotatable {
|
||||
|
||||
private final List<Annotation> annotations = new ArrayList<>();
|
||||
private final AnnotationContainer annotations = new AnnotationContainer();
|
||||
|
||||
private final int modifiers;
|
||||
|
||||
@ -55,13 +51,8 @@ public final class JavaFieldDeclaration implements Annotatable {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void annotate(Annotation annotation) {
|
||||
this.annotations.add(annotation);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Annotation> getAnnotations() {
|
||||
return Collections.unmodifiableList(this.annotations);
|
||||
public AnnotationContainer annotations() {
|
||||
return this.annotations;
|
||||
}
|
||||
|
||||
public int getModifiers() {
|
||||
|
@ -22,7 +22,7 @@ import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
import io.spring.initializr.generator.language.Annotatable;
|
||||
import io.spring.initializr.generator.language.Annotation;
|
||||
import io.spring.initializr.generator.language.AnnotationContainer;
|
||||
import io.spring.initializr.generator.language.CodeBlock;
|
||||
import io.spring.initializr.generator.language.Parameter;
|
||||
|
||||
@ -33,7 +33,7 @@ import io.spring.initializr.generator.language.Parameter;
|
||||
*/
|
||||
public final class JavaMethodDeclaration implements Annotatable {
|
||||
|
||||
private final List<Annotation> annotations = new ArrayList<>();
|
||||
private final AnnotationContainer annotations = new AnnotationContainer();
|
||||
|
||||
private final String name;
|
||||
|
||||
@ -96,13 +96,8 @@ public final class JavaMethodDeclaration implements Annotatable {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void annotate(Annotation annotation) {
|
||||
this.annotations.add(annotation);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Annotation> getAnnotations() {
|
||||
return Collections.unmodifiableList(this.annotations);
|
||||
public AnnotationContainer annotations() {
|
||||
return this.annotations;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -142,48 +142,10 @@ public class JavaSourceCodeWriter implements SourceCodeWriter<JavaSourceCode> {
|
||||
}
|
||||
|
||||
private void writeAnnotations(IndentingWriter writer, Annotatable annotatable) {
|
||||
annotatable.getAnnotations().forEach((annotation) -> writeAnnotation(writer, annotation));
|
||||
}
|
||||
|
||||
private void writeAnnotation(IndentingWriter writer, Annotation annotation) {
|
||||
writer.print("@" + getUnqualifiedName(annotation.getName()));
|
||||
List<Annotation.Attribute> attributes = annotation.getAttributes();
|
||||
if (!attributes.isEmpty()) {
|
||||
writer.print("(");
|
||||
if (attributes.size() == 1 && attributes.get(0).getName().equals("value")) {
|
||||
writer.print(formatAnnotationAttribute(attributes.get(0)));
|
||||
}
|
||||
else {
|
||||
writer.print(attributes.stream()
|
||||
.map((attribute) -> attribute.getName() + " = " + formatAnnotationAttribute(attribute))
|
||||
.collect(Collectors.joining(", ")));
|
||||
}
|
||||
writer.print(")");
|
||||
}
|
||||
writer.println();
|
||||
}
|
||||
|
||||
private String formatAnnotationAttribute(Annotation.Attribute attribute) {
|
||||
List<String> values = attribute.getValues();
|
||||
if (attribute.getType().equals(Class.class)) {
|
||||
return formatValues(values, (value) -> String.format("%s.class", getUnqualifiedName(value)));
|
||||
}
|
||||
if (Enum.class.isAssignableFrom(attribute.getType())) {
|
||||
return formatValues(values, (value) -> {
|
||||
String enumValue = value.substring(value.lastIndexOf(".") + 1);
|
||||
String enumClass = value.substring(0, value.lastIndexOf("."));
|
||||
return String.format("%s.%s", getUnqualifiedName(enumClass), enumValue);
|
||||
});
|
||||
}
|
||||
if (attribute.getType().equals(String.class)) {
|
||||
return formatValues(values, (value) -> String.format("\"%s\"", value));
|
||||
}
|
||||
return formatValues(values, (value) -> String.format("%s", value));
|
||||
}
|
||||
|
||||
private String formatValues(List<String> values, Function<String, String> formatter) {
|
||||
String result = values.stream().map(formatter).collect(Collectors.joining(", "));
|
||||
return (values.size() > 1) ? "{ " + result + " }" : result;
|
||||
annotatable.annotations().values().forEach((annotation) -> {
|
||||
annotation.write(writer, CodeBlock.JAVA_FORMATTING_OPTIONS);
|
||||
writer.println();
|
||||
});
|
||||
}
|
||||
|
||||
private void writeFieldDeclaration(IndentingWriter writer, JavaFieldDeclaration fieldDeclaration) {
|
||||
@ -263,14 +225,14 @@ public class JavaSourceCodeWriter implements SourceCodeWriter<JavaSourceCode> {
|
||||
for (JavaTypeDeclaration typeDeclaration : compilationUnit.getTypeDeclarations()) {
|
||||
imports.add(typeDeclaration.getExtends());
|
||||
|
||||
imports.addAll(appendImports(typeDeclaration.getAnnotations(), this::determineImports));
|
||||
imports.addAll(appendImports(typeDeclaration.annotations().values(), Annotation::getImports));
|
||||
for (JavaFieldDeclaration fieldDeclaration : typeDeclaration.getFieldDeclarations()) {
|
||||
imports.add(fieldDeclaration.getReturnType());
|
||||
imports.addAll(appendImports(fieldDeclaration.getAnnotations(), this::determineImports));
|
||||
imports.addAll(appendImports(fieldDeclaration.annotations().values(), Annotation::getImports));
|
||||
}
|
||||
for (JavaMethodDeclaration methodDeclaration : typeDeclaration.getMethodDeclarations()) {
|
||||
imports.add(methodDeclaration.getReturnType());
|
||||
imports.addAll(appendImports(methodDeclaration.getAnnotations(), this::determineImports));
|
||||
imports.addAll(appendImports(methodDeclaration.annotations().values(), Annotation::getImports));
|
||||
imports.addAll(appendImports(methodDeclaration.getParameters(),
|
||||
(parameter) -> Collections.singletonList(parameter.getType())));
|
||||
determineImportsFromStatements(imports, methodDeclaration);
|
||||
@ -296,23 +258,6 @@ public class JavaSourceCodeWriter implements SourceCodeWriter<JavaSourceCode> {
|
||||
(methodInvocation) -> Collections.singleton(methodInvocation.getTarget())));
|
||||
}
|
||||
|
||||
private Collection<String> determineImports(Annotation annotation) {
|
||||
List<String> imports = new ArrayList<>();
|
||||
imports.add(annotation.getName());
|
||||
annotation.getAttributes().forEach((attribute) -> {
|
||||
if (attribute.getType() == Class.class) {
|
||||
imports.addAll(attribute.getValues());
|
||||
}
|
||||
if (Enum.class.isAssignableFrom(attribute.getType())) {
|
||||
imports.addAll(attribute.getValues()
|
||||
.stream()
|
||||
.map((value) -> value.substring(0, value.lastIndexOf(".")))
|
||||
.toList());
|
||||
}
|
||||
});
|
||||
return imports;
|
||||
}
|
||||
|
||||
private <T> List<String> appendImports(List<T> candidates, Function<T, Collection<String>> mapping) {
|
||||
return appendImports(candidates.stream(), mapping);
|
||||
}
|
||||
|
@ -22,7 +22,7 @@ import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
import io.spring.initializr.generator.language.Annotatable;
|
||||
import io.spring.initializr.generator.language.Annotation;
|
||||
import io.spring.initializr.generator.language.AnnotationContainer;
|
||||
import io.spring.initializr.generator.language.CodeBlock;
|
||||
import io.spring.initializr.generator.language.Parameter;
|
||||
|
||||
@ -33,7 +33,7 @@ import io.spring.initializr.generator.language.Parameter;
|
||||
*/
|
||||
public final class KotlinFunctionDeclaration implements Annotatable {
|
||||
|
||||
private final List<Annotation> annotations = new ArrayList<>();
|
||||
private final AnnotationContainer annotations = new AnnotationContainer();
|
||||
|
||||
private final String name;
|
||||
|
||||
@ -95,13 +95,8 @@ public final class KotlinFunctionDeclaration implements Annotatable {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void annotate(Annotation annotation) {
|
||||
this.annotations.add(annotation);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Annotation> getAnnotations() {
|
||||
return Collections.unmodifiableList(this.annotations);
|
||||
public AnnotationContainer annotations() {
|
||||
return this.annotations;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -18,12 +18,13 @@ package io.spring.initializr.generator.language.kotlin;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
import io.spring.initializr.generator.language.Annotatable;
|
||||
import io.spring.initializr.generator.language.Annotation;
|
||||
import io.spring.initializr.generator.language.AnnotationContainer;
|
||||
import io.spring.initializr.generator.language.ClassName;
|
||||
import io.spring.initializr.generator.language.CodeBlock;
|
||||
|
||||
/**
|
||||
@ -33,7 +34,7 @@ import io.spring.initializr.generator.language.CodeBlock;
|
||||
*/
|
||||
public final class KotlinPropertyDeclaration implements Annotatable {
|
||||
|
||||
private final List<Annotation> annotations = new ArrayList<>();
|
||||
private final AnnotationContainer annotations = new AnnotationContainer();
|
||||
|
||||
private final boolean isVal;
|
||||
|
||||
@ -96,13 +97,8 @@ public final class KotlinPropertyDeclaration implements Annotatable {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void annotate(Annotation annotation) {
|
||||
this.annotations.add(annotation);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Annotation> getAnnotations() {
|
||||
return Collections.unmodifiableList(this.annotations);
|
||||
public AnnotationContainer annotations() {
|
||||
return this.annotations;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -196,7 +192,7 @@ public final class KotlinPropertyDeclaration implements Annotatable {
|
||||
|
||||
public static final class AccessorBuilder<T extends Builder<T>> {
|
||||
|
||||
private final List<Annotation> annotations = new ArrayList<>();
|
||||
private final AnnotationContainer annotations = new AnnotationContainer();
|
||||
|
||||
private CodeBlock code;
|
||||
|
||||
@ -211,8 +207,18 @@ public final class KotlinPropertyDeclaration implements Annotatable {
|
||||
this.accessorFunction = accessorFunction;
|
||||
}
|
||||
|
||||
@Deprecated(since = "0.20.0", forRemoval = true)
|
||||
public AccessorBuilder<?> withAnnotation(Annotation annotation) {
|
||||
this.annotations.add(annotation);
|
||||
this.annotations.add(annotation.getClassName(), (builder) -> builder.from(annotation));
|
||||
return this;
|
||||
}
|
||||
|
||||
public AccessorBuilder<?> withAnnotation(ClassName className) {
|
||||
return withAnnotation(className, null);
|
||||
}
|
||||
|
||||
public AccessorBuilder<?> withAnnotation(ClassName className, Consumer<Annotation.Builder> annotation) {
|
||||
this.annotations.add(className, annotation);
|
||||
return this;
|
||||
}
|
||||
|
||||
@ -236,14 +242,14 @@ public final class KotlinPropertyDeclaration implements Annotatable {
|
||||
|
||||
static final class Accessor implements Annotatable {
|
||||
|
||||
private final List<Annotation> annotations = new ArrayList<>();
|
||||
private final AnnotationContainer annotations;
|
||||
|
||||
private final CodeBlock code;
|
||||
|
||||
private final KotlinExpressionStatement body;
|
||||
|
||||
Accessor(AccessorBuilder<?> builder) {
|
||||
this.annotations.addAll(builder.annotations);
|
||||
this.annotations = builder.annotations.deepCopy();
|
||||
this.code = builder.code;
|
||||
this.body = builder.body;
|
||||
}
|
||||
@ -262,13 +268,8 @@ public final class KotlinPropertyDeclaration implements Annotatable {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void annotate(Annotation annotation) {
|
||||
this.annotations.add(annotation);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Annotation> getAnnotations() {
|
||||
return Collections.unmodifiableList(this.annotations);
|
||||
public AnnotationContainer annotations() {
|
||||
return this.annotations;
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -20,6 +20,7 @@ import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.LinkedHashSet;
|
||||
@ -34,6 +35,8 @@ import io.spring.initializr.generator.io.IndentingWriter;
|
||||
import io.spring.initializr.generator.io.IndentingWriterFactory;
|
||||
import io.spring.initializr.generator.language.Annotatable;
|
||||
import io.spring.initializr.generator.language.Annotation;
|
||||
import io.spring.initializr.generator.language.ClassName;
|
||||
import io.spring.initializr.generator.language.CodeBlock;
|
||||
import io.spring.initializr.generator.language.CodeBlock.FormattingOptions;
|
||||
import io.spring.initializr.generator.language.CompilationUnit;
|
||||
import io.spring.initializr.generator.language.Parameter;
|
||||
@ -153,10 +156,8 @@ public class KotlinSourceCodeWriter implements SourceCodeWriter<KotlinSourceCode
|
||||
|
||||
private void writeAccessor(IndentingWriter writer, String accessorName,
|
||||
KotlinPropertyDeclaration.Accessor accessor) {
|
||||
if (!accessor.getAnnotations().isEmpty()) {
|
||||
for (Annotation annotation : accessor.getAnnotations()) {
|
||||
writeAnnotation(writer, annotation, false);
|
||||
}
|
||||
if (!accessor.annotations().isEmpty()) {
|
||||
accessor.annotations().values().forEach((annotation) -> writeAnnotation(writer, annotation, false));
|
||||
}
|
||||
writer.print(accessorName);
|
||||
if (!accessor.isEmptyBody()) {
|
||||
@ -210,26 +211,11 @@ public class KotlinSourceCodeWriter implements SourceCodeWriter<KotlinSourceCode
|
||||
}
|
||||
|
||||
private void writeAnnotations(IndentingWriter writer, Annotatable annotatable) {
|
||||
for (Annotation annotation : annotatable.getAnnotations()) {
|
||||
writeAnnotation(writer, annotation, true);
|
||||
}
|
||||
annotatable.annotations().values().forEach((annotation) -> writeAnnotation(writer, annotation, true));
|
||||
}
|
||||
|
||||
private void writeAnnotation(IndentingWriter writer, Annotation annotation, boolean newLine) {
|
||||
writer.print("@" + getUnqualifiedName(annotation.getName()));
|
||||
List<Annotation.Attribute> attributes = annotation.getAttributes();
|
||||
if (!attributes.isEmpty()) {
|
||||
writer.print("(");
|
||||
if (attributes.size() == 1 && attributes.get(0).getName().equals("value")) {
|
||||
writer.print(formatAnnotationAttribute(attributes.get(0)));
|
||||
}
|
||||
else {
|
||||
writer.print(attributes.stream()
|
||||
.map((attribute) -> attribute.getName() + " = " + formatAnnotationAttribute(attribute))
|
||||
.collect(Collectors.joining(", ")));
|
||||
}
|
||||
writer.print(")");
|
||||
}
|
||||
annotation.write(writer, FORMATTING_OPTIONS);
|
||||
if (newLine) {
|
||||
writer.println();
|
||||
}
|
||||
@ -238,29 +224,6 @@ public class KotlinSourceCodeWriter implements SourceCodeWriter<KotlinSourceCode
|
||||
}
|
||||
}
|
||||
|
||||
private String formatAnnotationAttribute(Annotation.Attribute attribute) {
|
||||
List<String> values = attribute.getValues();
|
||||
if (attribute.getType().equals(Class.class)) {
|
||||
return formatValues(values, (value) -> String.format("%s::class", getUnqualifiedName(value)));
|
||||
}
|
||||
if (Enum.class.isAssignableFrom(attribute.getType())) {
|
||||
return formatValues(values, (value) -> {
|
||||
String enumValue = value.substring(value.lastIndexOf(".") + 1);
|
||||
String enumClass = value.substring(0, value.lastIndexOf("."));
|
||||
return String.format("%s.%s", getUnqualifiedName(enumClass), enumValue);
|
||||
});
|
||||
}
|
||||
if (attribute.getType().equals(String.class)) {
|
||||
return formatValues(values, (value) -> String.format("\"%s\"", value));
|
||||
}
|
||||
return formatValues(values, (value) -> String.format("%s", value));
|
||||
}
|
||||
|
||||
private String formatValues(List<String> values, Function<String, String> formatter) {
|
||||
String result = values.stream().map(formatter).collect(Collectors.joining(", "));
|
||||
return (values.size() > 1) ? "[" + result + "]" : result;
|
||||
}
|
||||
|
||||
private void writeModifiers(IndentingWriter writer, List<KotlinModifier> declaredModifiers) {
|
||||
String modifiers = declaredModifiers.stream()
|
||||
.filter((entry) -> !entry.equals(KotlinModifier.PUBLIC))
|
||||
@ -301,7 +264,7 @@ public class KotlinSourceCodeWriter implements SourceCodeWriter<KotlinSourceCode
|
||||
List<String> imports = new ArrayList<>();
|
||||
for (KotlinTypeDeclaration typeDeclaration : compilationUnit.getTypeDeclarations()) {
|
||||
imports.add(typeDeclaration.getExtends());
|
||||
imports.addAll(appendImports(typeDeclaration.getAnnotations(), this::determineImports));
|
||||
imports.addAll(appendImports(typeDeclaration.annotations().values(), Annotation::getImports));
|
||||
typeDeclaration.getPropertyDeclarations()
|
||||
.forEach(((propertyDeclaration) -> imports.addAll(determinePropertyImports(propertyDeclaration))));
|
||||
typeDeclaration.getFunctionDeclarations()
|
||||
@ -323,7 +286,7 @@ public class KotlinSourceCodeWriter implements SourceCodeWriter<KotlinSourceCode
|
||||
private Set<String> determineFunctionImports(KotlinFunctionDeclaration functionDeclaration) {
|
||||
Set<String> imports = new LinkedHashSet<>();
|
||||
imports.add(functionDeclaration.getReturnType());
|
||||
imports.addAll(appendImports(functionDeclaration.getAnnotations(), this::determineImports));
|
||||
imports.addAll(appendImports(functionDeclaration.annotations().values(), Annotation::getImports));
|
||||
imports.addAll(appendImports(functionDeclaration.getParameters(),
|
||||
(parameter) -> Collections.singleton(parameter.getType())));
|
||||
imports.addAll(functionDeclaration.getCode().getImports());
|
||||
@ -338,23 +301,6 @@ public class KotlinSourceCodeWriter implements SourceCodeWriter<KotlinSourceCode
|
||||
return imports;
|
||||
}
|
||||
|
||||
private Collection<String> determineImports(Annotation annotation) {
|
||||
List<String> imports = new ArrayList<>();
|
||||
imports.add(annotation.getName());
|
||||
annotation.getAttributes().forEach((attribute) -> {
|
||||
if (attribute.getType() == Class.class) {
|
||||
imports.addAll(attribute.getValues());
|
||||
}
|
||||
if (Enum.class.isAssignableFrom(attribute.getType())) {
|
||||
imports.addAll(attribute.getValues()
|
||||
.stream()
|
||||
.map((value) -> value.substring(0, value.lastIndexOf(".")))
|
||||
.toList());
|
||||
}
|
||||
});
|
||||
return imports;
|
||||
}
|
||||
|
||||
@SuppressWarnings("removal")
|
||||
private Stream<KotlinExpression> getKotlinExpressions(KotlinFunctionDeclaration functionDeclaration) {
|
||||
return functionDeclaration.getStatements()
|
||||
@ -394,6 +340,16 @@ public class KotlinSourceCodeWriter implements SourceCodeWriter<KotlinSourceCode
|
||||
return "";
|
||||
}
|
||||
|
||||
@Override
|
||||
public CodeBlock arrayOf(CodeBlock... values) {
|
||||
return CodeBlock.of("[$L]", CodeBlock.join(Arrays.asList(values), ", "));
|
||||
}
|
||||
|
||||
@Override
|
||||
public CodeBlock classReference(ClassName className) {
|
||||
return CodeBlock.of("$T::class", className);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -0,0 +1,129 @@
|
||||
/*
|
||||
* Copyright 2012-2023 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package io.spring.initializr.generator.language;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
/**
|
||||
* Tests for {@link AnnotationContainer}.
|
||||
*
|
||||
* @author Stephane Nicoll
|
||||
*/
|
||||
class AnnotationContainerTests {
|
||||
|
||||
private static final ClassName TEST_CLASS_NAME = ClassName.of("com.example.Test");
|
||||
|
||||
private static final ClassName NESTED_CLASS_NAME = ClassName.of("com.example.Nested");
|
||||
|
||||
@Test
|
||||
void isEmptyWithEmptyContainer() {
|
||||
AnnotationContainer container = new AnnotationContainer();
|
||||
assertThat(container.isEmpty()).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
void isEmptyWithAnnotation() {
|
||||
AnnotationContainer container = new AnnotationContainer();
|
||||
container.add(TEST_CLASS_NAME, (annotation) -> annotation.add("value", "test"));
|
||||
assertThat(container.isEmpty()).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
void hasWithMatchingAnnotation() {
|
||||
AnnotationContainer container = new AnnotationContainer();
|
||||
container.add(TEST_CLASS_NAME, (annotation) -> annotation.add("value", "test"));
|
||||
assertThat(container.has(TEST_CLASS_NAME)).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
void hasWithNonMatchingAnnotation() {
|
||||
AnnotationContainer container = new AnnotationContainer();
|
||||
container.add(TEST_CLASS_NAME, (annotation) -> annotation.add("value", "test"));
|
||||
assertThat(container.has(ClassName.of("com.example.Another"))).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
void valuesWithSimpleAnnotation() {
|
||||
AnnotationContainer container = new AnnotationContainer();
|
||||
container.add(TEST_CLASS_NAME, (annotation) -> annotation.add("value", "test"));
|
||||
assertThat(container.values()).singleElement().satisfies((annotation) -> {
|
||||
assertThat(annotation.getClassName()).isEqualTo(TEST_CLASS_NAME);
|
||||
assertThat(annotation.getImports()).containsOnly("com.example.Test");
|
||||
assertThat(annotation.getAttributes()).singleElement().satisfies((attribute) -> {
|
||||
assertThat(attribute.getName()).isEqualTo("value");
|
||||
assertThat(attribute.getValues()).containsExactly("test");
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
void addAnnotationSeveralTimeReuseConfiguration() {
|
||||
AnnotationContainer container = new AnnotationContainer();
|
||||
container.add(TEST_CLASS_NAME, (annotation) -> annotation.add("value", "test"));
|
||||
container.add(TEST_CLASS_NAME, (annotation) -> annotation.add("value", "another"));
|
||||
assertThat(container.values()).singleElement().satisfies((annotation) -> {
|
||||
assertThat(annotation.getClassName()).isEqualTo(TEST_CLASS_NAME);
|
||||
assertThat(annotation.getImports()).containsOnly("com.example.Test");
|
||||
assertThat(annotation.getAttributes()).singleElement().satisfies((attribute) -> {
|
||||
assertThat(attribute.getName()).isEqualTo("value");
|
||||
assertThat(attribute.getValues()).containsExactly("test", "another");
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
void addAnnotationSeveralTimeCanReplaceAttribute() {
|
||||
AnnotationContainer container = new AnnotationContainer();
|
||||
container.add(TEST_CLASS_NAME,
|
||||
(annotation) -> annotation.add("value", Annotation.of(NESTED_CLASS_NAME).add("counter", 42).build()));
|
||||
container.add(TEST_CLASS_NAME,
|
||||
(annotation) -> annotation.set("value", Annotation.of(NESTED_CLASS_NAME).add("counter", 24).build()));
|
||||
assertThat(container.values()).singleElement().satisfies((annotation) -> {
|
||||
assertThat(annotation.getClassName()).isEqualTo(TEST_CLASS_NAME);
|
||||
assertThat(annotation.getImports()).containsOnly("com.example.Test", "com.example.Nested");
|
||||
assertThat(annotation.getAttributes()).singleElement().satisfies((attribute) -> {
|
||||
assertThat(attribute.getName()).isEqualTo("value");
|
||||
assertThat(attribute.getValues()).singleElement().isInstanceOfSatisfying(Annotation.class, (nested) -> {
|
||||
assertThat(nested.getClassName()).isEqualTo(NESTED_CLASS_NAME);
|
||||
assertThat(nested.getAttributes()).singleElement().satisfies((nestedAttribute) -> {
|
||||
assertThat(nestedAttribute.getName()).isEqualTo("counter");
|
||||
assertThat(nestedAttribute.getValues()).containsExactly(24);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
void removeWithMatchingAnnotation() {
|
||||
AnnotationContainer container = new AnnotationContainer();
|
||||
container.add(TEST_CLASS_NAME, (annotation) -> annotation.add("value", "test"));
|
||||
assertThat(container.remove(TEST_CLASS_NAME)).isTrue();
|
||||
assertThat(container.isEmpty()).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
void removeWithNonMatchingAnnotation() {
|
||||
AnnotationContainer container = new AnnotationContainer();
|
||||
container.add(TEST_CLASS_NAME, (annotation) -> annotation.add("value", "test"));
|
||||
assertThat(container.remove(ClassName.of("com.example.Another"))).isFalse();
|
||||
assertThat(container.isEmpty()).isFalse();
|
||||
}
|
||||
|
||||
}
|
@ -16,9 +16,23 @@
|
||||
|
||||
package io.spring.initializr.generator.language;
|
||||
|
||||
import java.io.StringWriter;
|
||||
import java.time.temporal.ChronoUnit;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import io.spring.initializr.generator.io.IndentingWriter;
|
||||
import io.spring.initializr.generator.io.SimpleIndentStrategy;
|
||||
import io.spring.initializr.generator.language.CodeBlock.FormattingOptions;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.params.ParameterizedTest;
|
||||
import org.junit.jupiter.params.provider.Arguments;
|
||||
import org.junit.jupiter.params.provider.MethodSource;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
|
||||
import static org.mockito.BDDMockito.given;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.verify;
|
||||
|
||||
/**
|
||||
* Tests for {@link Annotation}.
|
||||
@ -28,22 +42,223 @@ import static org.assertj.core.api.Assertions.assertThat;
|
||||
class AnnotationTests {
|
||||
|
||||
@Test
|
||||
void annotationWithNoAttribute() {
|
||||
Annotation annotation = Annotation.name("com.example.Test");
|
||||
assertThat(annotation.getName()).isEqualTo("com.example.Test");
|
||||
assertThat(annotation.getAttributes()).isEmpty();
|
||||
void annotationWithInvalidParameterValue() {
|
||||
assertThatIllegalArgumentException()
|
||||
.isThrownBy(() -> Annotation.of(ClassName.of("com.example.Test")).set("test", new StringWriter()))
|
||||
.withMessage(
|
||||
"Incompatible type. Found: 'java.io.StringWriter', required: primitive, String, Class, an Enum, an Annotation, or a CodeBlock");
|
||||
}
|
||||
|
||||
@Test
|
||||
void annotationWithSingleAttribute() {
|
||||
Annotation annotation = Annotation.name("com.example.Test", (builder) -> builder.attribute("test", Enum.class,
|
||||
"com.example.Unit.CENTURIES, com.example.Unit.NANOS"));
|
||||
assertThat(annotation.getName()).isEqualTo("com.example.Test");
|
||||
assertThat(annotation.getAttributes()).hasSize(1);
|
||||
Annotation.Attribute attribute = annotation.getAttributes().get(0);
|
||||
assertThat(attribute.getName()).isEqualTo("test");
|
||||
assertThat(attribute.getType()).isEqualTo(Enum.class);
|
||||
assertThat(attribute.getValues()).containsExactly("com.example.Unit.CENTURIES, com.example.Unit.NANOS");
|
||||
void annotationWithMixedParameterValues() {
|
||||
assertThatIllegalArgumentException()
|
||||
.isThrownBy(() -> Annotation.of(ClassName.of("com.example.Test")).set("test", "value", true))
|
||||
.withMessage("Parameter value must not have mixed types, got [STRING, PRIMITIVE]");
|
||||
}
|
||||
|
||||
@Test
|
||||
void annotationWithAmendedValueAndTypeMismatch() {
|
||||
assertThatIllegalArgumentException()
|
||||
.isThrownBy(() -> Annotation.of(ClassName.of("com.example.Test")).set("test", "value").add("test", true))
|
||||
.withMessage("Incompatible type. 'STRING' is not compatible with 'PRIMITIVE'");
|
||||
}
|
||||
|
||||
@Test
|
||||
void annotationWithNoAttribute() {
|
||||
Annotation test = Annotation.of(ClassName.of("com.example.Test")).build();
|
||||
assertThat(write(test)).isEqualTo("@Test");
|
||||
assertThat(test.getImports()).containsOnly("com.example.Test");
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@MethodSource("parameters")
|
||||
void annotationWithPrimitives(Object parameter, String expectedCode) {
|
||||
Annotation test = Annotation.of(ClassName.of("com.example.Test")).set("test", parameter).build();
|
||||
assertThat(write(test)).isEqualTo("@Test(test = " + expectedCode + ")");
|
||||
assertThat(test.getImports()).containsOnly("com.example.Test");
|
||||
}
|
||||
|
||||
static Stream<Arguments> parameters() {
|
||||
return Stream.of(Arguments.arguments(1, "1"), Arguments.arguments(0x4f, "79"),
|
||||
Arguments.arguments((short) 4, "4"), Arguments.arguments(500L, "500"),
|
||||
Arguments.arguments((float) 3.14, "3.14"), Arguments.arguments(3.156, "3.156"),
|
||||
Arguments.arguments(true, "true"), Arguments.arguments('t', "'t'"));
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@MethodSource("parametersDeprecated")
|
||||
@SuppressWarnings("removal")
|
||||
void annotationWithPrimitivesDeprecated(Class<?> type, String parameter, String expectedCode) {
|
||||
Annotation test = Annotation.of(ClassName.of("com.example.Test")).attribute("test", type, parameter).build();
|
||||
assertThat(write(test)).isEqualTo("@Test(test = " + expectedCode + ")");
|
||||
assertThat(test.getImports()).containsOnly("com.example.Test");
|
||||
}
|
||||
|
||||
static Stream<Arguments> parametersDeprecated() {
|
||||
// Character wasn't supported previously
|
||||
return Stream.of(Arguments.arguments(Integer.class, "1", "1"), Arguments.arguments(Byte.class, "0x4f", "0x4f"),
|
||||
Arguments.arguments(Short.class, "4", "4"), Arguments.arguments(Long.class, "500", "500"),
|
||||
Arguments.arguments(Float.class, "3.14", "3.14"), Arguments.arguments(Double.class, "3.156", "3.156"),
|
||||
Arguments.arguments(Boolean.class, "true", "true"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void annotationWithString() {
|
||||
Annotation test = Annotation.of(ClassName.of("com.example.Test")).set("test", "value").build();
|
||||
assertThat(write(test)).isEqualTo("@Test(test = \"value\")");
|
||||
assertThat(test.getImports()).containsOnly("com.example.Test");
|
||||
}
|
||||
|
||||
@Test
|
||||
@Deprecated
|
||||
@SuppressWarnings("removal")
|
||||
void annotationWithStringDeprecated() {
|
||||
Annotation test = Annotation.of(ClassName.of("com.example.Test"))
|
||||
.attribute("test", String.class, "value")
|
||||
.build();
|
||||
assertThat(write(test)).isEqualTo("@Test(test = \"value\")");
|
||||
assertThat(test.getImports()).containsOnly("com.example.Test");
|
||||
}
|
||||
|
||||
@Test
|
||||
void annotationWithClass() {
|
||||
Annotation test = Annotation.of(ClassName.of("com.example.Test")).set("test", Test.class).build();
|
||||
assertThat(write(test)).isEqualTo("@Test(test = Test.class)");
|
||||
assertThat(test.getImports()).containsOnly("com.example.Test", Test.class.getName());
|
||||
}
|
||||
|
||||
@Test
|
||||
void annotationWithClassName() {
|
||||
Annotation test = Annotation.of(ClassName.of("com.example.Test")).set("test", ClassName.of(Test.class)).build();
|
||||
assertThat(write(test)).isEqualTo("@Test(test = Test.class)");
|
||||
assertThat(test.getImports()).containsOnly("com.example.Test", Test.class.getName());
|
||||
}
|
||||
|
||||
@Test
|
||||
@Deprecated
|
||||
@SuppressWarnings("removal")
|
||||
void annotationWithClassNameStringDeprecated() {
|
||||
Annotation test = Annotation.of(ClassName.of("com.example.Test"))
|
||||
.attribute("test", Class.class, Test.class.getName())
|
||||
.build();
|
||||
assertThat(write(test)).isEqualTo("@Test(test = Test.class)");
|
||||
assertThat(test.getImports()).containsOnly("com.example.Test", Test.class.getName());
|
||||
}
|
||||
|
||||
@Test
|
||||
void annotationWithTypeReferenceInvokeConfiguredFormattingOptions() {
|
||||
ClassName typeReference = ClassName.of("com.example.Another");
|
||||
FormattingOptions options = mock(FormattingOptions.class);
|
||||
given(options.classReference(typeReference)).willReturn(CodeBlock.of("$T::class", typeReference));
|
||||
Annotation test = Annotation.of(ClassName.of("com.example.Test")).set("test", typeReference).build();
|
||||
assertThat(write(test, options)).isEqualTo("@Test(test = Another::class)");
|
||||
verify(options).classReference(typeReference);
|
||||
assertThat(test.getImports()).containsOnly("com.example.Test", typeReference.getName());
|
||||
}
|
||||
|
||||
@Test
|
||||
void annotationWithEnum() {
|
||||
Annotation test = Annotation.of(ClassName.of("com.example.Test")).set("test", ChronoUnit.CENTURIES).build();
|
||||
assertThat(write(test)).isEqualTo("@Test(test = ChronoUnit.CENTURIES)");
|
||||
assertThat(test.getImports()).containsOnly("com.example.Test", ChronoUnit.class.getName());
|
||||
}
|
||||
|
||||
@Test
|
||||
void annotationWithEnumCodeBlock() {
|
||||
Annotation test = Annotation.of(ClassName.of("com.example.Test"))
|
||||
.set("test", CodeBlock.of("$T.CENTURIES", ChronoUnit.class))
|
||||
.build();
|
||||
assertThat(write(test)).isEqualTo("@Test(test = ChronoUnit.CENTURIES)");
|
||||
assertThat(test.getImports()).containsOnly("com.example.Test", ChronoUnit.class.getName());
|
||||
}
|
||||
|
||||
@Test
|
||||
@Deprecated
|
||||
@SuppressWarnings("removal")
|
||||
void annotationWithEnumDeprecated() {
|
||||
Annotation test = Annotation.of(ClassName.of("com.example.Test"))
|
||||
.attribute("test", Enum.class, "java.time.temporal.ChronoUnit.CENTURIES")
|
||||
.build();
|
||||
assertThat(write(test)).isEqualTo("@Test(test = ChronoUnit.CENTURIES)");
|
||||
assertThat(test.getImports()).containsOnly("com.example.Test", ChronoUnit.class.getName());
|
||||
}
|
||||
|
||||
@Test
|
||||
void annotationWithNestedAnnotation() {
|
||||
Annotation nested = Annotation.of(ClassName.of("com.example.Nested")).set("counter", 42).build();
|
||||
Annotation test = Annotation.of(ClassName.of("com.example.Test")).set("test", nested).build();
|
||||
assertThat(write(test)).isEqualTo("@Test(test = @Nested(counter = 42))");
|
||||
assertThat(test.getImports()).containsOnly("com.example.Test", "com.example.Nested");
|
||||
}
|
||||
|
||||
@Test
|
||||
void annotationWithOnlyValueUsesShortcut() {
|
||||
Annotation test = Annotation.of(ClassName.of("com.example.Test")).set("value", "test").build();
|
||||
assertThat(write(test)).isEqualTo("@Test(\"test\")");
|
||||
assertThat(test.getImports()).containsOnly("com.example.Test");
|
||||
}
|
||||
|
||||
@Test
|
||||
void annotationWithSeveralParameters() {
|
||||
Annotation test = Annotation.of(ClassName.of("com.example.Test"))
|
||||
.set("enabled", false)
|
||||
.set("counter", 42)
|
||||
.build();
|
||||
assertThat(write(test)).isEqualTo("@Test(enabled = false, counter = 42)");
|
||||
}
|
||||
|
||||
@Test
|
||||
void annotationWithParameterArray() {
|
||||
Annotation test = Annotation.of(ClassName.of("com.example.Test")).set("counters", 2, 4, 8, 10).build();
|
||||
assertThat(write(test)).isEqualTo("@Test(counters = { 2, 4, 8, 10 })");
|
||||
}
|
||||
|
||||
@Test
|
||||
@Deprecated
|
||||
@SuppressWarnings("removal")
|
||||
void annotationWithParameterArrayDeprecated() {
|
||||
Annotation test = Annotation.of(ClassName.of("com.example.Test"))
|
||||
.attribute("counters", Integer.class, "2", "4", "8", "10")
|
||||
.build();
|
||||
assertThat(write(test)).isEqualTo("@Test(counters = { 2, 4, 8, 10 })");
|
||||
}
|
||||
|
||||
@Test
|
||||
void annotationWithParameterArrayAsValueUsesShortcut() {
|
||||
Annotation test = Annotation.of(ClassName.of("com.example.Test")).set("value", 2, 4, 8, 10).build();
|
||||
assertThat(write(test)).isEqualTo("@Test({ 2, 4, 8, 10 })");
|
||||
}
|
||||
|
||||
@Test
|
||||
void annotationWithParameterClassAndCodeBlock() {
|
||||
Annotation test = Annotation.of(ClassName.of("com.example.Test"))
|
||||
.set("types", StringWriter.class, CodeBlock.of("$T.class", "com.example.io.AnotherWriter"))
|
||||
.build();
|
||||
assertThat(write(test)).isEqualTo("@Test(types = { StringWriter.class, AnotherWriter.class })");
|
||||
assertThat(test.getImports()).containsOnly("com.example.Test", StringWriter.class.getName(),
|
||||
"com.example.io.AnotherWriter");
|
||||
}
|
||||
|
||||
@Test
|
||||
void annotationWithAmendedValues() {
|
||||
Annotation test = Annotation.of(ClassName.of("com.example.Test"))
|
||||
.add("types", StringWriter.class)
|
||||
.add("types", CodeBlock.of("$T.class", "com.example.io.AnotherWriter"))
|
||||
.build();
|
||||
assertThat(write(test)).isEqualTo("@Test(types = { StringWriter.class, AnotherWriter.class })");
|
||||
assertThat(test.getImports()).containsOnly("com.example.Test", StringWriter.class.getName(),
|
||||
"com.example.io.AnotherWriter");
|
||||
}
|
||||
|
||||
private String write(Annotation annotation) {
|
||||
return write(annotation, CodeBlock.JAVA_FORMATTING_OPTIONS);
|
||||
}
|
||||
|
||||
private String write(Annotation annotation, FormattingOptions formattingOptions) {
|
||||
StringWriter out = new StringWriter();
|
||||
IndentingWriter writer = new IndentingWriter(out, new SimpleIndentStrategy("\t"));
|
||||
annotation.write(writer, formattingOptions);
|
||||
return out.toString();
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -26,9 +26,12 @@ import java.time.temporal.ChronoUnit;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
import io.spring.initializr.generator.io.IndentingWriterFactory;
|
||||
import io.spring.initializr.generator.language.Annotation;
|
||||
import io.spring.initializr.generator.language.Annotation.Builder;
|
||||
import io.spring.initializr.generator.language.ClassName;
|
||||
import io.spring.initializr.generator.language.CodeBlock;
|
||||
import io.spring.initializr.generator.language.Language;
|
||||
import io.spring.initializr.generator.language.Parameter;
|
||||
@ -156,7 +159,7 @@ class GroovySourceCodeWriterTests {
|
||||
GroovySourceCode sourceCode = new GroovySourceCode();
|
||||
GroovyCompilationUnit compilationUnit = sourceCode.createCompilationUnit("com.example", "Test");
|
||||
GroovyTypeDeclaration test = compilationUnit.createTypeDeclaration("Test");
|
||||
test.annotate(Annotation.name("org.springframework.boot.autoconfigure.SpringBootApplication"));
|
||||
test.annotations().add(ClassName.of("org.springframework.boot.autoconfigure.SpringBootApplication"));
|
||||
test.addMethodDeclaration(GroovyMethodDeclaration.method("main")
|
||||
.modifiers(Modifier.PUBLIC | Modifier.STATIC)
|
||||
.returning("void")
|
||||
@ -227,13 +230,29 @@ class GroovySourceCodeWriterTests {
|
||||
"class Test {", "", " public One testString", "", "}");
|
||||
}
|
||||
|
||||
@Test
|
||||
@Deprecated
|
||||
@SuppressWarnings("removal")
|
||||
void fieldAnnotationWithAnnotated() throws IOException {
|
||||
GroovySourceCode sourceCode = new GroovySourceCode();
|
||||
GroovyCompilationUnit compilationUnit = sourceCode.createCompilationUnit("com.example", "Test");
|
||||
GroovyTypeDeclaration test = compilationUnit.createTypeDeclaration("Test");
|
||||
GroovyFieldDeclaration field = GroovyFieldDeclaration.field("testString").returning("java.lang.String");
|
||||
field.annotate(Annotation.of(ClassName.of("org.springframework.beans.factory.annotation.Autowired")).build());
|
||||
test.addFieldDeclaration(field);
|
||||
List<String> lines = writeSingleType(sourceCode, "com/example/Test.groovy");
|
||||
assertThat(lines).containsExactly("package com.example", "",
|
||||
"import org.springframework.beans.factory.annotation.Autowired", "", "class Test {", "",
|
||||
" @Autowired", " String testString", "", "}");
|
||||
}
|
||||
|
||||
@Test
|
||||
void fieldAnnotation() throws IOException {
|
||||
GroovySourceCode sourceCode = new GroovySourceCode();
|
||||
GroovyCompilationUnit compilationUnit = sourceCode.createCompilationUnit("com.example", "Test");
|
||||
GroovyTypeDeclaration test = compilationUnit.createTypeDeclaration("Test");
|
||||
GroovyFieldDeclaration field = GroovyFieldDeclaration.field("testString").returning("java.lang.String");
|
||||
field.annotate(Annotation.name("org.springframework.beans.factory.annotation.Autowired"));
|
||||
field.annotations().add(ClassName.of("org.springframework.beans.factory.annotation.Autowired"));
|
||||
test.addFieldDeclaration(field);
|
||||
List<String> lines = writeSingleType(sourceCode, "com/example/Test.groovy");
|
||||
assertThat(lines).containsExactly("package com.example", "",
|
||||
@ -243,32 +262,16 @@ class GroovySourceCodeWriterTests {
|
||||
|
||||
@Test
|
||||
void annotationWithSimpleAttribute() throws IOException {
|
||||
List<String> lines = writeClassAnnotation(Annotation.name("org.springframework.test.TestApplication",
|
||||
(builder) -> builder.attribute("counter", Integer.class, "42")));
|
||||
List<String> lines = writeClassAnnotation("org.springframework.test.TestApplication",
|
||||
(builder) -> builder.set("counter", 42));
|
||||
assertThat(lines).containsExactly("package com.example", "", "import org.springframework.test.TestApplication",
|
||||
"", "@TestApplication(counter = 42)", "class Test {", "", "}");
|
||||
}
|
||||
|
||||
@Test
|
||||
void annotationWithSimpleStringAttribute() throws IOException {
|
||||
List<String> lines = writeClassAnnotation(Annotation.name("org.springframework.test.TestApplication",
|
||||
(builder) -> builder.attribute("name", String.class, "test")));
|
||||
assertThat(lines).containsExactly("package com.example", "", "import org.springframework.test.TestApplication",
|
||||
"", "@TestApplication(name = \"test\")", "class Test {", "", "}");
|
||||
}
|
||||
|
||||
@Test
|
||||
void annotationWithOnlyValueAttribute() throws IOException {
|
||||
List<String> lines = writeClassAnnotation(Annotation.name("org.springframework.test.TestApplication",
|
||||
(builder) -> builder.attribute("value", String.class, "test")));
|
||||
assertThat(lines).containsExactly("package com.example", "", "import org.springframework.test.TestApplication",
|
||||
"", "@TestApplication(\"test\")", "class Test {", "", "}");
|
||||
}
|
||||
|
||||
@Test
|
||||
void annotationWithSimpleEnumAttribute() throws IOException {
|
||||
List<String> lines = writeClassAnnotation(Annotation.name("org.springframework.test.TestApplication",
|
||||
(builder) -> builder.attribute("unit", Enum.class, "java.time.temporal.ChronoUnit.SECONDS")));
|
||||
List<String> lines = writeClassAnnotation("org.springframework.test.TestApplication",
|
||||
(builder) -> builder.set("unit", ChronoUnit.SECONDS));
|
||||
assertThat(lines).containsExactly("package com.example", "", "import java.time.temporal.ChronoUnit",
|
||||
"import org.springframework.test.TestApplication", "", "@TestApplication(unit = ChronoUnit.SECONDS)",
|
||||
"class Test {", "", "}");
|
||||
@ -276,29 +279,19 @@ class GroovySourceCodeWriterTests {
|
||||
|
||||
@Test
|
||||
void annotationWithClassArrayAttribute() throws IOException {
|
||||
List<String> lines = writeClassAnnotation(
|
||||
Annotation.name("org.springframework.test.TestApplication", (builder) -> builder.attribute("target",
|
||||
Class.class, "com.example.another.One", "com.example.another.Two")));
|
||||
List<String> lines = writeClassAnnotation("org.springframework.test.TestApplication", (builder) -> builder
|
||||
.set("target", ClassName.of("com.example.another.One"), ClassName.of("com.example.another.Two")));
|
||||
assertThat(lines).containsExactly("package com.example", "", "import com.example.another.One",
|
||||
"import com.example.another.Two", "import org.springframework.test.TestApplication", "",
|
||||
"@TestApplication(target = [ One, Two ])", "class Test {", "", "}");
|
||||
}
|
||||
|
||||
@Test
|
||||
void annotationWithSeveralAttributes() throws IOException {
|
||||
List<String> lines = writeClassAnnotation(Annotation.name("org.springframework.test.TestApplication",
|
||||
(builder) -> builder.attribute("target", Class.class, "com.example.One")
|
||||
.attribute("unit", ChronoUnit.class, "java.time.temporal.ChronoUnit.NANOS")));
|
||||
assertThat(lines).containsExactly("package com.example", "", "import java.time.temporal.ChronoUnit",
|
||||
"import org.springframework.test.TestApplication", "",
|
||||
"@TestApplication(target = One, unit = ChronoUnit.NANOS)", "class Test {", "", "}");
|
||||
}
|
||||
|
||||
private List<String> writeClassAnnotation(Annotation annotation) throws IOException {
|
||||
private List<String> writeClassAnnotation(String annotationClassName, Consumer<Builder> annotation)
|
||||
throws IOException {
|
||||
GroovySourceCode sourceCode = new GroovySourceCode();
|
||||
GroovyCompilationUnit compilationUnit = sourceCode.createCompilationUnit("com.example", "Test");
|
||||
GroovyTypeDeclaration test = compilationUnit.createTypeDeclaration("Test");
|
||||
test.annotate(annotation);
|
||||
test.annotations().add(ClassName.of(annotationClassName), annotation);
|
||||
return writeSingleType(sourceCode, "com/example/Test.groovy");
|
||||
}
|
||||
|
||||
@ -311,7 +304,7 @@ class GroovySourceCodeWriterTests {
|
||||
.returning("void")
|
||||
.parameters()
|
||||
.body(CodeBlock.of(""));
|
||||
method.annotate(Annotation.name("com.example.test.TestAnnotation"));
|
||||
method.annotations().add(ClassName.of("com.example.test.TestAnnotation"));
|
||||
test.addMethodDeclaration(method);
|
||||
List<String> lines = writeSingleType(sourceCode, "com/example/Test.groovy");
|
||||
assertThat(lines).containsExactly("package com.example", "", "import com.example.test.TestAnnotation", "",
|
||||
|
@ -26,9 +26,12 @@ import java.time.temporal.ChronoUnit;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
import io.spring.initializr.generator.io.IndentingWriterFactory;
|
||||
import io.spring.initializr.generator.language.Annotation;
|
||||
import io.spring.initializr.generator.language.Annotation.Builder;
|
||||
import io.spring.initializr.generator.language.ClassName;
|
||||
import io.spring.initializr.generator.language.CodeBlock;
|
||||
import io.spring.initializr.generator.language.Language;
|
||||
import io.spring.initializr.generator.language.Parameter;
|
||||
@ -163,6 +166,25 @@ class JavaSourceCodeWriterTests {
|
||||
" public One testString;", "", "}");
|
||||
}
|
||||
|
||||
@Test
|
||||
@Deprecated
|
||||
@SuppressWarnings("removal")
|
||||
void fieldAnnotationWithAnnotate() throws IOException {
|
||||
JavaSourceCode sourceCode = new JavaSourceCode();
|
||||
JavaCompilationUnit compilationUnit = sourceCode.createCompilationUnit("com.example", "Test");
|
||||
JavaTypeDeclaration test = compilationUnit.createTypeDeclaration("Test");
|
||||
test.modifiers(Modifier.PUBLIC);
|
||||
JavaFieldDeclaration field = JavaFieldDeclaration.field("testString")
|
||||
.modifiers(Modifier.PRIVATE)
|
||||
.returning("java.lang.String");
|
||||
field.annotate(Annotation.of(ClassName.of("org.springframework.beans.factory.annotation.Autowired")).build());
|
||||
test.addFieldDeclaration(field);
|
||||
List<String> lines = writeSingleType(sourceCode, "com/example/Test.java");
|
||||
assertThat(lines).containsExactly("package com.example;", "",
|
||||
"import org.springframework.beans.factory.annotation.Autowired;", "", "public class Test {", "",
|
||||
" @Autowired", " private String testString;", "", "}");
|
||||
}
|
||||
|
||||
@Test
|
||||
void fieldAnnotation() throws IOException {
|
||||
JavaSourceCode sourceCode = new JavaSourceCode();
|
||||
@ -172,7 +194,7 @@ class JavaSourceCodeWriterTests {
|
||||
JavaFieldDeclaration field = JavaFieldDeclaration.field("testString")
|
||||
.modifiers(Modifier.PRIVATE)
|
||||
.returning("java.lang.String");
|
||||
field.annotate(Annotation.name("org.springframework.beans.factory.annotation.Autowired"));
|
||||
field.annotations().add(ClassName.of("org.springframework.beans.factory.annotation.Autowired"));
|
||||
test.addFieldDeclaration(field);
|
||||
List<String> lines = writeSingleType(sourceCode, "com/example/Test.java");
|
||||
assertThat(lines).containsExactly("package com.example;", "",
|
||||
@ -232,7 +254,7 @@ class JavaSourceCodeWriterTests {
|
||||
JavaSourceCode sourceCode = new JavaSourceCode();
|
||||
JavaCompilationUnit compilationUnit = sourceCode.createCompilationUnit("com.example", "Test");
|
||||
JavaTypeDeclaration test = compilationUnit.createTypeDeclaration("Test");
|
||||
test.annotate(Annotation.name("org.springframework.boot.autoconfigure.SpringBootApplication"));
|
||||
test.annotations().add(ClassName.of("org.springframework.boot.autoconfigure.SpringBootApplication"));
|
||||
test.addMethodDeclaration(JavaMethodDeclaration.method("main")
|
||||
.modifiers(Modifier.PUBLIC | Modifier.STATIC)
|
||||
.returning("void")
|
||||
@ -249,35 +271,17 @@ class JavaSourceCodeWriterTests {
|
||||
|
||||
@Test
|
||||
void annotationWithSimpleAttribute() throws IOException {
|
||||
List<String> lines = writeClassAnnotation(Annotation.name("org.springframework.test.TestApplication",
|
||||
(builder) -> builder.attribute("counter", Integer.class, "42")));
|
||||
List<String> lines = writeClassAnnotation("org.springframework.test.TestApplication",
|
||||
(builder) -> builder.set("counter", 42));
|
||||
assertThat(lines).containsExactly("package com.example;", "",
|
||||
"import org.springframework.test.TestApplication;", "", "@TestApplication(counter = 42)",
|
||||
"class Test {", "", "}");
|
||||
}
|
||||
|
||||
@Test
|
||||
void annotationWithSimpleStringAttribute() throws IOException {
|
||||
List<String> lines = writeClassAnnotation(Annotation.name("org.springframework.test.TestApplication",
|
||||
(builder) -> builder.attribute("name", String.class, "test")));
|
||||
assertThat(lines).containsExactly("package com.example;", "",
|
||||
"import org.springframework.test.TestApplication;", "", "@TestApplication(name = \"test\")",
|
||||
"class Test {", "", "}");
|
||||
}
|
||||
|
||||
@Test
|
||||
void annotationWithOnlyValueAttribute() throws IOException {
|
||||
List<String> lines = writeClassAnnotation(Annotation.name("org.springframework.test.TestApplication",
|
||||
(builder) -> builder.attribute("value", String.class, "test")));
|
||||
assertThat(lines).containsExactly("package com.example;", "",
|
||||
"import org.springframework.test.TestApplication;", "", "@TestApplication(\"test\")", "class Test {",
|
||||
"", "}");
|
||||
}
|
||||
|
||||
@Test
|
||||
void annotationWithSimpleEnumAttribute() throws IOException {
|
||||
List<String> lines = writeClassAnnotation(Annotation.name("org.springframework.test.TestApplication",
|
||||
(builder) -> builder.attribute("unit", Enum.class, "java.time.temporal.ChronoUnit.SECONDS")));
|
||||
List<String> lines = writeClassAnnotation("org.springframework.test.TestApplication",
|
||||
(builder) -> builder.set("unit", ChronoUnit.SECONDS));
|
||||
assertThat(lines).containsExactly("package com.example;", "", "import java.time.temporal.ChronoUnit;",
|
||||
"import org.springframework.test.TestApplication;", "", "@TestApplication(unit = ChronoUnit.SECONDS)",
|
||||
"class Test {", "", "}");
|
||||
@ -285,31 +289,40 @@ class JavaSourceCodeWriterTests {
|
||||
|
||||
@Test
|
||||
void annotationWithClassArrayAttribute() throws IOException {
|
||||
List<String> lines = writeClassAnnotation(Annotation.name("org.springframework.test.TestApplication",
|
||||
(builder) -> builder.attribute("target", Class.class, "com.another.One", "com.another.Two")));
|
||||
List<String> lines = writeClassAnnotation("org.springframework.test.TestApplication",
|
||||
(builder) -> builder.set("target", ClassName.of("com.another.One"), ClassName.of("com.another.Two")));
|
||||
assertThat(lines).containsExactly("package com.example;", "", "import com.another.One;",
|
||||
"import com.another.Two;", "import org.springframework.test.TestApplication;", "",
|
||||
"@TestApplication(target = { One.class, Two.class })", "class Test {", "", "}");
|
||||
}
|
||||
|
||||
@Test
|
||||
void annotationWithSeveralAttributes() throws IOException {
|
||||
List<String> lines = writeClassAnnotation(Annotation.name("org.springframework.test.TestApplication",
|
||||
(builder) -> builder.attribute("target", Class.class, "com.example.One")
|
||||
.attribute("unit", ChronoUnit.class, "java.time.temporal.ChronoUnit.NANOS")));
|
||||
assertThat(lines).containsExactly("package com.example;", "", "import java.time.temporal.ChronoUnit;",
|
||||
"import org.springframework.test.TestApplication;", "",
|
||||
"@TestApplication(target = One.class, unit = ChronoUnit.NANOS)", "class Test {", "", "}");
|
||||
}
|
||||
|
||||
private List<String> writeClassAnnotation(Annotation annotation) throws IOException {
|
||||
private List<String> writeClassAnnotation(String annotationClassName, Consumer<Builder> annotation)
|
||||
throws IOException {
|
||||
JavaSourceCode sourceCode = new JavaSourceCode();
|
||||
JavaCompilationUnit compilationUnit = sourceCode.createCompilationUnit("com.example", "Test");
|
||||
JavaTypeDeclaration test = compilationUnit.createTypeDeclaration("Test");
|
||||
test.annotate(annotation);
|
||||
test.annotations().add(ClassName.of(annotationClassName), annotation);
|
||||
return writeSingleType(sourceCode, "com/example/Test.java");
|
||||
}
|
||||
|
||||
@Test
|
||||
@Deprecated
|
||||
@SuppressWarnings("removal")
|
||||
void methodWithSimpleAnnotationWithAnnotated() throws IOException {
|
||||
JavaSourceCode sourceCode = new JavaSourceCode();
|
||||
JavaCompilationUnit compilationUnit = sourceCode.createCompilationUnit("com.example", "Test");
|
||||
JavaTypeDeclaration test = compilationUnit.createTypeDeclaration("Test");
|
||||
JavaMethodDeclaration method = JavaMethodDeclaration.method("something")
|
||||
.returning("void")
|
||||
.parameters()
|
||||
.body(CodeBlock.of(""));
|
||||
method.annotate(Annotation.name("com.example.test.TestAnnotation"));
|
||||
test.addMethodDeclaration(method);
|
||||
List<String> lines = writeSingleType(sourceCode, "com/example/Test.java");
|
||||
assertThat(lines).containsExactly("package com.example;", "", "import com.example.test.TestAnnotation;", "",
|
||||
"class Test {", "", " @TestAnnotation", " void something() {", " }", "", "}");
|
||||
}
|
||||
|
||||
@Test
|
||||
void methodWithSimpleAnnotation() throws IOException {
|
||||
JavaSourceCode sourceCode = new JavaSourceCode();
|
||||
@ -319,7 +332,7 @@ class JavaSourceCodeWriterTests {
|
||||
.returning("void")
|
||||
.parameters()
|
||||
.body(CodeBlock.of(""));
|
||||
method.annotate(Annotation.name("com.example.test.TestAnnotation"));
|
||||
method.annotations().add(ClassName.of("com.example.test.TestAnnotation"));
|
||||
test.addMethodDeclaration(method);
|
||||
List<String> lines = writeSingleType(sourceCode, "com/example/Test.java");
|
||||
assertThat(lines).containsExactly("package com.example;", "", "import com.example.test.TestAnnotation;", "",
|
||||
|
@ -25,9 +25,12 @@ import java.time.temporal.ChronoUnit;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
import io.spring.initializr.generator.io.IndentingWriterFactory;
|
||||
import io.spring.initializr.generator.language.Annotation;
|
||||
import io.spring.initializr.generator.language.Annotation.Builder;
|
||||
import io.spring.initializr.generator.language.ClassName;
|
||||
import io.spring.initializr.generator.language.CodeBlock;
|
||||
import io.spring.initializr.generator.language.Language;
|
||||
import io.spring.initializr.generator.language.Parameter;
|
||||
@ -249,7 +252,7 @@ class KotlinSourceCodeWriterTests {
|
||||
test.addPropertyDeclaration(KotlinPropertyDeclaration.var("testProp")
|
||||
.returning("java.lang.String")
|
||||
.setter()
|
||||
.withAnnotation(Annotation.name("org.springframework.beans.factory.annotation.Autowired"))
|
||||
.withAnnotation(ClassName.of("org.springframework.beans.factory.annotation.Autowired"))
|
||||
.buildAccessor()
|
||||
.value("\"This is a test\""));
|
||||
List<String> lines = writeSingleType(sourceCode, "com/example/Test.kt");
|
||||
@ -315,7 +318,7 @@ class KotlinSourceCodeWriterTests {
|
||||
KotlinSourceCode sourceCode = new KotlinSourceCode();
|
||||
KotlinCompilationUnit compilationUnit = sourceCode.createCompilationUnit("com.example", "Test");
|
||||
KotlinTypeDeclaration test = compilationUnit.createTypeDeclaration("Test");
|
||||
test.annotate(Annotation.name("org.springframework.boot.autoconfigure.SpringBootApplication"));
|
||||
test.annotations().add(ClassName.of("org.springframework.boot.autoconfigure.SpringBootApplication"));
|
||||
compilationUnit.addTopLevelFunction(KotlinFunctionDeclaration.function("main")
|
||||
.parameters(new Parameter("Array<String>", "args"))
|
||||
.body(CodeBlock.ofStatement("$T<$L>(*args)", "org.springframework.boot.runApplication", "Test")));
|
||||
@ -328,32 +331,16 @@ class KotlinSourceCodeWriterTests {
|
||||
|
||||
@Test
|
||||
void annotationWithSimpleAttribute() throws IOException {
|
||||
List<String> lines = writeClassAnnotation(Annotation.name("org.springframework.test.TestApplication",
|
||||
(builder) -> builder.attribute("counter", Integer.class, "42")));
|
||||
List<String> lines = writeClassAnnotation("org.springframework.test.TestApplication",
|
||||
(builder) -> builder.set("counter", 42));
|
||||
assertThat(lines).containsExactly("package com.example", "", "import org.springframework.test.TestApplication",
|
||||
"", "@TestApplication(counter = 42)", "class Test");
|
||||
}
|
||||
|
||||
@Test
|
||||
void annotationWithSimpleStringAttribute() throws IOException {
|
||||
List<String> lines = writeClassAnnotation(Annotation.name("org.springframework.test.TestApplication",
|
||||
(builder) -> builder.attribute("name", String.class, "test")));
|
||||
assertThat(lines).containsExactly("package com.example", "", "import org.springframework.test.TestApplication",
|
||||
"", "@TestApplication(name = \"test\")", "class Test");
|
||||
}
|
||||
|
||||
@Test
|
||||
void annotationWithOnlyValueAttribute() throws IOException {
|
||||
List<String> lines = writeClassAnnotation(Annotation.name("org.springframework.test.TestApplication",
|
||||
(builder) -> builder.attribute("value", String.class, "test")));
|
||||
assertThat(lines).containsExactly("package com.example", "", "import org.springframework.test.TestApplication",
|
||||
"", "@TestApplication(\"test\")", "class Test");
|
||||
}
|
||||
|
||||
@Test
|
||||
void annotationWithSimpleEnumAttribute() throws IOException {
|
||||
List<String> lines = writeClassAnnotation(Annotation.name("org.springframework.test.TestApplication",
|
||||
(builder) -> builder.attribute("unit", Enum.class, "java.time.temporal.ChronoUnit.SECONDS")));
|
||||
List<String> lines = writeClassAnnotation("org.springframework.test.TestApplication",
|
||||
(builder) -> builder.set("unit", ChronoUnit.SECONDS));
|
||||
assertThat(lines).containsExactly("package com.example", "", "import java.time.temporal.ChronoUnit",
|
||||
"import org.springframework.test.TestApplication", "", "@TestApplication(unit = ChronoUnit.SECONDS)",
|
||||
"class Test");
|
||||
@ -361,39 +348,44 @@ class KotlinSourceCodeWriterTests {
|
||||
|
||||
@Test
|
||||
void annotationWithClassArrayAttribute() throws IOException {
|
||||
List<String> lines = writeClassAnnotation(
|
||||
Annotation.name("org.springframework.test.TestApplication", (builder) -> builder.attribute("target",
|
||||
Class.class, "com.example.another.One", "com.example.another.Two")));
|
||||
List<String> lines = writeClassAnnotation("org.springframework.test.TestApplication", (builder) -> builder
|
||||
.set("target", ClassName.of("com.example.another.One"), ClassName.of("com.example.another.Two")));
|
||||
assertThat(lines).containsExactly("package com.example", "", "import com.example.another.One",
|
||||
"import com.example.another.Two", "import org.springframework.test.TestApplication", "",
|
||||
"@TestApplication(target = [One::class, Two::class])", "class Test");
|
||||
}
|
||||
|
||||
@Test
|
||||
void annotationWithSeveralAttributes() throws IOException {
|
||||
List<String> lines = writeClassAnnotation(Annotation.name("org.springframework.test.TestApplication",
|
||||
(builder) -> builder.attribute("target", Class.class, "com.example.One")
|
||||
.attribute("unit", ChronoUnit.class, "java.time.temporal.ChronoUnit.NANOS")));
|
||||
assertThat(lines).containsExactly("package com.example", "", "import java.time.temporal.ChronoUnit",
|
||||
"import org.springframework.test.TestApplication", "",
|
||||
"@TestApplication(target = One::class, unit = ChronoUnit.NANOS)", "class Test");
|
||||
}
|
||||
|
||||
private List<String> writeClassAnnotation(Annotation annotation) throws IOException {
|
||||
private List<String> writeClassAnnotation(String annotationClassName, Consumer<Builder> annotation)
|
||||
throws IOException {
|
||||
KotlinSourceCode sourceCode = new KotlinSourceCode();
|
||||
KotlinCompilationUnit compilationUnit = sourceCode.createCompilationUnit("com.example", "Test");
|
||||
KotlinTypeDeclaration test = compilationUnit.createTypeDeclaration("Test");
|
||||
test.annotate(annotation);
|
||||
test.annotations().add(ClassName.of(annotationClassName), annotation);
|
||||
return writeSingleType(sourceCode, "com/example/Test.kt");
|
||||
}
|
||||
|
||||
@Test
|
||||
@Deprecated
|
||||
@SuppressWarnings("removal")
|
||||
void functionWithSimpleAnnotationAndAnnotate() throws IOException {
|
||||
KotlinSourceCode sourceCode = new KotlinSourceCode();
|
||||
KotlinCompilationUnit compilationUnit = sourceCode.createCompilationUnit("com.example", "Test");
|
||||
KotlinTypeDeclaration test = compilationUnit.createTypeDeclaration("Test");
|
||||
KotlinFunctionDeclaration function = KotlinFunctionDeclaration.function("something").body(CodeBlock.of(""));
|
||||
function.annotate(Annotation.of(ClassName.of("com.example.test.TestAnnotation")).build());
|
||||
test.addFunctionDeclaration(function);
|
||||
List<String> lines = writeSingleType(sourceCode, "com/example/Test.kt");
|
||||
assertThat(lines).containsExactly("package com.example", "", "import com.example.test.TestAnnotation", "",
|
||||
"class Test {", "", " @TestAnnotation", " fun something() {", " }", "", "}");
|
||||
}
|
||||
|
||||
@Test
|
||||
void functionWithSimpleAnnotation() throws IOException {
|
||||
KotlinSourceCode sourceCode = new KotlinSourceCode();
|
||||
KotlinCompilationUnit compilationUnit = sourceCode.createCompilationUnit("com.example", "Test");
|
||||
KotlinTypeDeclaration test = compilationUnit.createTypeDeclaration("Test");
|
||||
KotlinFunctionDeclaration function = KotlinFunctionDeclaration.function("something").body(CodeBlock.of(""));
|
||||
function.annotate(Annotation.name("com.example.test.TestAnnotation"));
|
||||
function.annotations().add(ClassName.of("com.example.test.TestAnnotation"));
|
||||
test.addFunctionDeclaration(function);
|
||||
List<String> lines = writeSingleType(sourceCode, "com/example/Test.kt");
|
||||
assertThat(lines).containsExactly("package com.example", "", "import com.example.test.TestAnnotation", "",
|
||||
|
Loading…
Reference in New Issue
Block a user