From 29b98614f0089c70388106440b3dcbab27e1129b Mon Sep 17 00:00:00 2001 From: sijun-yang Date: Tue, 8 Jul 2025 20:52:49 +0900 Subject: [PATCH 01/28] Create AnnotationHolder.java Signed-off-by: sijun-yang --- .../generator/language/AnnotationHolder.java | 80 +++++++++++++++++++ 1 file changed, 80 insertions(+) create mode 100644 initializr-generator/src/main/java/io/spring/initializr/generator/language/AnnotationHolder.java diff --git a/initializr-generator/src/main/java/io/spring/initializr/generator/language/AnnotationHolder.java b/initializr-generator/src/main/java/io/spring/initializr/generator/language/AnnotationHolder.java new file mode 100644 index 0000000000..cecaabeb6e --- /dev/null +++ b/initializr-generator/src/main/java/io/spring/initializr/generator/language/AnnotationHolder.java @@ -0,0 +1,80 @@ +/* + * Copyright 2012 - present 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 io.spring.initializr.generator.language.Annotation.Builder; + +import java.util.function.Consumer; +import java.util.stream.Stream; + +/** + * A holder for {@linkplain Annotation annotations} defined on an annotated element. + * + * @author Stephane Nicoll + * @author Sijun Yang + */ +public interface AnnotationHolder { + + /** + * Specify if this holder is empty. + * @return {@code true} if no annotation is registered + */ + boolean isEmpty(); + + /** + * Specify if this holder has 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 + */ + boolean has(ClassName className); + + /** + * Return the {@link Annotation annotations}. + * @return the annotations + */ + Stream values(); + + /** + * 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} + */ + void add(ClassName className, Consumer annotation); + + /** + * 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 + */ + void add(ClassName className); + + /** + * 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 + */ + boolean remove(ClassName className); + + /** + * Create a deep copy of this annotation holder. + * @return a new annotation holder with the same annotations + */ + AnnotationHolder deepCopy(); + +} From ebf3cf8a01bce35c9b759a7966f879984c7d7107 Mon Sep 17 00:00:00 2001 From: sijun-yang Date: Tue, 8 Jul 2025 21:21:16 +0900 Subject: [PATCH 02/28] Implement AnnotationHolder in AnnotationContainer Signed-off-by: sijun-yang --- .../language/AnnotationContainer.java | 42 +++++-------------- 1 file changed, 10 insertions(+), 32 deletions(-) diff --git a/initializr-generator/src/main/java/io/spring/initializr/generator/language/AnnotationContainer.java b/initializr-generator/src/main/java/io/spring/initializr/generator/language/AnnotationContainer.java index 8526797715..5f9e9fc3bb 100644 --- a/initializr-generator/src/main/java/io/spring/initializr/generator/language/AnnotationContainer.java +++ b/initializr-generator/src/main/java/io/spring/initializr/generator/language/AnnotationContainer.java @@ -24,11 +24,12 @@ import io.spring.initializr.generator.language.Annotation.Builder; /** - * A container for {@linkplain Annotation annotations} defined on an annotated element. + * An {@link AnnotationHolder} implementation that holds at most one annotation per type. * * @author Stephane Nicoll + * @author Sijun Yang */ -public class AnnotationContainer { +public class AnnotationContainer implements AnnotationHolder { private final Map annotations; @@ -40,38 +41,22 @@ private AnnotationContainer(Map annotations) { this.annotations = annotations; } - /** - * Specify if this container is empty. - * @return {@code true} if no annotation is registered - */ + @Override 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 - */ + @Override public boolean has(ClassName className) { return this.annotations.containsKey(className); } - /** - * Return the {@link Annotation annotations}. - * @return the annotations - */ + @Override public Stream 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} - */ + @Override public void add(ClassName className, Consumer annotation) { Builder builder = this.annotations.computeIfAbsent(className, (key) -> new Builder(className)); if (annotation != null) { @@ -79,24 +64,17 @@ public void add(ClassName className, Consumer annotation) { } } - /** - * 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 - */ + @Override 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 - */ + @Override public boolean remove(ClassName className) { return this.annotations.remove(className) != null; } + @Override public AnnotationContainer deepCopy() { Map copy = new LinkedHashMap<>(); this.annotations.forEach((className, builder) -> copy.put(className, new Builder(builder))); From 5f339cb44a3355526f9823889fa74b0d7ca04502 Mon Sep 17 00:00:00 2001 From: sijun-yang Date: Tue, 8 Jul 2025 21:44:31 +0900 Subject: [PATCH 03/28] Create MultipleAnnotationContainer.java Signed-off-by: sijun-yang --- .../language/MultipleAnnotationContainer.java | 169 ++++++++++++++++++ 1 file changed, 169 insertions(+) create mode 100644 initializr-generator/src/main/java/io/spring/initializr/generator/language/MultipleAnnotationContainer.java diff --git a/initializr-generator/src/main/java/io/spring/initializr/generator/language/MultipleAnnotationContainer.java b/initializr-generator/src/main/java/io/spring/initializr/generator/language/MultipleAnnotationContainer.java new file mode 100644 index 0000000000..7ff28cb8ea --- /dev/null +++ b/initializr-generator/src/main/java/io/spring/initializr/generator/language/MultipleAnnotationContainer.java @@ -0,0 +1,169 @@ +/* + * Copyright 2012 - present 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 io.spring.initializr.generator.language.Annotation.Builder; + +import java.util.*; +import java.util.function.Consumer; +import java.util.stream.Stream; + +/** + * An {@link AnnotationHolder} implementation that can hold multiple annotations per type. + * + * @author Sijun Yang + */ +public class MultipleAnnotationContainer implements AnnotationHolder { + + private final Map> annotations; + + public MultipleAnnotationContainer() { + this(new LinkedHashMap<>()); + } + + private MultipleAnnotationContainer(Map> annotations) { + this.annotations = annotations; + } + + @Override + public boolean isEmpty() { + return this.annotations.isEmpty() || + this.annotations.values().stream().allMatch(List::isEmpty); + } + + @Override + public boolean has(ClassName className) { + List builders = this.annotations.get(className); + return builders != null && !builders.isEmpty(); + } + + @Override + public Stream values() { + return this.annotations.values().stream() + .flatMap(List::stream) + .map(Builder::build); + } + + /** + * Add operation is not supported for {@link MultipleAnnotationContainer}. + * Use {@link #addToList(ClassName, Consumer)} instead to explicitly add to the list. + * @throws UnsupportedOperationException always + */ + @Override + public void add(ClassName className, Consumer annotation) { + throw new UnsupportedOperationException( + "Add operation with potential overwrite is not supported for MultipleAnnotationContainer. " + + "Use addToList() to explicitly add annotations to the list."); + } + + /** + * Add operation is not supported for {@link MultipleAnnotationContainer}. + * Use {@link #addToList(ClassName)} instead to explicitly add to the list. + * @throws UnsupportedOperationException always + */ + @Override + public void add(ClassName className) { + throw new UnsupportedOperationException( + "Add operation with potential overwrite is not supported for MultipleAnnotationContainer. " + + "Use addToList() to explicitly add annotations to the list."); + } + + /** + * Add an annotation to the list of annotations with the specified class name. + * Always adds a new annotation. + * @param className the class name of an annotation + * @param annotation a {@link Consumer} to customize the {@link Annotation} + */ + public void addToList(ClassName className, Consumer annotation) { + List builders = this.annotations.computeIfAbsent(className, (key) -> new ArrayList<>()); + Builder builder = new Builder(className); + if (annotation != null) { + annotation.accept(builder); + } + builders.add(builder); + } + + /** + * Add an annotation to the list of annotations with the specified class name. + * Always adds a new annotation. + * @param className the class name of an annotation + */ + public void addToList(ClassName className) { + addToList(className, null); + } + + /** + * Return all annotations with the specified class name. + * @param className the class name of an annotation + * @return a stream of all annotations with the specified class name + */ + public Stream valuesOf(ClassName className) { + List builders = this.annotations.get(className); + if (builders == null || builders.isEmpty()) { + return Stream.empty(); + } + return builders.stream().map(Builder::build); + } + + /** + * Return the number of annotations with the specified class name. + * @param className the class name of an annotation + * @return the count of annotations with the specified class name + */ + public int countOf(ClassName className) { + List builders = this.annotations.get(className); + return builders != null ? builders.size() : 0; + } + + /** + * Remove all annotations with the specified class name. + * @param className the class name of the annotation + * @return the number of annotations that were removed + */ + public int removeAll(ClassName className) { + List builders = this.annotations.remove(className); + return builders != null ? builders.size() : 0; + } + + @Override + public boolean remove(ClassName className) { + int removedCount = removeAll(className); + return removedCount > 0; + } + + /** + * Check if this container has multiple annotations with the specified class name. + * @param className the class name of an annotation + * @return {@code true} if there are multiple annotations with the specified class name + */ + public boolean hasMultiple(ClassName className) { + List builders = this.annotations.get(className); + return builders != null && builders.size() > 1; + } + + @Override + public MultipleAnnotationContainer deepCopy() { + Map> copy = new LinkedHashMap<>(); + this.annotations.forEach((className, builders) -> { + List buildersCopy = new ArrayList<>(); + builders.forEach(builder -> buildersCopy.add(new Builder(builder))); + copy.put(className, buildersCopy); + }); + return new MultipleAnnotationContainer(copy); + } + +} From ee7f144dc6a9342ce8fca1b371a33198cd616734 Mon Sep 17 00:00:00 2001 From: sijun-yang Date: Tue, 8 Jul 2025 22:15:36 +0900 Subject: [PATCH 04/28] Create MultipleAnnotationContainerTests.java Signed-off-by: sijun-yang --- .../MultipleAnnotationContainerTests.java | 165 ++++++++++++++++++ 1 file changed, 165 insertions(+) create mode 100644 initializr-generator/src/test/java/io/spring/initializr/generator/language/MultipleAnnotationContainerTests.java diff --git a/initializr-generator/src/test/java/io/spring/initializr/generator/language/MultipleAnnotationContainerTests.java b/initializr-generator/src/test/java/io/spring/initializr/generator/language/MultipleAnnotationContainerTests.java new file mode 100644 index 0000000000..5591882896 --- /dev/null +++ b/initializr-generator/src/test/java/io/spring/initializr/generator/language/MultipleAnnotationContainerTests.java @@ -0,0 +1,165 @@ +package io.spring.initializr.generator.language; + +import org.junit.jupiter.api.Test; + +import java.util.List; +import java.util.stream.Collectors; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +/** + * Tests for {@link MultipleAnnotationContainer}. + * + * @author Sijun Yang + */ +class MultipleAnnotationContainerTests { + + private static final ClassName TEST_CLASS_NAME = ClassName.of("com.example.Test"); + + private static final ClassName OTHER_CLASS_NAME = ClassName.of("com.example.Other"); + + @Test + void isEmptyWithEmptyContainer() { + MultipleAnnotationContainer container = new MultipleAnnotationContainer(); + assertThat(container.isEmpty()).isTrue(); + } + + @Test + void isEmptyWithAnnotation() { + MultipleAnnotationContainer container = new MultipleAnnotationContainer(); + container.addToList(TEST_CLASS_NAME, builder -> builder.add("value", "test")); + assertThat(container.isEmpty()).isFalse(); + } + + @Test + void hasWithMatchingAnnotation() { + MultipleAnnotationContainer container = new MultipleAnnotationContainer(); + container.addToList(TEST_CLASS_NAME, builder -> builder.add("value", "test")); + assertThat(container.has(TEST_CLASS_NAME)).isTrue(); + } + + @Test + void hasWithNonMatchingAnnotation() { + MultipleAnnotationContainer container = new MultipleAnnotationContainer(); + container.addToList(TEST_CLASS_NAME, builder -> builder.add("value", "test")); + assertThat(container.has(OTHER_CLASS_NAME)).isFalse(); + } + + @Test + void valuesShouldReturnAllAnnotations() { + MultipleAnnotationContainer container = new MultipleAnnotationContainer(); + container.addToList(TEST_CLASS_NAME, builder -> builder.add("value", "one")); + container.addToList(TEST_CLASS_NAME, builder -> builder.add("value", "two")); + List annotations = container.values().collect(Collectors.toList()); + assertThat(annotations).hasSize(2); + assertThat(annotations).allMatch(annotation -> annotation.getClassName().equals(TEST_CLASS_NAME)); + } + + @Test + void valuesOfShouldReturnMatchingAnnotationsOnly() { + MultipleAnnotationContainer container = new MultipleAnnotationContainer(); + container.addToList(TEST_CLASS_NAME, builder -> builder.add("value", "one")); + container.addToList(OTHER_CLASS_NAME, builder -> builder.add("name", "other")); + List annotations = container.valuesOf(TEST_CLASS_NAME).collect(Collectors.toList()); + assertThat(annotations).hasSize(1); + assertThat(annotations.get(0).getAttributes()).singleElement().satisfies((attribute) -> { + assertThat(attribute.getName()).isEqualTo("value"); + assertThat(attribute.getValues()).containsExactly("one"); + }); + } + + @Test + void valuesOfShouldReturnEmptyStreamForNonExistingClassName() { + MultipleAnnotationContainer container = new MultipleAnnotationContainer(); + container.addToList(TEST_CLASS_NAME); + assertThat(container.valuesOf(OTHER_CLASS_NAME)).isEmpty(); + } + + @Test + void countOfShouldReturnCorrectCount() { + MultipleAnnotationContainer container = new MultipleAnnotationContainer(); + container.addToList(TEST_CLASS_NAME); + container.addToList(TEST_CLASS_NAME); + assertThat(container.countOf(TEST_CLASS_NAME)).isEqualTo(2); + assertThat(container.countOf(OTHER_CLASS_NAME)).isZero(); + } + + @Test + void removeAllShouldRemoveAllAnnotationsOfClassName() { + MultipleAnnotationContainer container = new MultipleAnnotationContainer(); + container.addToList(TEST_CLASS_NAME); + container.addToList(TEST_CLASS_NAME); + int removed = container.removeAll(TEST_CLASS_NAME); + assertThat(removed).isEqualTo(2); + assertThat(container.has(TEST_CLASS_NAME)).isFalse(); + } + + @Test + void removeShouldReturnFalseIfAllAnnotationRemoved() { + MultipleAnnotationContainer container = new MultipleAnnotationContainer(); + container.addToList(TEST_CLASS_NAME); + container.addToList(TEST_CLASS_NAME); + assertThat(container.remove(TEST_CLASS_NAME)).isTrue(); + assertThat(container.has(TEST_CLASS_NAME)).isFalse(); + } + + @Test + void removeShouldReturnFalseIfNoAnnotationRemoved() { + MultipleAnnotationContainer container = new MultipleAnnotationContainer(); + assertThat(container.remove(TEST_CLASS_NAME)).isFalse(); + } + + @Test + void hasMultipleReturnsTrueForMultipleAnnotations() { + MultipleAnnotationContainer container = new MultipleAnnotationContainer(); + container.addToList(TEST_CLASS_NAME); + container.addToList(TEST_CLASS_NAME); + assertThat(container.hasMultiple(TEST_CLASS_NAME)).isTrue(); + assertThat(container.hasMultiple(OTHER_CLASS_NAME)).isFalse(); + } + + @Test + void addUnsupportedMethodsThrowException() { + MultipleAnnotationContainer container = new MultipleAnnotationContainer(); + assertThatThrownBy(() -> container.add(TEST_CLASS_NAME)) + .isInstanceOf(UnsupportedOperationException.class); + assertThatThrownBy(() -> container.add(TEST_CLASS_NAME, builder -> {})) + .isInstanceOf(UnsupportedOperationException.class); + } + + @Test + void deepCopyShouldCreateDistinctObjectReferences() { + MultipleAnnotationContainer container = new MultipleAnnotationContainer(); + container.addToList(TEST_CLASS_NAME, builder -> builder.add("value", "test")); + + MultipleAnnotationContainer copy = container.deepCopy(); + + assertThat(copy).isNotSameAs(container); + + List originalAnnotations = container.valuesOf(TEST_CLASS_NAME).collect(Collectors.toList()); + List copiedAnnotations = copy.valuesOf(TEST_CLASS_NAME).collect(Collectors.toList()); + + assertThat(copiedAnnotations).hasSize(originalAnnotations.size()); + for (int i = 0; i < originalAnnotations.size(); i++) { + assertThat(copiedAnnotations.get(i)).isNotSameAs(originalAnnotations.get(i)); + } + } + + @Test + void deepCopyMutationShouldNotAffectOriginal() { + MultipleAnnotationContainer container = new MultipleAnnotationContainer(); + container.addToList(TEST_CLASS_NAME, builder -> builder.add("value", "original")); + + MultipleAnnotationContainer copy = container.deepCopy(); + + copy.addToList(TEST_CLASS_NAME, builder -> builder.add("value", "new")); + copy.addToList(OTHER_CLASS_NAME, builder -> builder.add("other", "test")); + + assertThat(container.countOf(TEST_CLASS_NAME)).isEqualTo(1); + assertThat(container.has(OTHER_CLASS_NAME)).isFalse(); + + assertThat(copy.countOf(TEST_CLASS_NAME)).isEqualTo(2); + assertThat(copy.has(OTHER_CLASS_NAME)).isTrue(); + } +} From 18eb26f5f7cb774b199c7709f1975e27dfc9e484 Mon Sep 17 00:00:00 2001 From: sijun-yang Date: Tue, 8 Jul 2025 22:54:42 +0900 Subject: [PATCH 05/28] Update Annotatable interface to use AnnotationHolder Signed-off-by: sijun-yang --- .../generator/language/Annotatable.java | 7 ++++--- .../initializr/generator/language/Parameter.java | 4 ++-- .../generator/language/TypeDeclaration.java | 11 ++++++++--- .../language/groovy/GroovyFieldDeclaration.java | 12 +++++++++--- .../language/groovy/GroovyMethodDeclaration.java | 12 +++++++++--- .../language/java/JavaFieldDeclaration.java | 12 +++++++++--- .../language/java/JavaMethodDeclaration.java | 12 +++++++++--- .../kotlin/KotlinFunctionDeclaration.java | 12 +++++++++--- .../kotlin/KotlinPropertyDeclaration.java | 16 +++++++++++----- 9 files changed, 70 insertions(+), 28 deletions(-) diff --git a/initializr-generator/src/main/java/io/spring/initializr/generator/language/Annotatable.java b/initializr-generator/src/main/java/io/spring/initializr/generator/language/Annotatable.java index 9f648aa216..f9840e9ad6 100644 --- a/initializr-generator/src/main/java/io/spring/initializr/generator/language/Annotatable.java +++ b/initializr-generator/src/main/java/io/spring/initializr/generator/language/Annotatable.java @@ -21,14 +21,15 @@ * * @author Andy Wilkinson * @author Stephane Nicoll + * @author Sijun Yang */ public interface Annotatable { /** - * Return the {@link AnnotationContainer} to use to configure the annotations of this + * Return the {@link AnnotationHolder} to use to configure the annotations of this * element. - * @return the annotation container + * @return the annotation holder */ - AnnotationContainer annotations(); + AnnotationHolder annotations(); } diff --git a/initializr-generator/src/main/java/io/spring/initializr/generator/language/Parameter.java b/initializr-generator/src/main/java/io/spring/initializr/generator/language/Parameter.java index af1acbfcd1..d92e2d8739 100644 --- a/initializr-generator/src/main/java/io/spring/initializr/generator/language/Parameter.java +++ b/initializr-generator/src/main/java/io/spring/initializr/generator/language/Parameter.java @@ -30,7 +30,7 @@ public final class Parameter implements Annotatable { private final String type; - private final AnnotationContainer annotations; + private final AnnotationHolder annotations; private Parameter(Builder builder) { this.name = builder.name; @@ -94,7 +94,7 @@ public String getType() { } @Override - public AnnotationContainer annotations() { + public AnnotationHolder annotations() { return this.annotations; } diff --git a/initializr-generator/src/main/java/io/spring/initializr/generator/language/TypeDeclaration.java b/initializr-generator/src/main/java/io/spring/initializr/generator/language/TypeDeclaration.java index 59b01eefa7..518e879566 100644 --- a/initializr-generator/src/main/java/io/spring/initializr/generator/language/TypeDeclaration.java +++ b/initializr-generator/src/main/java/io/spring/initializr/generator/language/TypeDeclaration.java @@ -28,7 +28,7 @@ */ public class TypeDeclaration implements Annotatable { - private final AnnotationContainer annotations = new AnnotationContainer(); + private final AnnotationHolder annotations; private final String name; @@ -36,12 +36,17 @@ public class TypeDeclaration implements Annotatable { private List implementsClassNames = Collections.emptyList(); + public TypeDeclaration(String name, AnnotationHolder annotations) { + this.name = name; + this.annotations = annotations; + } + /** * Creates a new instance. * @param name the type name */ public TypeDeclaration(String name) { - this.name = name; + this(name, new AnnotationContainer()); } /** @@ -69,7 +74,7 @@ public void implement(String... names) { } @Override - public AnnotationContainer annotations() { + public AnnotationHolder annotations() { return this.annotations; } diff --git a/initializr-generator/src/main/java/io/spring/initializr/generator/language/groovy/GroovyFieldDeclaration.java b/initializr-generator/src/main/java/io/spring/initializr/generator/language/groovy/GroovyFieldDeclaration.java index 3050f0d253..2a5268f974 100644 --- a/initializr-generator/src/main/java/io/spring/initializr/generator/language/groovy/GroovyFieldDeclaration.java +++ b/initializr-generator/src/main/java/io/spring/initializr/generator/language/groovy/GroovyFieldDeclaration.java @@ -18,6 +18,7 @@ import io.spring.initializr.generator.language.Annotatable; import io.spring.initializr.generator.language.AnnotationContainer; +import io.spring.initializr.generator.language.AnnotationHolder; /** * Declaration of a field written in Groovy. @@ -26,7 +27,7 @@ */ public final class GroovyFieldDeclaration implements Annotatable { - private final AnnotationContainer annotations = new AnnotationContainer(); + private final AnnotationHolder annotations; private final int modifiers; @@ -38,7 +39,8 @@ public final class GroovyFieldDeclaration implements Annotatable { private final boolean initialized; - private GroovyFieldDeclaration(Builder builder) { + public GroovyFieldDeclaration(Builder builder, AnnotationHolder annotations) { + this.annotations = annotations; this.modifiers = builder.modifiers; this.name = builder.name; this.returnType = builder.returnType; @@ -46,6 +48,10 @@ private GroovyFieldDeclaration(Builder builder) { this.initialized = builder.initialized; } + private GroovyFieldDeclaration(Builder builder) { + this(builder, new AnnotationContainer()); + } + /** * Creates a new builder for the field with the given name. * @param name the name @@ -56,7 +62,7 @@ public static Builder field(String name) { } @Override - public AnnotationContainer annotations() { + public AnnotationHolder annotations() { return this.annotations; } diff --git a/initializr-generator/src/main/java/io/spring/initializr/generator/language/groovy/GroovyMethodDeclaration.java b/initializr-generator/src/main/java/io/spring/initializr/generator/language/groovy/GroovyMethodDeclaration.java index 2dcc58f36a..4c7de5f860 100644 --- a/initializr-generator/src/main/java/io/spring/initializr/generator/language/groovy/GroovyMethodDeclaration.java +++ b/initializr-generator/src/main/java/io/spring/initializr/generator/language/groovy/GroovyMethodDeclaration.java @@ -23,6 +23,7 @@ import io.spring.initializr.generator.language.Annotatable; import io.spring.initializr.generator.language.AnnotationContainer; +import io.spring.initializr.generator.language.AnnotationHolder; import io.spring.initializr.generator.language.CodeBlock; import io.spring.initializr.generator.language.Parameter; @@ -33,7 +34,7 @@ */ public final class GroovyMethodDeclaration implements Annotatable { - private final AnnotationContainer annotations = new AnnotationContainer(); + private final AnnotationHolder annotations; private final String name; @@ -45,7 +46,8 @@ public final class GroovyMethodDeclaration implements Annotatable { private final CodeBlock code; - private GroovyMethodDeclaration(Builder builder, CodeBlock code) { + private GroovyMethodDeclaration(Builder builder, CodeBlock code, AnnotationHolder annotations) { + this.annotations = annotations; this.name = builder.name; this.returnType = builder.returnType; this.modifiers = builder.modifiers; @@ -53,6 +55,10 @@ private GroovyMethodDeclaration(Builder builder, CodeBlock code) { this.code = code; } + public GroovyMethodDeclaration(Builder builder, CodeBlock code) { + this(builder, code, new AnnotationContainer()); + } + /** * Creates a new builder for the method with the given name. * @param name the name @@ -83,7 +89,7 @@ CodeBlock getCode() { } @Override - public AnnotationContainer annotations() { + public AnnotationHolder annotations() { return this.annotations; } diff --git a/initializr-generator/src/main/java/io/spring/initializr/generator/language/java/JavaFieldDeclaration.java b/initializr-generator/src/main/java/io/spring/initializr/generator/language/java/JavaFieldDeclaration.java index 883620b551..e266515824 100644 --- a/initializr-generator/src/main/java/io/spring/initializr/generator/language/java/JavaFieldDeclaration.java +++ b/initializr-generator/src/main/java/io/spring/initializr/generator/language/java/JavaFieldDeclaration.java @@ -18,6 +18,7 @@ import io.spring.initializr.generator.language.Annotatable; import io.spring.initializr.generator.language.AnnotationContainer; +import io.spring.initializr.generator.language.AnnotationHolder; /** * Declaration of a field written in Java. @@ -26,7 +27,7 @@ */ public final class JavaFieldDeclaration implements Annotatable { - private final AnnotationContainer annotations = new AnnotationContainer(); + private final AnnotationHolder annotations; private final int modifiers; @@ -38,7 +39,8 @@ public final class JavaFieldDeclaration implements Annotatable { private final boolean initialized; - private JavaFieldDeclaration(Builder builder) { + public JavaFieldDeclaration(Builder builder, AnnotationHolder annotations) { + this.annotations = annotations; this.modifiers = builder.modifiers; this.name = builder.name; this.returnType = builder.returnType; @@ -46,6 +48,10 @@ private JavaFieldDeclaration(Builder builder) { this.initialized = builder.initialized; } + private JavaFieldDeclaration(Builder builder) { + this(builder, new AnnotationContainer()); + } + /** * Creates a new builder for the field with the given name. * @param name the field name @@ -56,7 +62,7 @@ public static Builder field(String name) { } @Override - public AnnotationContainer annotations() { + public AnnotationHolder annotations() { return this.annotations; } diff --git a/initializr-generator/src/main/java/io/spring/initializr/generator/language/java/JavaMethodDeclaration.java b/initializr-generator/src/main/java/io/spring/initializr/generator/language/java/JavaMethodDeclaration.java index 809099a3c5..432926948f 100644 --- a/initializr-generator/src/main/java/io/spring/initializr/generator/language/java/JavaMethodDeclaration.java +++ b/initializr-generator/src/main/java/io/spring/initializr/generator/language/java/JavaMethodDeclaration.java @@ -22,6 +22,7 @@ import io.spring.initializr.generator.language.Annotatable; import io.spring.initializr.generator.language.AnnotationContainer; +import io.spring.initializr.generator.language.AnnotationHolder; import io.spring.initializr.generator.language.CodeBlock; import io.spring.initializr.generator.language.Parameter; @@ -32,7 +33,7 @@ */ public final class JavaMethodDeclaration implements Annotatable { - private final AnnotationContainer annotations = new AnnotationContainer(); + private final AnnotationHolder annotations; private final String name; @@ -44,7 +45,8 @@ public final class JavaMethodDeclaration implements Annotatable { private final CodeBlock code; - private JavaMethodDeclaration(Builder builder, CodeBlock code) { + public JavaMethodDeclaration(Builder builder, CodeBlock code, AnnotationHolder annotations) { + this.annotations = annotations; this.name = builder.name; this.returnType = builder.returnType; this.modifiers = builder.modifiers; @@ -52,6 +54,10 @@ private JavaMethodDeclaration(Builder builder, CodeBlock code) { this.code = code; } + private JavaMethodDeclaration(Builder builder, CodeBlock code) { + this(builder, code, new AnnotationContainer()); + } + /** * Creates a new builder for the method with the given name. * @param name the name @@ -82,7 +88,7 @@ CodeBlock getCode() { } @Override - public AnnotationContainer annotations() { + public AnnotationHolder annotations() { return this.annotations; } diff --git a/initializr-generator/src/main/java/io/spring/initializr/generator/language/kotlin/KotlinFunctionDeclaration.java b/initializr-generator/src/main/java/io/spring/initializr/generator/language/kotlin/KotlinFunctionDeclaration.java index b579a8d379..5b250e06c1 100644 --- a/initializr-generator/src/main/java/io/spring/initializr/generator/language/kotlin/KotlinFunctionDeclaration.java +++ b/initializr-generator/src/main/java/io/spring/initializr/generator/language/kotlin/KotlinFunctionDeclaration.java @@ -22,6 +22,7 @@ import io.spring.initializr.generator.language.Annotatable; import io.spring.initializr.generator.language.AnnotationContainer; +import io.spring.initializr.generator.language.AnnotationHolder; import io.spring.initializr.generator.language.CodeBlock; import io.spring.initializr.generator.language.Parameter; @@ -32,7 +33,7 @@ */ public final class KotlinFunctionDeclaration implements Annotatable { - private final AnnotationContainer annotations = new AnnotationContainer(); + private final AnnotationHolder annotations; private final String name; @@ -44,7 +45,8 @@ public final class KotlinFunctionDeclaration implements Annotatable { private final CodeBlock code; - private KotlinFunctionDeclaration(Builder builder, CodeBlock code) { + private KotlinFunctionDeclaration(Builder builder, CodeBlock code, AnnotationHolder annotations) { + this.annotations = annotations; this.name = builder.name; this.returnType = builder.returnType; this.modifiers = builder.modifiers; @@ -52,6 +54,10 @@ private KotlinFunctionDeclaration(Builder builder, CodeBlock code) { this.code = code; } + private KotlinFunctionDeclaration(Builder builder, CodeBlock code) { + this(builder, code, new AnnotationContainer()); + } + /** * Creates a new builder for the function with the given name. * @param name the name @@ -82,7 +88,7 @@ CodeBlock getCode() { } @Override - public AnnotationContainer annotations() { + public AnnotationHolder annotations() { return this.annotations; } diff --git a/initializr-generator/src/main/java/io/spring/initializr/generator/language/kotlin/KotlinPropertyDeclaration.java b/initializr-generator/src/main/java/io/spring/initializr/generator/language/kotlin/KotlinPropertyDeclaration.java index 3e2a364fbc..3f0713e7e5 100644 --- a/initializr-generator/src/main/java/io/spring/initializr/generator/language/kotlin/KotlinPropertyDeclaration.java +++ b/initializr-generator/src/main/java/io/spring/initializr/generator/language/kotlin/KotlinPropertyDeclaration.java @@ -24,6 +24,7 @@ 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.AnnotationHolder; import io.spring.initializr.generator.language.ClassName; import io.spring.initializr.generator.language.CodeBlock; @@ -34,7 +35,7 @@ */ public final class KotlinPropertyDeclaration implements Annotatable { - private final AnnotationContainer annotations = new AnnotationContainer(); + private final AnnotationHolder annotations; private final boolean isVal; @@ -50,7 +51,8 @@ public final class KotlinPropertyDeclaration implements Annotatable { private final Accessor setter; - private KotlinPropertyDeclaration(Builder builder) { + private KotlinPropertyDeclaration(Builder builder, AnnotationHolder annotations) { + this.annotations = annotations; this.name = builder.name; this.returnType = builder.returnType; this.modifiers = new ArrayList<>(builder.modifiers); @@ -60,6 +62,10 @@ private KotlinPropertyDeclaration(Builder builder) { this.setter = builder.setter; } + public KotlinPropertyDeclaration(Builder builder) { + this(builder, new AnnotationContainer()); + } + /** * Returns a builder for a {@code val} property with the given name. * @param name the name @@ -111,7 +117,7 @@ Accessor getSetter() { } @Override - public AnnotationContainer annotations() { + public AnnotationHolder annotations() { return this.annotations; } @@ -308,7 +314,7 @@ public T buildAccessor() { static final class Accessor implements Annotatable { - private final AnnotationContainer annotations; + private final AnnotationHolder annotations; private final CodeBlock code; @@ -322,7 +328,7 @@ CodeBlock getCode() { } @Override - public AnnotationContainer annotations() { + public AnnotationHolder annotations() { return this.annotations; } From cd472dbf8d0f411d5919af3d7eebe60bb62a7517 Mon Sep 17 00:00:00 2001 From: sijun-yang Date: Tue, 8 Jul 2025 22:59:59 +0900 Subject: [PATCH 06/28] Format code to match style guide Signed-off-by: sijun-yang --- .../generator/language/AnnotationHolder.java | 82 ++--- .../language/MultipleAnnotationContainer.java | 270 ++++++++-------- .../MultipleAnnotationContainerTests.java | 294 +++++++++--------- 3 files changed, 322 insertions(+), 324 deletions(-) diff --git a/initializr-generator/src/main/java/io/spring/initializr/generator/language/AnnotationHolder.java b/initializr-generator/src/main/java/io/spring/initializr/generator/language/AnnotationHolder.java index cecaabeb6e..423e0a2f6b 100644 --- a/initializr-generator/src/main/java/io/spring/initializr/generator/language/AnnotationHolder.java +++ b/initializr-generator/src/main/java/io/spring/initializr/generator/language/AnnotationHolder.java @@ -29,52 +29,52 @@ */ public interface AnnotationHolder { - /** - * Specify if this holder is empty. - * @return {@code true} if no annotation is registered - */ - boolean isEmpty(); + /** + * Specify if this holder is empty. + * @return {@code true} if no annotation is registered + */ + boolean isEmpty(); - /** - * Specify if this holder has 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 - */ - boolean has(ClassName className); + /** + * Specify if this holder has 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 + */ + boolean has(ClassName className); - /** - * Return the {@link Annotation annotations}. - * @return the annotations - */ - Stream values(); + /** + * Return the {@link Annotation annotations}. + * @return the annotations + */ + Stream values(); - /** - * 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} - */ - void add(ClassName className, Consumer annotation); + /** + * 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} + */ + void add(ClassName className, Consumer annotation); - /** - * 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 - */ - void add(ClassName className); + /** + * 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 + */ + void add(ClassName className); - /** - * 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 - */ - boolean remove(ClassName className); + /** + * 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 + */ + boolean remove(ClassName className); - /** - * Create a deep copy of this annotation holder. - * @return a new annotation holder with the same annotations - */ - AnnotationHolder deepCopy(); + /** + * Create a deep copy of this annotation holder. + * @return a new annotation holder with the same annotations + */ + AnnotationHolder deepCopy(); } diff --git a/initializr-generator/src/main/java/io/spring/initializr/generator/language/MultipleAnnotationContainer.java b/initializr-generator/src/main/java/io/spring/initializr/generator/language/MultipleAnnotationContainer.java index 7ff28cb8ea..461ce4c8f3 100644 --- a/initializr-generator/src/main/java/io/spring/initializr/generator/language/MultipleAnnotationContainer.java +++ b/initializr-generator/src/main/java/io/spring/initializr/generator/language/MultipleAnnotationContainer.java @@ -29,141 +29,139 @@ */ public class MultipleAnnotationContainer implements AnnotationHolder { - private final Map> annotations; - - public MultipleAnnotationContainer() { - this(new LinkedHashMap<>()); - } - - private MultipleAnnotationContainer(Map> annotations) { - this.annotations = annotations; - } - - @Override - public boolean isEmpty() { - return this.annotations.isEmpty() || - this.annotations.values().stream().allMatch(List::isEmpty); - } - - @Override - public boolean has(ClassName className) { - List builders = this.annotations.get(className); - return builders != null && !builders.isEmpty(); - } - - @Override - public Stream values() { - return this.annotations.values().stream() - .flatMap(List::stream) - .map(Builder::build); - } - - /** - * Add operation is not supported for {@link MultipleAnnotationContainer}. - * Use {@link #addToList(ClassName, Consumer)} instead to explicitly add to the list. - * @throws UnsupportedOperationException always - */ - @Override - public void add(ClassName className, Consumer annotation) { - throw new UnsupportedOperationException( - "Add operation with potential overwrite is not supported for MultipleAnnotationContainer. " + - "Use addToList() to explicitly add annotations to the list."); - } - - /** - * Add operation is not supported for {@link MultipleAnnotationContainer}. - * Use {@link #addToList(ClassName)} instead to explicitly add to the list. - * @throws UnsupportedOperationException always - */ - @Override - public void add(ClassName className) { - throw new UnsupportedOperationException( - "Add operation with potential overwrite is not supported for MultipleAnnotationContainer. " + - "Use addToList() to explicitly add annotations to the list."); - } - - /** - * Add an annotation to the list of annotations with the specified class name. - * Always adds a new annotation. - * @param className the class name of an annotation - * @param annotation a {@link Consumer} to customize the {@link Annotation} - */ - public void addToList(ClassName className, Consumer annotation) { - List builders = this.annotations.computeIfAbsent(className, (key) -> new ArrayList<>()); - Builder builder = new Builder(className); - if (annotation != null) { - annotation.accept(builder); - } - builders.add(builder); - } - - /** - * Add an annotation to the list of annotations with the specified class name. - * Always adds a new annotation. - * @param className the class name of an annotation - */ - public void addToList(ClassName className) { - addToList(className, null); - } - - /** - * Return all annotations with the specified class name. - * @param className the class name of an annotation - * @return a stream of all annotations with the specified class name - */ - public Stream valuesOf(ClassName className) { - List builders = this.annotations.get(className); - if (builders == null || builders.isEmpty()) { - return Stream.empty(); - } - return builders.stream().map(Builder::build); - } - - /** - * Return the number of annotations with the specified class name. - * @param className the class name of an annotation - * @return the count of annotations with the specified class name - */ - public int countOf(ClassName className) { - List builders = this.annotations.get(className); - return builders != null ? builders.size() : 0; - } - - /** - * Remove all annotations with the specified class name. - * @param className the class name of the annotation - * @return the number of annotations that were removed - */ - public int removeAll(ClassName className) { - List builders = this.annotations.remove(className); - return builders != null ? builders.size() : 0; - } - - @Override - public boolean remove(ClassName className) { - int removedCount = removeAll(className); - return removedCount > 0; - } - - /** - * Check if this container has multiple annotations with the specified class name. - * @param className the class name of an annotation - * @return {@code true} if there are multiple annotations with the specified class name - */ - public boolean hasMultiple(ClassName className) { - List builders = this.annotations.get(className); - return builders != null && builders.size() > 1; - } - - @Override - public MultipleAnnotationContainer deepCopy() { - Map> copy = new LinkedHashMap<>(); - this.annotations.forEach((className, builders) -> { - List buildersCopy = new ArrayList<>(); - builders.forEach(builder -> buildersCopy.add(new Builder(builder))); - copy.put(className, buildersCopy); - }); - return new MultipleAnnotationContainer(copy); - } + private final Map> annotations; + + public MultipleAnnotationContainer() { + this(new LinkedHashMap<>()); + } + + private MultipleAnnotationContainer(Map> annotations) { + this.annotations = annotations; + } + + @Override + public boolean isEmpty() { + return this.annotations.isEmpty() || this.annotations.values().stream().allMatch(List::isEmpty); + } + + @Override + public boolean has(ClassName className) { + List builders = this.annotations.get(className); + return builders != null && !builders.isEmpty(); + } + + @Override + public Stream values() { + return this.annotations.values().stream().flatMap(List::stream).map(Builder::build); + } + + /** + * Add operation is not supported for {@link MultipleAnnotationContainer}. Use + * {@link #addToList(ClassName, Consumer)} instead to explicitly add to the list. + * @throws UnsupportedOperationException always + */ + @Override + public void add(ClassName className, Consumer annotation) { + throw new UnsupportedOperationException( + "Add operation with potential overwrite is not supported for MultipleAnnotationContainer. " + + "Use addToList() to explicitly add annotations to the list."); + } + + /** + * Add operation is not supported for {@link MultipleAnnotationContainer}. Use + * {@link #addToList(ClassName)} instead to explicitly add to the list. + * @throws UnsupportedOperationException always + */ + @Override + public void add(ClassName className) { + throw new UnsupportedOperationException( + "Add operation with potential overwrite is not supported for MultipleAnnotationContainer. " + + "Use addToList() to explicitly add annotations to the list."); + } + + /** + * Add an annotation to the list of annotations with the specified class name. Always + * adds a new annotation. + * @param className the class name of an annotation + * @param annotation a {@link Consumer} to customize the {@link Annotation} + */ + public void addToList(ClassName className, Consumer annotation) { + List builders = this.annotations.computeIfAbsent(className, (key) -> new ArrayList<>()); + Builder builder = new Builder(className); + if (annotation != null) { + annotation.accept(builder); + } + builders.add(builder); + } + + /** + * Add an annotation to the list of annotations with the specified class name. Always + * adds a new annotation. + * @param className the class name of an annotation + */ + public void addToList(ClassName className) { + addToList(className, null); + } + + /** + * Return all annotations with the specified class name. + * @param className the class name of an annotation + * @return a stream of all annotations with the specified class name + */ + public Stream valuesOf(ClassName className) { + List builders = this.annotations.get(className); + if (builders == null || builders.isEmpty()) { + return Stream.empty(); + } + return builders.stream().map(Builder::build); + } + + /** + * Return the number of annotations with the specified class name. + * @param className the class name of an annotation + * @return the count of annotations with the specified class name + */ + public int countOf(ClassName className) { + List builders = this.annotations.get(className); + return builders != null ? builders.size() : 0; + } + + /** + * Remove all annotations with the specified class name. + * @param className the class name of the annotation + * @return the number of annotations that were removed + */ + public int removeAll(ClassName className) { + List builders = this.annotations.remove(className); + return builders != null ? builders.size() : 0; + } + + @Override + public boolean remove(ClassName className) { + int removedCount = removeAll(className); + return removedCount > 0; + } + + /** + * Check if this container has multiple annotations with the specified class name. + * @param className the class name of an annotation + * @return {@code true} if there are multiple annotations with the specified class + * name + */ + public boolean hasMultiple(ClassName className) { + List builders = this.annotations.get(className); + return builders != null && builders.size() > 1; + } + + @Override + public MultipleAnnotationContainer deepCopy() { + Map> copy = new LinkedHashMap<>(); + this.annotations.forEach((className, builders) -> { + List buildersCopy = new ArrayList<>(); + builders.forEach(builder -> buildersCopy.add(new Builder(builder))); + copy.put(className, buildersCopy); + }); + return new MultipleAnnotationContainer(copy); + } } diff --git a/initializr-generator/src/test/java/io/spring/initializr/generator/language/MultipleAnnotationContainerTests.java b/initializr-generator/src/test/java/io/spring/initializr/generator/language/MultipleAnnotationContainerTests.java index 5591882896..1f7dac933d 100644 --- a/initializr-generator/src/test/java/io/spring/initializr/generator/language/MultipleAnnotationContainerTests.java +++ b/initializr-generator/src/test/java/io/spring/initializr/generator/language/MultipleAnnotationContainerTests.java @@ -15,151 +15,151 @@ */ class MultipleAnnotationContainerTests { - private static final ClassName TEST_CLASS_NAME = ClassName.of("com.example.Test"); - - private static final ClassName OTHER_CLASS_NAME = ClassName.of("com.example.Other"); - - @Test - void isEmptyWithEmptyContainer() { - MultipleAnnotationContainer container = new MultipleAnnotationContainer(); - assertThat(container.isEmpty()).isTrue(); - } - - @Test - void isEmptyWithAnnotation() { - MultipleAnnotationContainer container = new MultipleAnnotationContainer(); - container.addToList(TEST_CLASS_NAME, builder -> builder.add("value", "test")); - assertThat(container.isEmpty()).isFalse(); - } - - @Test - void hasWithMatchingAnnotation() { - MultipleAnnotationContainer container = new MultipleAnnotationContainer(); - container.addToList(TEST_CLASS_NAME, builder -> builder.add("value", "test")); - assertThat(container.has(TEST_CLASS_NAME)).isTrue(); - } - - @Test - void hasWithNonMatchingAnnotation() { - MultipleAnnotationContainer container = new MultipleAnnotationContainer(); - container.addToList(TEST_CLASS_NAME, builder -> builder.add("value", "test")); - assertThat(container.has(OTHER_CLASS_NAME)).isFalse(); - } - - @Test - void valuesShouldReturnAllAnnotations() { - MultipleAnnotationContainer container = new MultipleAnnotationContainer(); - container.addToList(TEST_CLASS_NAME, builder -> builder.add("value", "one")); - container.addToList(TEST_CLASS_NAME, builder -> builder.add("value", "two")); - List annotations = container.values().collect(Collectors.toList()); - assertThat(annotations).hasSize(2); - assertThat(annotations).allMatch(annotation -> annotation.getClassName().equals(TEST_CLASS_NAME)); - } - - @Test - void valuesOfShouldReturnMatchingAnnotationsOnly() { - MultipleAnnotationContainer container = new MultipleAnnotationContainer(); - container.addToList(TEST_CLASS_NAME, builder -> builder.add("value", "one")); - container.addToList(OTHER_CLASS_NAME, builder -> builder.add("name", "other")); - List annotations = container.valuesOf(TEST_CLASS_NAME).collect(Collectors.toList()); - assertThat(annotations).hasSize(1); - assertThat(annotations.get(0).getAttributes()).singleElement().satisfies((attribute) -> { - assertThat(attribute.getName()).isEqualTo("value"); - assertThat(attribute.getValues()).containsExactly("one"); - }); - } - - @Test - void valuesOfShouldReturnEmptyStreamForNonExistingClassName() { - MultipleAnnotationContainer container = new MultipleAnnotationContainer(); - container.addToList(TEST_CLASS_NAME); - assertThat(container.valuesOf(OTHER_CLASS_NAME)).isEmpty(); - } - - @Test - void countOfShouldReturnCorrectCount() { - MultipleAnnotationContainer container = new MultipleAnnotationContainer(); - container.addToList(TEST_CLASS_NAME); - container.addToList(TEST_CLASS_NAME); - assertThat(container.countOf(TEST_CLASS_NAME)).isEqualTo(2); - assertThat(container.countOf(OTHER_CLASS_NAME)).isZero(); - } - - @Test - void removeAllShouldRemoveAllAnnotationsOfClassName() { - MultipleAnnotationContainer container = new MultipleAnnotationContainer(); - container.addToList(TEST_CLASS_NAME); - container.addToList(TEST_CLASS_NAME); - int removed = container.removeAll(TEST_CLASS_NAME); - assertThat(removed).isEqualTo(2); - assertThat(container.has(TEST_CLASS_NAME)).isFalse(); - } - - @Test - void removeShouldReturnFalseIfAllAnnotationRemoved() { - MultipleAnnotationContainer container = new MultipleAnnotationContainer(); - container.addToList(TEST_CLASS_NAME); - container.addToList(TEST_CLASS_NAME); - assertThat(container.remove(TEST_CLASS_NAME)).isTrue(); - assertThat(container.has(TEST_CLASS_NAME)).isFalse(); - } - - @Test - void removeShouldReturnFalseIfNoAnnotationRemoved() { - MultipleAnnotationContainer container = new MultipleAnnotationContainer(); - assertThat(container.remove(TEST_CLASS_NAME)).isFalse(); - } - - @Test - void hasMultipleReturnsTrueForMultipleAnnotations() { - MultipleAnnotationContainer container = new MultipleAnnotationContainer(); - container.addToList(TEST_CLASS_NAME); - container.addToList(TEST_CLASS_NAME); - assertThat(container.hasMultiple(TEST_CLASS_NAME)).isTrue(); - assertThat(container.hasMultiple(OTHER_CLASS_NAME)).isFalse(); - } - - @Test - void addUnsupportedMethodsThrowException() { - MultipleAnnotationContainer container = new MultipleAnnotationContainer(); - assertThatThrownBy(() -> container.add(TEST_CLASS_NAME)) - .isInstanceOf(UnsupportedOperationException.class); - assertThatThrownBy(() -> container.add(TEST_CLASS_NAME, builder -> {})) - .isInstanceOf(UnsupportedOperationException.class); - } - - @Test - void deepCopyShouldCreateDistinctObjectReferences() { - MultipleAnnotationContainer container = new MultipleAnnotationContainer(); - container.addToList(TEST_CLASS_NAME, builder -> builder.add("value", "test")); - - MultipleAnnotationContainer copy = container.deepCopy(); - - assertThat(copy).isNotSameAs(container); - - List originalAnnotations = container.valuesOf(TEST_CLASS_NAME).collect(Collectors.toList()); - List copiedAnnotations = copy.valuesOf(TEST_CLASS_NAME).collect(Collectors.toList()); - - assertThat(copiedAnnotations).hasSize(originalAnnotations.size()); - for (int i = 0; i < originalAnnotations.size(); i++) { - assertThat(copiedAnnotations.get(i)).isNotSameAs(originalAnnotations.get(i)); - } - } - - @Test - void deepCopyMutationShouldNotAffectOriginal() { - MultipleAnnotationContainer container = new MultipleAnnotationContainer(); - container.addToList(TEST_CLASS_NAME, builder -> builder.add("value", "original")); - - MultipleAnnotationContainer copy = container.deepCopy(); - - copy.addToList(TEST_CLASS_NAME, builder -> builder.add("value", "new")); - copy.addToList(OTHER_CLASS_NAME, builder -> builder.add("other", "test")); - - assertThat(container.countOf(TEST_CLASS_NAME)).isEqualTo(1); - assertThat(container.has(OTHER_CLASS_NAME)).isFalse(); - - assertThat(copy.countOf(TEST_CLASS_NAME)).isEqualTo(2); - assertThat(copy.has(OTHER_CLASS_NAME)).isTrue(); - } + private static final ClassName TEST_CLASS_NAME = ClassName.of("com.example.Test"); + + private static final ClassName OTHER_CLASS_NAME = ClassName.of("com.example.Other"); + + @Test + void isEmptyWithEmptyContainer() { + MultipleAnnotationContainer container = new MultipleAnnotationContainer(); + assertThat(container.isEmpty()).isTrue(); + } + + @Test + void isEmptyWithAnnotation() { + MultipleAnnotationContainer container = new MultipleAnnotationContainer(); + container.addToList(TEST_CLASS_NAME, builder -> builder.add("value", "test")); + assertThat(container.isEmpty()).isFalse(); + } + + @Test + void hasWithMatchingAnnotation() { + MultipleAnnotationContainer container = new MultipleAnnotationContainer(); + container.addToList(TEST_CLASS_NAME, builder -> builder.add("value", "test")); + assertThat(container.has(TEST_CLASS_NAME)).isTrue(); + } + + @Test + void hasWithNonMatchingAnnotation() { + MultipleAnnotationContainer container = new MultipleAnnotationContainer(); + container.addToList(TEST_CLASS_NAME, builder -> builder.add("value", "test")); + assertThat(container.has(OTHER_CLASS_NAME)).isFalse(); + } + + @Test + void valuesShouldReturnAllAnnotations() { + MultipleAnnotationContainer container = new MultipleAnnotationContainer(); + container.addToList(TEST_CLASS_NAME, builder -> builder.add("value", "one")); + container.addToList(TEST_CLASS_NAME, builder -> builder.add("value", "two")); + List annotations = container.values().collect(Collectors.toList()); + assertThat(annotations).hasSize(2); + assertThat(annotations).allMatch(annotation -> annotation.getClassName().equals(TEST_CLASS_NAME)); + } + + @Test + void valuesOfShouldReturnMatchingAnnotationsOnly() { + MultipleAnnotationContainer container = new MultipleAnnotationContainer(); + container.addToList(TEST_CLASS_NAME, builder -> builder.add("value", "one")); + container.addToList(OTHER_CLASS_NAME, builder -> builder.add("name", "other")); + List annotations = container.valuesOf(TEST_CLASS_NAME).collect(Collectors.toList()); + assertThat(annotations).hasSize(1); + assertThat(annotations.get(0).getAttributes()).singleElement().satisfies((attribute) -> { + assertThat(attribute.getName()).isEqualTo("value"); + assertThat(attribute.getValues()).containsExactly("one"); + }); + } + + @Test + void valuesOfShouldReturnEmptyStreamForNonExistingClassName() { + MultipleAnnotationContainer container = new MultipleAnnotationContainer(); + container.addToList(TEST_CLASS_NAME); + assertThat(container.valuesOf(OTHER_CLASS_NAME)).isEmpty(); + } + + @Test + void countOfShouldReturnCorrectCount() { + MultipleAnnotationContainer container = new MultipleAnnotationContainer(); + container.addToList(TEST_CLASS_NAME); + container.addToList(TEST_CLASS_NAME); + assertThat(container.countOf(TEST_CLASS_NAME)).isEqualTo(2); + assertThat(container.countOf(OTHER_CLASS_NAME)).isZero(); + } + + @Test + void removeAllShouldRemoveAllAnnotationsOfClassName() { + MultipleAnnotationContainer container = new MultipleAnnotationContainer(); + container.addToList(TEST_CLASS_NAME); + container.addToList(TEST_CLASS_NAME); + int removed = container.removeAll(TEST_CLASS_NAME); + assertThat(removed).isEqualTo(2); + assertThat(container.has(TEST_CLASS_NAME)).isFalse(); + } + + @Test + void removeShouldReturnFalseIfAllAnnotationRemoved() { + MultipleAnnotationContainer container = new MultipleAnnotationContainer(); + container.addToList(TEST_CLASS_NAME); + container.addToList(TEST_CLASS_NAME); + assertThat(container.remove(TEST_CLASS_NAME)).isTrue(); + assertThat(container.has(TEST_CLASS_NAME)).isFalse(); + } + + @Test + void removeShouldReturnFalseIfNoAnnotationRemoved() { + MultipleAnnotationContainer container = new MultipleAnnotationContainer(); + assertThat(container.remove(TEST_CLASS_NAME)).isFalse(); + } + + @Test + void hasMultipleReturnsTrueForMultipleAnnotations() { + MultipleAnnotationContainer container = new MultipleAnnotationContainer(); + container.addToList(TEST_CLASS_NAME); + container.addToList(TEST_CLASS_NAME); + assertThat(container.hasMultiple(TEST_CLASS_NAME)).isTrue(); + assertThat(container.hasMultiple(OTHER_CLASS_NAME)).isFalse(); + } + + @Test + void addUnsupportedMethodsThrowException() { + MultipleAnnotationContainer container = new MultipleAnnotationContainer(); + assertThatThrownBy(() -> container.add(TEST_CLASS_NAME)).isInstanceOf(UnsupportedOperationException.class); + assertThatThrownBy(() -> container.add(TEST_CLASS_NAME, builder -> { + })).isInstanceOf(UnsupportedOperationException.class); + } + + @Test + void deepCopyShouldCreateDistinctObjectReferences() { + MultipleAnnotationContainer container = new MultipleAnnotationContainer(); + container.addToList(TEST_CLASS_NAME, builder -> builder.add("value", "test")); + + MultipleAnnotationContainer copy = container.deepCopy(); + + assertThat(copy).isNotSameAs(container); + + List originalAnnotations = container.valuesOf(TEST_CLASS_NAME).collect(Collectors.toList()); + List copiedAnnotations = copy.valuesOf(TEST_CLASS_NAME).collect(Collectors.toList()); + + assertThat(copiedAnnotations).hasSize(originalAnnotations.size()); + for (int i = 0; i < originalAnnotations.size(); i++) { + assertThat(copiedAnnotations.get(i)).isNotSameAs(originalAnnotations.get(i)); + } + } + + @Test + void deepCopyMutationShouldNotAffectOriginal() { + MultipleAnnotationContainer container = new MultipleAnnotationContainer(); + container.addToList(TEST_CLASS_NAME, builder -> builder.add("value", "original")); + + MultipleAnnotationContainer copy = container.deepCopy(); + + copy.addToList(TEST_CLASS_NAME, builder -> builder.add("value", "new")); + copy.addToList(OTHER_CLASS_NAME, builder -> builder.add("other", "test")); + + assertThat(container.countOf(TEST_CLASS_NAME)).isEqualTo(1); + assertThat(container.has(OTHER_CLASS_NAME)).isFalse(); + + assertThat(copy.countOf(TEST_CLASS_NAME)).isEqualTo(2); + assertThat(copy.has(OTHER_CLASS_NAME)).isTrue(); + } + } From cf4734cbf71bb89160125b0ffb5006b9b29465ce Mon Sep 17 00:00:00 2001 From: sijun-yang Date: Tue, 8 Jul 2025 23:22:00 +0900 Subject: [PATCH 07/28] Format code to match style guide Signed-off-by: sijun-yang --- .../generator/language/AnnotationHolder.java | 4 +- .../language/MultipleAnnotationContainer.java | 15 ++++--- .../MultipleAnnotationContainerTests.java | 42 +++++++++++++------ 3 files changed, 40 insertions(+), 21 deletions(-) diff --git a/initializr-generator/src/main/java/io/spring/initializr/generator/language/AnnotationHolder.java b/initializr-generator/src/main/java/io/spring/initializr/generator/language/AnnotationHolder.java index 423e0a2f6b..c34a3b1124 100644 --- a/initializr-generator/src/main/java/io/spring/initializr/generator/language/AnnotationHolder.java +++ b/initializr-generator/src/main/java/io/spring/initializr/generator/language/AnnotationHolder.java @@ -16,11 +16,11 @@ package io.spring.initializr.generator.language; -import io.spring.initializr.generator.language.Annotation.Builder; - import java.util.function.Consumer; import java.util.stream.Stream; +import io.spring.initializr.generator.language.Annotation.Builder; + /** * A holder for {@linkplain Annotation annotations} defined on an annotated element. * diff --git a/initializr-generator/src/main/java/io/spring/initializr/generator/language/MultipleAnnotationContainer.java b/initializr-generator/src/main/java/io/spring/initializr/generator/language/MultipleAnnotationContainer.java index 461ce4c8f3..c6fb078f2d 100644 --- a/initializr-generator/src/main/java/io/spring/initializr/generator/language/MultipleAnnotationContainer.java +++ b/initializr-generator/src/main/java/io/spring/initializr/generator/language/MultipleAnnotationContainer.java @@ -16,12 +16,15 @@ package io.spring.initializr.generator.language; -import io.spring.initializr.generator.language.Annotation.Builder; - -import java.util.*; +import java.util.ArrayList; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; import java.util.function.Consumer; import java.util.stream.Stream; +import io.spring.initializr.generator.language.Annotation.Builder; + /** * An {@link AnnotationHolder} implementation that can hold multiple annotations per type. * @@ -123,7 +126,7 @@ public Stream valuesOf(ClassName className) { */ public int countOf(ClassName className) { List builders = this.annotations.get(className); - return builders != null ? builders.size() : 0; + return (builders != null) ? builders.size() : 0; } /** @@ -133,7 +136,7 @@ public int countOf(ClassName className) { */ public int removeAll(ClassName className) { List builders = this.annotations.remove(className); - return builders != null ? builders.size() : 0; + return (builders != null) ? builders.size() : 0; } @Override @@ -158,7 +161,7 @@ public MultipleAnnotationContainer deepCopy() { Map> copy = new LinkedHashMap<>(); this.annotations.forEach((className, builders) -> { List buildersCopy = new ArrayList<>(); - builders.forEach(builder -> buildersCopy.add(new Builder(builder))); + builders.forEach((builder) -> buildersCopy.add(new Builder(builder))); copy.put(className, buildersCopy); }); return new MultipleAnnotationContainer(copy); diff --git a/initializr-generator/src/test/java/io/spring/initializr/generator/language/MultipleAnnotationContainerTests.java b/initializr-generator/src/test/java/io/spring/initializr/generator/language/MultipleAnnotationContainerTests.java index 1f7dac933d..c7895f2bb8 100644 --- a/initializr-generator/src/test/java/io/spring/initializr/generator/language/MultipleAnnotationContainerTests.java +++ b/initializr-generator/src/test/java/io/spring/initializr/generator/language/MultipleAnnotationContainerTests.java @@ -1,3 +1,19 @@ +/* + * Copyright 2012 - present 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; @@ -28,39 +44,39 @@ void isEmptyWithEmptyContainer() { @Test void isEmptyWithAnnotation() { MultipleAnnotationContainer container = new MultipleAnnotationContainer(); - container.addToList(TEST_CLASS_NAME, builder -> builder.add("value", "test")); + container.addToList(TEST_CLASS_NAME, (builder) -> builder.add("value", "test")); assertThat(container.isEmpty()).isFalse(); } @Test void hasWithMatchingAnnotation() { MultipleAnnotationContainer container = new MultipleAnnotationContainer(); - container.addToList(TEST_CLASS_NAME, builder -> builder.add("value", "test")); + container.addToList(TEST_CLASS_NAME, (builder) -> builder.add("value", "test")); assertThat(container.has(TEST_CLASS_NAME)).isTrue(); } @Test void hasWithNonMatchingAnnotation() { MultipleAnnotationContainer container = new MultipleAnnotationContainer(); - container.addToList(TEST_CLASS_NAME, builder -> builder.add("value", "test")); + container.addToList(TEST_CLASS_NAME, (builder) -> builder.add("value", "test")); assertThat(container.has(OTHER_CLASS_NAME)).isFalse(); } @Test void valuesShouldReturnAllAnnotations() { MultipleAnnotationContainer container = new MultipleAnnotationContainer(); - container.addToList(TEST_CLASS_NAME, builder -> builder.add("value", "one")); - container.addToList(TEST_CLASS_NAME, builder -> builder.add("value", "two")); + container.addToList(TEST_CLASS_NAME, (builder) -> builder.add("value", "one")); + container.addToList(TEST_CLASS_NAME, (builder) -> builder.add("value", "two")); List annotations = container.values().collect(Collectors.toList()); assertThat(annotations).hasSize(2); - assertThat(annotations).allMatch(annotation -> annotation.getClassName().equals(TEST_CLASS_NAME)); + assertThat(annotations).allMatch((annotation) -> annotation.getClassName().equals(TEST_CLASS_NAME)); } @Test void valuesOfShouldReturnMatchingAnnotationsOnly() { MultipleAnnotationContainer container = new MultipleAnnotationContainer(); - container.addToList(TEST_CLASS_NAME, builder -> builder.add("value", "one")); - container.addToList(OTHER_CLASS_NAME, builder -> builder.add("name", "other")); + container.addToList(TEST_CLASS_NAME, (builder) -> builder.add("value", "one")); + container.addToList(OTHER_CLASS_NAME, (builder) -> builder.add("name", "other")); List annotations = container.valuesOf(TEST_CLASS_NAME).collect(Collectors.toList()); assertThat(annotations).hasSize(1); assertThat(annotations.get(0).getAttributes()).singleElement().satisfies((attribute) -> { @@ -123,14 +139,14 @@ void hasMultipleReturnsTrueForMultipleAnnotations() { void addUnsupportedMethodsThrowException() { MultipleAnnotationContainer container = new MultipleAnnotationContainer(); assertThatThrownBy(() -> container.add(TEST_CLASS_NAME)).isInstanceOf(UnsupportedOperationException.class); - assertThatThrownBy(() -> container.add(TEST_CLASS_NAME, builder -> { + assertThatThrownBy(() -> container.add(TEST_CLASS_NAME, (builder) -> { })).isInstanceOf(UnsupportedOperationException.class); } @Test void deepCopyShouldCreateDistinctObjectReferences() { MultipleAnnotationContainer container = new MultipleAnnotationContainer(); - container.addToList(TEST_CLASS_NAME, builder -> builder.add("value", "test")); + container.addToList(TEST_CLASS_NAME, (builder) -> builder.add("value", "test")); MultipleAnnotationContainer copy = container.deepCopy(); @@ -148,12 +164,12 @@ void deepCopyShouldCreateDistinctObjectReferences() { @Test void deepCopyMutationShouldNotAffectOriginal() { MultipleAnnotationContainer container = new MultipleAnnotationContainer(); - container.addToList(TEST_CLASS_NAME, builder -> builder.add("value", "original")); + container.addToList(TEST_CLASS_NAME, (builder) -> builder.add("value", "original")); MultipleAnnotationContainer copy = container.deepCopy(); - copy.addToList(TEST_CLASS_NAME, builder -> builder.add("value", "new")); - copy.addToList(OTHER_CLASS_NAME, builder -> builder.add("other", "test")); + copy.addToList(TEST_CLASS_NAME, (builder) -> builder.add("value", "new")); + copy.addToList(OTHER_CLASS_NAME, (builder) -> builder.add("other", "test")); assertThat(container.countOf(TEST_CLASS_NAME)).isEqualTo(1); assertThat(container.has(OTHER_CLASS_NAME)).isFalse(); From 99401a32851e3245950120f891db36572a553223 Mon Sep 17 00:00:00 2001 From: sijun-yang Date: Tue, 8 Jul 2025 23:44:15 +0900 Subject: [PATCH 08/28] Update comments Signed-off-by: sijun-yang --- .../initializr/generator/language/AnnotationContainer.java | 2 +- .../spring/initializr/generator/language/AnnotationHolder.java | 1 - .../generator/language/MultipleAnnotationContainer.java | 2 +- 3 files changed, 2 insertions(+), 3 deletions(-) diff --git a/initializr-generator/src/main/java/io/spring/initializr/generator/language/AnnotationContainer.java b/initializr-generator/src/main/java/io/spring/initializr/generator/language/AnnotationContainer.java index 5f9e9fc3bb..07b037b9f6 100644 --- a/initializr-generator/src/main/java/io/spring/initializr/generator/language/AnnotationContainer.java +++ b/initializr-generator/src/main/java/io/spring/initializr/generator/language/AnnotationContainer.java @@ -24,7 +24,7 @@ import io.spring.initializr.generator.language.Annotation.Builder; /** - * An {@link AnnotationHolder} implementation that holds at most one annotation per type. + * An {@link AnnotationHolder} implementation that holds a single annotation per type. * * @author Stephane Nicoll * @author Sijun Yang diff --git a/initializr-generator/src/main/java/io/spring/initializr/generator/language/AnnotationHolder.java b/initializr-generator/src/main/java/io/spring/initializr/generator/language/AnnotationHolder.java index c34a3b1124..0462a3f786 100644 --- a/initializr-generator/src/main/java/io/spring/initializr/generator/language/AnnotationHolder.java +++ b/initializr-generator/src/main/java/io/spring/initializr/generator/language/AnnotationHolder.java @@ -24,7 +24,6 @@ /** * A holder for {@linkplain Annotation annotations} defined on an annotated element. * - * @author Stephane Nicoll * @author Sijun Yang */ public interface AnnotationHolder { diff --git a/initializr-generator/src/main/java/io/spring/initializr/generator/language/MultipleAnnotationContainer.java b/initializr-generator/src/main/java/io/spring/initializr/generator/language/MultipleAnnotationContainer.java index c6fb078f2d..e3ee45147e 100644 --- a/initializr-generator/src/main/java/io/spring/initializr/generator/language/MultipleAnnotationContainer.java +++ b/initializr-generator/src/main/java/io/spring/initializr/generator/language/MultipleAnnotationContainer.java @@ -26,7 +26,7 @@ import io.spring.initializr.generator.language.Annotation.Builder; /** - * An {@link AnnotationHolder} implementation that can hold multiple annotations per type. + * An {@link AnnotationHolder} implementation that holds multiple annotations per type. * * @author Sijun Yang */ From 12ccae536f5a2a9bc5f0dca3f0c5a1c074426758 Mon Sep 17 00:00:00 2001 From: sijun-yang Date: Wed, 9 Jul 2025 13:23:57 +0900 Subject: [PATCH 09/28] Add constructor for AnnotationHolder Signed-off-by: sijun-yang --- .../generator/language/groovy/GroovyTypeDeclaration.java | 5 +++++ .../generator/language/java/JavaTypeDeclaration.java | 5 +++++ .../generator/language/kotlin/KotlinTypeDeclaration.java | 5 +++++ 3 files changed, 15 insertions(+) diff --git a/initializr-generator/src/main/java/io/spring/initializr/generator/language/groovy/GroovyTypeDeclaration.java b/initializr-generator/src/main/java/io/spring/initializr/generator/language/groovy/GroovyTypeDeclaration.java index aee0fead04..d8a63b8e2d 100644 --- a/initializr-generator/src/main/java/io/spring/initializr/generator/language/groovy/GroovyTypeDeclaration.java +++ b/initializr-generator/src/main/java/io/spring/initializr/generator/language/groovy/GroovyTypeDeclaration.java @@ -19,6 +19,7 @@ import java.util.ArrayList; import java.util.List; +import io.spring.initializr.generator.language.AnnotationHolder; import io.spring.initializr.generator.language.TypeDeclaration; /** @@ -38,6 +39,10 @@ public class GroovyTypeDeclaration extends TypeDeclaration { super(name); } + GroovyTypeDeclaration(String name, AnnotationHolder annotations) { + super(name, annotations); + } + /** * Sets the modifiers. * @param modifiers the modifiers diff --git a/initializr-generator/src/main/java/io/spring/initializr/generator/language/java/JavaTypeDeclaration.java b/initializr-generator/src/main/java/io/spring/initializr/generator/language/java/JavaTypeDeclaration.java index b91e87a9d9..69ea2cc1fc 100644 --- a/initializr-generator/src/main/java/io/spring/initializr/generator/language/java/JavaTypeDeclaration.java +++ b/initializr-generator/src/main/java/io/spring/initializr/generator/language/java/JavaTypeDeclaration.java @@ -19,6 +19,7 @@ import java.util.ArrayList; import java.util.List; +import io.spring.initializr.generator.language.AnnotationHolder; import io.spring.initializr.generator.language.TypeDeclaration; /** @@ -39,6 +40,10 @@ public class JavaTypeDeclaration extends TypeDeclaration { super(name); } + JavaTypeDeclaration(String name, AnnotationHolder annotations) { + super(name, annotations); + } + /** * Sets the modifiers. * @param modifiers the modifiers diff --git a/initializr-generator/src/main/java/io/spring/initializr/generator/language/kotlin/KotlinTypeDeclaration.java b/initializr-generator/src/main/java/io/spring/initializr/generator/language/kotlin/KotlinTypeDeclaration.java index 71fb406716..aa7633bea5 100644 --- a/initializr-generator/src/main/java/io/spring/initializr/generator/language/kotlin/KotlinTypeDeclaration.java +++ b/initializr-generator/src/main/java/io/spring/initializr/generator/language/kotlin/KotlinTypeDeclaration.java @@ -20,6 +20,7 @@ import java.util.Arrays; import java.util.List; +import io.spring.initializr.generator.language.AnnotationHolder; import io.spring.initializr.generator.language.TypeDeclaration; /** @@ -39,6 +40,10 @@ public class KotlinTypeDeclaration extends TypeDeclaration { super(name); } + KotlinTypeDeclaration(String name, AnnotationHolder annotations) { + super(name, annotations); + } + /** * Sets the modifiers. * @param modifiers the modifiers From cc900e29c7148ac7e9b6beaa3f64b12de518a478 Mon Sep 17 00:00:00 2001 From: sijun-yang Date: Wed, 9 Jul 2025 13:44:40 +0900 Subject: [PATCH 10/28] Streamline constructor for AnnotationHolder Signed-off-by: sijun-yang --- .../groovy/GroovyFieldDeclaration.java | 20 ++++++++++++----- .../groovy/GroovyMethodDeclaration.java | 22 +++++++++++++------ .../language/java/JavaFieldDeclaration.java | 20 ++++++++++++----- .../language/java/JavaMethodDeclaration.java | 20 ++++++++++++----- .../kotlin/KotlinFunctionDeclaration.java | 20 ++++++++++++----- .../kotlin/KotlinPropertyDeclaration.java | 20 ++++++++++++----- 6 files changed, 85 insertions(+), 37 deletions(-) diff --git a/initializr-generator/src/main/java/io/spring/initializr/generator/language/groovy/GroovyFieldDeclaration.java b/initializr-generator/src/main/java/io/spring/initializr/generator/language/groovy/GroovyFieldDeclaration.java index 2a5268f974..d12daff309 100644 --- a/initializr-generator/src/main/java/io/spring/initializr/generator/language/groovy/GroovyFieldDeclaration.java +++ b/initializr-generator/src/main/java/io/spring/initializr/generator/language/groovy/GroovyFieldDeclaration.java @@ -39,8 +39,8 @@ public final class GroovyFieldDeclaration implements Annotatable { private final boolean initialized; - public GroovyFieldDeclaration(Builder builder, AnnotationHolder annotations) { - this.annotations = annotations; + private GroovyFieldDeclaration(Builder builder) { + this.annotations = builder.annotations; this.modifiers = builder.modifiers; this.name = builder.name; this.returnType = builder.returnType; @@ -48,10 +48,6 @@ public GroovyFieldDeclaration(Builder builder, AnnotationHolder annotations) { this.initialized = builder.initialized; } - private GroovyFieldDeclaration(Builder builder) { - this(builder, new AnnotationContainer()); - } - /** * Creates a new builder for the field with the given name. * @param name the name @@ -111,6 +107,8 @@ public boolean isInitialized() { */ public static final class Builder { + private AnnotationHolder annotations = new AnnotationContainer(); + private final String name; private String returnType; @@ -135,6 +133,16 @@ public Builder modifiers(int modifiers) { return this; } + /** + * Sets the annotation holder. + * @param annotations the annotation holder + * @return this for method chaining + */ + public Builder annotations(AnnotationHolder annotations) { + this.annotations = annotations; + return this; + } + /** * Sets the value. * @param value the value diff --git a/initializr-generator/src/main/java/io/spring/initializr/generator/language/groovy/GroovyMethodDeclaration.java b/initializr-generator/src/main/java/io/spring/initializr/generator/language/groovy/GroovyMethodDeclaration.java index 4c7de5f860..4e64ab0791 100644 --- a/initializr-generator/src/main/java/io/spring/initializr/generator/language/groovy/GroovyMethodDeclaration.java +++ b/initializr-generator/src/main/java/io/spring/initializr/generator/language/groovy/GroovyMethodDeclaration.java @@ -46,19 +46,15 @@ public final class GroovyMethodDeclaration implements Annotatable { private final CodeBlock code; - private GroovyMethodDeclaration(Builder builder, CodeBlock code, AnnotationHolder annotations) { - this.annotations = annotations; + public GroovyMethodDeclaration(Builder builder, CodeBlock code) { + this.annotations = builder.annotations; this.name = builder.name; this.returnType = builder.returnType; + this.parameters = new ArrayList<>(builder.parameters); this.modifiers = builder.modifiers; - this.parameters = List.copyOf(builder.parameters); this.code = code; } - public GroovyMethodDeclaration(Builder builder, CodeBlock code) { - this(builder, code, new AnnotationContainer()); - } - /** * Creates a new builder for the method with the given name. * @param name the name @@ -98,6 +94,8 @@ public AnnotationHolder annotations() { */ public static final class Builder { + private AnnotationHolder annotations = new AnnotationContainer(); + private final String name; private List parameters = new ArrayList<>(); @@ -140,6 +138,16 @@ public Builder parameters(Parameter... parameters) { return this; } + /** + * Sets the annotation holder. + * @param annotations the annotation holder + * @return this for method chaining + */ + public Builder annotations(AnnotationHolder annotations) { + this.annotations = annotations; + return this; + } + /** * Sets the body. * @param code the code for the body diff --git a/initializr-generator/src/main/java/io/spring/initializr/generator/language/java/JavaFieldDeclaration.java b/initializr-generator/src/main/java/io/spring/initializr/generator/language/java/JavaFieldDeclaration.java index e266515824..fbaec95472 100644 --- a/initializr-generator/src/main/java/io/spring/initializr/generator/language/java/JavaFieldDeclaration.java +++ b/initializr-generator/src/main/java/io/spring/initializr/generator/language/java/JavaFieldDeclaration.java @@ -39,8 +39,8 @@ public final class JavaFieldDeclaration implements Annotatable { private final boolean initialized; - public JavaFieldDeclaration(Builder builder, AnnotationHolder annotations) { - this.annotations = annotations; + private JavaFieldDeclaration(Builder builder) { + this.annotations = builder.annotations; this.modifiers = builder.modifiers; this.name = builder.name; this.returnType = builder.returnType; @@ -48,10 +48,6 @@ public JavaFieldDeclaration(Builder builder, AnnotationHolder annotations) { this.initialized = builder.initialized; } - private JavaFieldDeclaration(Builder builder) { - this(builder, new AnnotationContainer()); - } - /** * Creates a new builder for the field with the given name. * @param name the field name @@ -111,6 +107,8 @@ public boolean isInitialized() { */ public static final class Builder { + private AnnotationHolder annotations = new AnnotationContainer(); + private final String name; private String returnType; @@ -135,6 +133,16 @@ public Builder modifiers(int modifiers) { return this; } + /** + * Sets the annotation holder. + * @param annotations the annotation holder + * @return this for method chaining + */ + public Builder annotations(AnnotationHolder annotations) { + this.annotations = annotations; + return this; + } + /** * Sets the value. * @param value the value diff --git a/initializr-generator/src/main/java/io/spring/initializr/generator/language/java/JavaMethodDeclaration.java b/initializr-generator/src/main/java/io/spring/initializr/generator/language/java/JavaMethodDeclaration.java index 432926948f..0fd8fc9bad 100644 --- a/initializr-generator/src/main/java/io/spring/initializr/generator/language/java/JavaMethodDeclaration.java +++ b/initializr-generator/src/main/java/io/spring/initializr/generator/language/java/JavaMethodDeclaration.java @@ -45,8 +45,8 @@ public final class JavaMethodDeclaration implements Annotatable { private final CodeBlock code; - public JavaMethodDeclaration(Builder builder, CodeBlock code, AnnotationHolder annotations) { - this.annotations = annotations; + public JavaMethodDeclaration(Builder builder, CodeBlock code) { + this.annotations = builder.annotations; this.name = builder.name; this.returnType = builder.returnType; this.modifiers = builder.modifiers; @@ -54,10 +54,6 @@ public JavaMethodDeclaration(Builder builder, CodeBlock code, AnnotationHolder a this.code = code; } - private JavaMethodDeclaration(Builder builder, CodeBlock code) { - this(builder, code, new AnnotationContainer()); - } - /** * Creates a new builder for the method with the given name. * @param name the name @@ -97,6 +93,8 @@ public AnnotationHolder annotations() { */ public static final class Builder { + private AnnotationHolder annotations = new AnnotationContainer(); + private final String name; private List parameters = new ArrayList<>(); @@ -139,6 +137,16 @@ public Builder parameters(Parameter... parameters) { return this; } + /** + * Sets the annotation holder. + * @param annotations the annotation holder + * @return this for method chaining + */ + public Builder annotations(AnnotationHolder annotations) { + this.annotations = annotations; + return this; + } + /** * Sets the body. * @param code the code for the body diff --git a/initializr-generator/src/main/java/io/spring/initializr/generator/language/kotlin/KotlinFunctionDeclaration.java b/initializr-generator/src/main/java/io/spring/initializr/generator/language/kotlin/KotlinFunctionDeclaration.java index 5b250e06c1..77b4b3d565 100644 --- a/initializr-generator/src/main/java/io/spring/initializr/generator/language/kotlin/KotlinFunctionDeclaration.java +++ b/initializr-generator/src/main/java/io/spring/initializr/generator/language/kotlin/KotlinFunctionDeclaration.java @@ -45,8 +45,8 @@ public final class KotlinFunctionDeclaration implements Annotatable { private final CodeBlock code; - private KotlinFunctionDeclaration(Builder builder, CodeBlock code, AnnotationHolder annotations) { - this.annotations = annotations; + private KotlinFunctionDeclaration(Builder builder, CodeBlock code) { + this.annotations = builder.annotations; this.name = builder.name; this.returnType = builder.returnType; this.modifiers = builder.modifiers; @@ -54,10 +54,6 @@ private KotlinFunctionDeclaration(Builder builder, CodeBlock code, AnnotationHol this.code = code; } - private KotlinFunctionDeclaration(Builder builder, CodeBlock code) { - this(builder, code, new AnnotationContainer()); - } - /** * Creates a new builder for the function with the given name. * @param name the name @@ -97,6 +93,8 @@ public AnnotationHolder annotations() { */ public static final class Builder { + private AnnotationHolder annotations = new AnnotationContainer(); + private final String name; private List parameters = new ArrayList<>(); @@ -139,6 +137,16 @@ public Builder parameters(Parameter... parameters) { return this; } + /** + * Sets the annotation holder. + * @param annotations the annotation holder + * @return this for method chaining + */ + public Builder annotations(AnnotationHolder annotations) { + this.annotations = annotations; + return this; + } + /** * Sets the body. * @param code the code for the body diff --git a/initializr-generator/src/main/java/io/spring/initializr/generator/language/kotlin/KotlinPropertyDeclaration.java b/initializr-generator/src/main/java/io/spring/initializr/generator/language/kotlin/KotlinPropertyDeclaration.java index 3f0713e7e5..17aecfcce1 100644 --- a/initializr-generator/src/main/java/io/spring/initializr/generator/language/kotlin/KotlinPropertyDeclaration.java +++ b/initializr-generator/src/main/java/io/spring/initializr/generator/language/kotlin/KotlinPropertyDeclaration.java @@ -51,8 +51,8 @@ public final class KotlinPropertyDeclaration implements Annotatable { private final Accessor setter; - private KotlinPropertyDeclaration(Builder builder, AnnotationHolder annotations) { - this.annotations = annotations; + private KotlinPropertyDeclaration(Builder builder) { + this.annotations = builder.annotations; this.name = builder.name; this.returnType = builder.returnType; this.modifiers = new ArrayList<>(builder.modifiers); @@ -62,10 +62,6 @@ private KotlinPropertyDeclaration(Builder builder, AnnotationHolder annotatio this.setter = builder.setter; } - public KotlinPropertyDeclaration(Builder builder) { - this(builder, new AnnotationContainer()); - } - /** * Returns a builder for a {@code val} property with the given name. * @param name the name @@ -128,6 +124,8 @@ public AnnotationHolder annotations() { */ public abstract static class Builder> { + private AnnotationHolder annotations = new AnnotationContainer(); + private final boolean isVal; private final String name; @@ -191,6 +189,16 @@ public T modifiers(KotlinModifier... modifiers) { return self(); } + /** + * Sets the annotation holder. + * @param annotations the annotation holder + * @return this for method chaining + */ + public T annotations(AnnotationHolder annotations) { + this.annotations = annotations; + return self(); + } + /** * Sets no value. * @return the property declaration From 84bcecfad84c31bae40a2549b9921cf44d8d0146 Mon Sep 17 00:00:00 2001 From: sijun-yang Date: Wed, 9 Jul 2025 14:31:19 +0900 Subject: [PATCH 11/28] Add type declaration creation with annotations Signed-off-by: sijun-yang --- .../generator/language/CompilationUnit.java | 4 ++++ .../language/groovy/GroovyCompilationUnit.java | 14 ++++++++++++++ .../language/java/JavaCompilationUnit.java | 14 ++++++++++++++ .../language/kotlin/KotlinCompilationUnit.java | 14 ++++++++++++++ 4 files changed, 46 insertions(+) diff --git a/initializr-generator/src/main/java/io/spring/initializr/generator/language/CompilationUnit.java b/initializr-generator/src/main/java/io/spring/initializr/generator/language/CompilationUnit.java index c9ba65a8e9..7ff8c06f88 100644 --- a/initializr-generator/src/main/java/io/spring/initializr/generator/language/CompilationUnit.java +++ b/initializr-generator/src/main/java/io/spring/initializr/generator/language/CompilationUnit.java @@ -74,6 +74,10 @@ public List getTypeDeclarations() { return Collections.unmodifiableList(this.typeDeclarations); } + protected void addTypeDeclaration(T typeDeclaration) { + this.typeDeclarations.add(typeDeclaration); + } + protected abstract T doCreateTypeDeclaration(String name); } diff --git a/initializr-generator/src/main/java/io/spring/initializr/generator/language/groovy/GroovyCompilationUnit.java b/initializr-generator/src/main/java/io/spring/initializr/generator/language/groovy/GroovyCompilationUnit.java index 0217cde5a3..b7c4afffec 100644 --- a/initializr-generator/src/main/java/io/spring/initializr/generator/language/groovy/GroovyCompilationUnit.java +++ b/initializr-generator/src/main/java/io/spring/initializr/generator/language/groovy/GroovyCompilationUnit.java @@ -16,6 +16,7 @@ package io.spring.initializr.generator.language.groovy; +import io.spring.initializr.generator.language.AnnotationHolder; import io.spring.initializr.generator.language.CompilationUnit; /** @@ -34,4 +35,17 @@ protected GroovyTypeDeclaration doCreateTypeDeclaration(String name) { return new GroovyTypeDeclaration(name); } + /** + * Creates a new {@link GroovyTypeDeclaration} with the specified name and + * {@link AnnotationHolder}. + * @param name the name of the type declaration + * @param annotations the annotation holder to use + * @return a new GroovyTypeDeclaration instance + */ + public GroovyTypeDeclaration createTypeDeclaration(String name, AnnotationHolder annotations) { + GroovyTypeDeclaration typeDeclaration = new GroovyTypeDeclaration(name, annotations); + addTypeDeclaration(typeDeclaration); + return typeDeclaration; + } + } diff --git a/initializr-generator/src/main/java/io/spring/initializr/generator/language/java/JavaCompilationUnit.java b/initializr-generator/src/main/java/io/spring/initializr/generator/language/java/JavaCompilationUnit.java index 233e403210..e9be3128d3 100644 --- a/initializr-generator/src/main/java/io/spring/initializr/generator/language/java/JavaCompilationUnit.java +++ b/initializr-generator/src/main/java/io/spring/initializr/generator/language/java/JavaCompilationUnit.java @@ -16,6 +16,7 @@ package io.spring.initializr.generator.language.java; +import io.spring.initializr.generator.language.AnnotationHolder; import io.spring.initializr.generator.language.CompilationUnit; /** @@ -34,4 +35,17 @@ protected JavaTypeDeclaration doCreateTypeDeclaration(String name) { return new JavaTypeDeclaration(name); } + /** + * Creates a new {@link JavaTypeDeclaration} with the specified name and + * {@link AnnotationHolder}. + * @param name the name of the type declaration + * @param annotations the annotation holder to use + * @return a new JavaTypeDeclaration instance + */ + public JavaTypeDeclaration createTypeDeclaration(String name, AnnotationHolder annotations) { + JavaTypeDeclaration typeDeclaration = new JavaTypeDeclaration(name, annotations); + addTypeDeclaration(typeDeclaration); + return typeDeclaration; + } + } diff --git a/initializr-generator/src/main/java/io/spring/initializr/generator/language/kotlin/KotlinCompilationUnit.java b/initializr-generator/src/main/java/io/spring/initializr/generator/language/kotlin/KotlinCompilationUnit.java index 27be7a654d..fc041a52f2 100644 --- a/initializr-generator/src/main/java/io/spring/initializr/generator/language/kotlin/KotlinCompilationUnit.java +++ b/initializr-generator/src/main/java/io/spring/initializr/generator/language/kotlin/KotlinCompilationUnit.java @@ -19,6 +19,7 @@ import java.util.ArrayList; import java.util.List; +import io.spring.initializr.generator.language.AnnotationHolder; import io.spring.initializr.generator.language.CompilationUnit; /** @@ -39,6 +40,19 @@ protected KotlinTypeDeclaration doCreateTypeDeclaration(String name) { return new KotlinTypeDeclaration(name); } + /** + * Creates a new {@link KotlinTypeDeclaration} with the specified name and + * {@link AnnotationHolder}. + * @param name the name of the type declaration + * @param annotations the annotation holder to use + * @return a new KotlinTypeDeclaration instance + */ + public KotlinTypeDeclaration createTypeDeclaration(String name, AnnotationHolder annotations) { + KotlinTypeDeclaration typeDeclaration = new KotlinTypeDeclaration(name, annotations); + addTypeDeclaration(typeDeclaration); + return typeDeclaration; + } + /** * Adds the given function as a top level function. * @param function the function to add From f0444b9f6b70c67d239d0585588045422090a7a8 Mon Sep 17 00:00:00 2001 From: sijun-yang Date: Wed, 9 Jul 2025 17:12:35 +0900 Subject: [PATCH 12/28] Fix writeProperty to write annotations Signed-off-by: sijun-yang --- .../generator/language/kotlin/KotlinSourceCodeWriter.java | 1 + 1 file changed, 1 insertion(+) diff --git a/initializr-generator/src/main/java/io/spring/initializr/generator/language/kotlin/KotlinSourceCodeWriter.java b/initializr-generator/src/main/java/io/spring/initializr/generator/language/kotlin/KotlinSourceCodeWriter.java index 7fc79adc1a..ff46ef9f08 100644 --- a/initializr-generator/src/main/java/io/spring/initializr/generator/language/kotlin/KotlinSourceCodeWriter.java +++ b/initializr-generator/src/main/java/io/spring/initializr/generator/language/kotlin/KotlinSourceCodeWriter.java @@ -162,6 +162,7 @@ private String escapeKotlinKeywords(String packageName) { private void writeProperty(IndentingWriter writer, KotlinPropertyDeclaration propertyDeclaration) { writer.println(); + writeAnnotations(writer, propertyDeclaration); writeModifiers(writer, propertyDeclaration.getModifiers()); if (propertyDeclaration.isVal()) { writer.print("val "); From 870857bd348ffe4c40850573b9d5f33d087635b6 Mon Sep 17 00:00:00 2001 From: sijun-yang Date: Wed, 9 Jul 2025 17:20:39 +0900 Subject: [PATCH 13/28] Add tests for multiple annotations Signed-off-by: sijun-yang --- .../groovy/GroovySourceCodeWriterTests.java | 50 ++++++++++++++++++ .../java/JavaSourceCodeWriterTests.java | 51 +++++++++++++++++++ .../kotlin/KotlinSourceCodeWriterTests.java | 49 ++++++++++++++++++ 3 files changed, 150 insertions(+) diff --git a/initializr-generator/src/test/java/io/spring/initializr/generator/language/groovy/GroovySourceCodeWriterTests.java b/initializr-generator/src/test/java/io/spring/initializr/generator/language/groovy/GroovySourceCodeWriterTests.java index c960402ef5..1c6d5664de 100644 --- a/initializr-generator/src/test/java/io/spring/initializr/generator/language/groovy/GroovySourceCodeWriterTests.java +++ b/initializr-generator/src/test/java/io/spring/initializr/generator/language/groovy/GroovySourceCodeWriterTests.java @@ -33,6 +33,7 @@ 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.MultipleAnnotationContainer; import io.spring.initializr.generator.language.Parameter; import io.spring.initializr.generator.language.SourceStructure; import org.junit.jupiter.api.Test; @@ -321,6 +322,55 @@ void methodWithParameterAnnotation() throws IOException { " void something(@Service MyService service) {", " }", "", "}"); } + @Test + void multipleAnnotationsOnClass() throws IOException { + GroovySourceCode sourceCode = new GroovySourceCode(); + GroovyCompilationUnit compilationUnit = sourceCode.createCompilationUnit("com.example", "Test"); + MultipleAnnotationContainer classAnnotations = new MultipleAnnotationContainer(); + classAnnotations.addToList(ClassName.of("com.example.TestClassAnnotation")); + classAnnotations.addToList(ClassName.of("com.example.TestClassAnnotation")); + GroovyTypeDeclaration test = compilationUnit.createTypeDeclaration("Test", classAnnotations); + List lines = writeSingleType(sourceCode, "com/example/Test.groovy"); + assertThat(lines).containsExactly("package com.example", "", "@TestClassAnnotation", + "@TestClassAnnotation", "class Test {", "", "}"); + } + + @Test + void multipleAnnotationsOnField() throws IOException { + GroovySourceCode sourceCode = new GroovySourceCode(); + GroovyCompilationUnit compilationUnit = sourceCode.createCompilationUnit("com.example", "Test"); + GroovyTypeDeclaration test = compilationUnit.createTypeDeclaration("Test"); + MultipleAnnotationContainer fieldAnnotations = new MultipleAnnotationContainer(); + fieldAnnotations.addToList(ClassName.of("com.example.TestFiledAnnotation")); + fieldAnnotations.addToList(ClassName.of("com.example.TestFiledAnnotation")); + GroovyFieldDeclaration field = GroovyFieldDeclaration.field("testField") + .annotations(fieldAnnotations) + .returning("java.lang.String"); + test.addFieldDeclaration(field); + List lines = writeSingleType(sourceCode, "com/example/Test.groovy"); + assertThat(lines).containsExactly("package com.example", "", "class Test {", "", + " @TestFiledAnnotation", " @TestFiledAnnotation", " String testField", "", "}"); + } + + @Test + void multipleAnnotationsOnMethod() throws IOException { + GroovySourceCode sourceCode = new GroovySourceCode(); + GroovyCompilationUnit compilationUnit = sourceCode.createCompilationUnit("com.example", "Test"); + GroovyTypeDeclaration test = compilationUnit.createTypeDeclaration("Test"); + MultipleAnnotationContainer methodAnnotations = new MultipleAnnotationContainer(); + methodAnnotations.addToList(ClassName.of("com.example.TestMethodAnnotation")); + methodAnnotations.addToList(ClassName.of("com.example.TestMethodAnnotation")); + GroovyMethodDeclaration method = GroovyMethodDeclaration.method("testMethod") + .annotations(methodAnnotations) + .returning("void") + .parameters() + .body(CodeBlock.of("")); + test.addMethodDeclaration(method); + List lines = writeSingleType(sourceCode, "com/example/Test.groovy"); + assertThat(lines).containsExactly("package com.example", "", "class Test {", "", + " @TestMethodAnnotation", " @TestMethodAnnotation", " void testMethod() {", " }", "", "}"); + } + private List writeSingleType(GroovySourceCode sourceCode, String location) throws IOException { Path source = writeSourceCode(sourceCode).resolve(location); try (InputStream stream = Files.newInputStream(source)) { diff --git a/initializr-generator/src/test/java/io/spring/initializr/generator/language/java/JavaSourceCodeWriterTests.java b/initializr-generator/src/test/java/io/spring/initializr/generator/language/java/JavaSourceCodeWriterTests.java index 66ffaf0a76..f7b0f62ee5 100644 --- a/initializr-generator/src/test/java/io/spring/initializr/generator/language/java/JavaSourceCodeWriterTests.java +++ b/initializr-generator/src/test/java/io/spring/initializr/generator/language/java/JavaSourceCodeWriterTests.java @@ -33,6 +33,7 @@ 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.MultipleAnnotationContainer; import io.spring.initializr.generator.language.Parameter; import io.spring.initializr.generator.language.SourceStructure; import org.junit.jupiter.api.Test; @@ -327,6 +328,56 @@ void methodWithParameterAnnotation() throws IOException { " void something(@Service MyService service) {", " }", "", "}"); } + @Test + void multipleAnnotationsOnClass() throws IOException { + JavaSourceCode sourceCode = new JavaSourceCode(); + JavaCompilationUnit compilationUnit = sourceCode.createCompilationUnit("com.example", "Test"); + MultipleAnnotationContainer classAnnotations = new MultipleAnnotationContainer(); + classAnnotations.addToList(ClassName.of("com.example.TestClassAnnotation")); + classAnnotations.addToList(ClassName.of("com.example.TestClassAnnotation")); + compilationUnit.createTypeDeclaration("Test", classAnnotations); + List lines = writeSingleType(sourceCode, "com/example/Test.java"); + assertThat(lines).containsExactly("package com.example;", "", "@TestClassAnnotation", + "@TestClassAnnotation", "class Test {", "", "}"); + } + + @Test + void multipleAnnotationsOnField() throws IOException { + JavaSourceCode sourceCode = new JavaSourceCode(); + JavaCompilationUnit compilationUnit = sourceCode.createCompilationUnit("com.example", "Test"); + JavaTypeDeclaration test = compilationUnit.createTypeDeclaration("Test"); + MultipleAnnotationContainer fieldAnnotations = new MultipleAnnotationContainer(); + fieldAnnotations.addToList(ClassName.of("com.example.TestFiledAnnotation")); + fieldAnnotations.addToList(ClassName.of("com.example.TestFiledAnnotation")); + JavaFieldDeclaration field = JavaFieldDeclaration.field("testField") + .annotations(fieldAnnotations) + .modifiers(Modifier.PRIVATE) + .returning("java.lang.String"); + test.addFieldDeclaration(field); + List lines = writeSingleType(sourceCode, "com/example/Test.java"); + assertThat(lines).containsExactly("package com.example;", "", "class Test {", "", " @TestFiledAnnotation", + " @TestFiledAnnotation", " private String testField;", "", "}"); + } + + @Test + void multipleAnnotationsOnMethod() throws IOException { + JavaSourceCode sourceCode = new JavaSourceCode(); + JavaCompilationUnit compilationUnit = sourceCode.createCompilationUnit("com.example", "Test"); + JavaTypeDeclaration test = compilationUnit.createTypeDeclaration("Test"); + MultipleAnnotationContainer methodAnnotations = new MultipleAnnotationContainer(); + methodAnnotations.addToList(ClassName.of("com.example.TestMethodAnnotation")); + methodAnnotations.addToList(ClassName.of("com.example.TestMethodAnnotation")); + JavaMethodDeclaration method = JavaMethodDeclaration.method("testMethod") + .annotations(methodAnnotations) + .returning("void") + .parameters() + .body(CodeBlock.of("")); + test.addMethodDeclaration(method); + List lines = writeSingleType(sourceCode, "com/example/Test.java"); + assertThat(lines).containsExactly("package com.example;", "", "class Test {", "", + " @TestMethodAnnotation", " @TestMethodAnnotation", " void testMethod() {", " }", "", "}"); + } + private List writeSingleType(JavaSourceCode sourceCode, String location) throws IOException { Path source = writeSourceCode(sourceCode).resolve(location); try (InputStream stream = Files.newInputStream(source)) { diff --git a/initializr-generator/src/test/java/io/spring/initializr/generator/language/kotlin/KotlinSourceCodeWriterTests.java b/initializr-generator/src/test/java/io/spring/initializr/generator/language/kotlin/KotlinSourceCodeWriterTests.java index 431508048d..31400b2a4c 100644 --- a/initializr-generator/src/test/java/io/spring/initializr/generator/language/kotlin/KotlinSourceCodeWriterTests.java +++ b/initializr-generator/src/test/java/io/spring/initializr/generator/language/kotlin/KotlinSourceCodeWriterTests.java @@ -32,6 +32,7 @@ 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.MultipleAnnotationContainer; import io.spring.initializr.generator.language.Parameter; import io.spring.initializr.generator.language.SourceStructure; import org.junit.jupiter.api.Test; @@ -386,6 +387,54 @@ void functionWithParameterAnnotation() throws IOException { " fun something(@Service service: MyService) {", " }", "", "}"); } + @Test + void multipleAnnotationsOnClass() throws IOException { + KotlinSourceCode sourceCode = new KotlinSourceCode(); + KotlinCompilationUnit compilationUnit = sourceCode.createCompilationUnit("com.example", "Test"); + MultipleAnnotationContainer classAnnotations = new MultipleAnnotationContainer(); + classAnnotations.addToList(ClassName.of("com.example.TestClassAnnotation")); + classAnnotations.addToList(ClassName.of("com.example.TestClassAnnotation")); + KotlinTypeDeclaration test = compilationUnit.createTypeDeclaration("Test", classAnnotations); + List lines = writeSingleType(sourceCode, "com/example/Test.kt"); + assertThat(lines).containsExactly("package com.example", "", "@TestClassAnnotation", + "@TestClassAnnotation", "class Test"); + } + + @Test + void multipleAnnotationsOnProperty() throws IOException { + KotlinSourceCode sourceCode = new KotlinSourceCode(); + KotlinCompilationUnit compilationUnit = sourceCode.createCompilationUnit("com.example", "Test"); + KotlinTypeDeclaration test = compilationUnit.createTypeDeclaration("Test"); + MultipleAnnotationContainer propertyAnnotations = new MultipleAnnotationContainer(); + propertyAnnotations.addToList(ClassName.of("com.example.TestPropertyAnnotation")); + propertyAnnotations.addToList(ClassName.of("com.example.TestPropertyAnnotation")); + KotlinPropertyDeclaration property = KotlinPropertyDeclaration.val("testProperty") + .annotations(propertyAnnotations) + .returning("String").emptyValue(); + test.addPropertyDeclaration(property); + List lines = writeSingleType(sourceCode, "com/example/Test.kt"); + assertThat(lines).containsExactly("package com.example", "", "class Test {", "", + " @TestPropertyAnnotation", " @TestPropertyAnnotation", " val testProperty: String", "", "}"); + } + + @Test + void multipleAnnotationsOnFunction() throws IOException { + KotlinSourceCode sourceCode = new KotlinSourceCode(); + KotlinCompilationUnit compilationUnit = sourceCode.createCompilationUnit("com.example", "Test"); + KotlinTypeDeclaration test = compilationUnit.createTypeDeclaration("Test"); + MultipleAnnotationContainer functionAnnotations = new MultipleAnnotationContainer(); + functionAnnotations.addToList(ClassName.of("com.example.TestFunctionAnnotation")); + functionAnnotations.addToList(ClassName.of("com.example.TestFunctionAnnotation")); + KotlinFunctionDeclaration function = KotlinFunctionDeclaration.function("testFunction") + .annotations(functionAnnotations) + .body(CodeBlock.of("")); + test.addFunctionDeclaration(function); + List lines = writeSingleType(sourceCode, "com/example/Test.kt"); + assertThat(lines).containsExactly("package com.example", "", "class Test {", "", + " @TestFunctionAnnotation", " @TestFunctionAnnotation", " fun testFunction() {", + " }", "", "}"); + } + @Test void reservedKeywordsStartPackageName() throws IOException { KotlinSourceCode sourceCode = new KotlinSourceCode(); From c7a3a9999d4ea6a158f751f1c2cb02ead89e9c88 Mon Sep 17 00:00:00 2001 From: sijun-yang Date: Wed, 9 Jul 2025 17:31:34 +0900 Subject: [PATCH 14/28] Chore Signed-off-by: sijun-yang --- .../initializr/generator/language/TypeDeclaration.java | 5 +++++ .../generator/language/groovy/GroovyMethodDeclaration.java | 2 +- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/initializr-generator/src/main/java/io/spring/initializr/generator/language/TypeDeclaration.java b/initializr-generator/src/main/java/io/spring/initializr/generator/language/TypeDeclaration.java index 518e879566..eed6d1844b 100644 --- a/initializr-generator/src/main/java/io/spring/initializr/generator/language/TypeDeclaration.java +++ b/initializr-generator/src/main/java/io/spring/initializr/generator/language/TypeDeclaration.java @@ -36,6 +36,11 @@ public class TypeDeclaration implements Annotatable { private List implementsClassNames = Collections.emptyList(); + /** + * Creates a new instance. + * @param name the type name + * @param annotations the annotation holder + */ public TypeDeclaration(String name, AnnotationHolder annotations) { this.name = name; this.annotations = annotations; diff --git a/initializr-generator/src/main/java/io/spring/initializr/generator/language/groovy/GroovyMethodDeclaration.java b/initializr-generator/src/main/java/io/spring/initializr/generator/language/groovy/GroovyMethodDeclaration.java index 4e64ab0791..a20bb8ca5e 100644 --- a/initializr-generator/src/main/java/io/spring/initializr/generator/language/groovy/GroovyMethodDeclaration.java +++ b/initializr-generator/src/main/java/io/spring/initializr/generator/language/groovy/GroovyMethodDeclaration.java @@ -50,7 +50,7 @@ public GroovyMethodDeclaration(Builder builder, CodeBlock code) { this.annotations = builder.annotations; this.name = builder.name; this.returnType = builder.returnType; - this.parameters = new ArrayList<>(builder.parameters); + this.parameters = List.copyOf(builder.parameters); this.modifiers = builder.modifiers; this.code = code; } From d26833063572b3dc367e181389e60e85bee7933a Mon Sep 17 00:00:00 2001 From: sijun-yang Date: Wed, 9 Jul 2025 17:46:03 +0900 Subject: [PATCH 15/28] Format code to match style guide Signed-off-by: sijun-yang --- .../groovy/GroovySourceCodeWriterTests.java | 24 ++++++++--------- .../java/JavaSourceCodeWriterTests.java | 26 +++++++++---------- .../kotlin/KotlinSourceCodeWriterTests.java | 22 ++++++++-------- 3 files changed, 36 insertions(+), 36 deletions(-) diff --git a/initializr-generator/src/test/java/io/spring/initializr/generator/language/groovy/GroovySourceCodeWriterTests.java b/initializr-generator/src/test/java/io/spring/initializr/generator/language/groovy/GroovySourceCodeWriterTests.java index 1c6d5664de..d18074d818 100644 --- a/initializr-generator/src/test/java/io/spring/initializr/generator/language/groovy/GroovySourceCodeWriterTests.java +++ b/initializr-generator/src/test/java/io/spring/initializr/generator/language/groovy/GroovySourceCodeWriterTests.java @@ -331,8 +331,8 @@ void multipleAnnotationsOnClass() throws IOException { classAnnotations.addToList(ClassName.of("com.example.TestClassAnnotation")); GroovyTypeDeclaration test = compilationUnit.createTypeDeclaration("Test", classAnnotations); List lines = writeSingleType(sourceCode, "com/example/Test.groovy"); - assertThat(lines).containsExactly("package com.example", "", "@TestClassAnnotation", - "@TestClassAnnotation", "class Test {", "", "}"); + assertThat(lines).containsExactly("package com.example", "", "@TestClassAnnotation", "@TestClassAnnotation", + "class Test {", "", "}"); } @Test @@ -344,12 +344,12 @@ void multipleAnnotationsOnField() throws IOException { fieldAnnotations.addToList(ClassName.of("com.example.TestFiledAnnotation")); fieldAnnotations.addToList(ClassName.of("com.example.TestFiledAnnotation")); GroovyFieldDeclaration field = GroovyFieldDeclaration.field("testField") - .annotations(fieldAnnotations) - .returning("java.lang.String"); + .annotations(fieldAnnotations) + .returning("java.lang.String"); test.addFieldDeclaration(field); List lines = writeSingleType(sourceCode, "com/example/Test.groovy"); - assertThat(lines).containsExactly("package com.example", "", "class Test {", "", - " @TestFiledAnnotation", " @TestFiledAnnotation", " String testField", "", "}"); + assertThat(lines).containsExactly("package com.example", "", "class Test {", "", " @TestFiledAnnotation", + " @TestFiledAnnotation", " String testField", "", "}"); } @Test @@ -361,14 +361,14 @@ void multipleAnnotationsOnMethod() throws IOException { methodAnnotations.addToList(ClassName.of("com.example.TestMethodAnnotation")); methodAnnotations.addToList(ClassName.of("com.example.TestMethodAnnotation")); GroovyMethodDeclaration method = GroovyMethodDeclaration.method("testMethod") - .annotations(methodAnnotations) - .returning("void") - .parameters() - .body(CodeBlock.of("")); + .annotations(methodAnnotations) + .returning("void") + .parameters() + .body(CodeBlock.of("")); test.addMethodDeclaration(method); List lines = writeSingleType(sourceCode, "com/example/Test.groovy"); - assertThat(lines).containsExactly("package com.example", "", "class Test {", "", - " @TestMethodAnnotation", " @TestMethodAnnotation", " void testMethod() {", " }", "", "}"); + assertThat(lines).containsExactly("package com.example", "", "class Test {", "", " @TestMethodAnnotation", + " @TestMethodAnnotation", " void testMethod() {", " }", "", "}"); } private List writeSingleType(GroovySourceCode sourceCode, String location) throws IOException { diff --git a/initializr-generator/src/test/java/io/spring/initializr/generator/language/java/JavaSourceCodeWriterTests.java b/initializr-generator/src/test/java/io/spring/initializr/generator/language/java/JavaSourceCodeWriterTests.java index f7b0f62ee5..c727ff8cee 100644 --- a/initializr-generator/src/test/java/io/spring/initializr/generator/language/java/JavaSourceCodeWriterTests.java +++ b/initializr-generator/src/test/java/io/spring/initializr/generator/language/java/JavaSourceCodeWriterTests.java @@ -335,10 +335,10 @@ void multipleAnnotationsOnClass() throws IOException { MultipleAnnotationContainer classAnnotations = new MultipleAnnotationContainer(); classAnnotations.addToList(ClassName.of("com.example.TestClassAnnotation")); classAnnotations.addToList(ClassName.of("com.example.TestClassAnnotation")); - compilationUnit.createTypeDeclaration("Test", classAnnotations); - List lines = writeSingleType(sourceCode, "com/example/Test.java"); - assertThat(lines).containsExactly("package com.example;", "", "@TestClassAnnotation", - "@TestClassAnnotation", "class Test {", "", "}"); + compilationUnit.createTypeDeclaration("Test", classAnnotations); + List lines = writeSingleType(sourceCode, "com/example/Test.java"); + assertThat(lines).containsExactly("package com.example;", "", "@TestClassAnnotation", "@TestClassAnnotation", + "class Test {", "", "}"); } @Test @@ -350,9 +350,9 @@ void multipleAnnotationsOnField() throws IOException { fieldAnnotations.addToList(ClassName.of("com.example.TestFiledAnnotation")); fieldAnnotations.addToList(ClassName.of("com.example.TestFiledAnnotation")); JavaFieldDeclaration field = JavaFieldDeclaration.field("testField") - .annotations(fieldAnnotations) - .modifiers(Modifier.PRIVATE) - .returning("java.lang.String"); + .annotations(fieldAnnotations) + .modifiers(Modifier.PRIVATE) + .returning("java.lang.String"); test.addFieldDeclaration(field); List lines = writeSingleType(sourceCode, "com/example/Test.java"); assertThat(lines).containsExactly("package com.example;", "", "class Test {", "", " @TestFiledAnnotation", @@ -368,14 +368,14 @@ void multipleAnnotationsOnMethod() throws IOException { methodAnnotations.addToList(ClassName.of("com.example.TestMethodAnnotation")); methodAnnotations.addToList(ClassName.of("com.example.TestMethodAnnotation")); JavaMethodDeclaration method = JavaMethodDeclaration.method("testMethod") - .annotations(methodAnnotations) - .returning("void") - .parameters() - .body(CodeBlock.of("")); + .annotations(methodAnnotations) + .returning("void") + .parameters() + .body(CodeBlock.of("")); test.addMethodDeclaration(method); List lines = writeSingleType(sourceCode, "com/example/Test.java"); - assertThat(lines).containsExactly("package com.example;", "", "class Test {", "", - " @TestMethodAnnotation", " @TestMethodAnnotation", " void testMethod() {", " }", "", "}"); + assertThat(lines).containsExactly("package com.example;", "", "class Test {", "", " @TestMethodAnnotation", + " @TestMethodAnnotation", " void testMethod() {", " }", "", "}"); } private List writeSingleType(JavaSourceCode sourceCode, String location) throws IOException { diff --git a/initializr-generator/src/test/java/io/spring/initializr/generator/language/kotlin/KotlinSourceCodeWriterTests.java b/initializr-generator/src/test/java/io/spring/initializr/generator/language/kotlin/KotlinSourceCodeWriterTests.java index 31400b2a4c..658cbc8b36 100644 --- a/initializr-generator/src/test/java/io/spring/initializr/generator/language/kotlin/KotlinSourceCodeWriterTests.java +++ b/initializr-generator/src/test/java/io/spring/initializr/generator/language/kotlin/KotlinSourceCodeWriterTests.java @@ -396,8 +396,8 @@ void multipleAnnotationsOnClass() throws IOException { classAnnotations.addToList(ClassName.of("com.example.TestClassAnnotation")); KotlinTypeDeclaration test = compilationUnit.createTypeDeclaration("Test", classAnnotations); List lines = writeSingleType(sourceCode, "com/example/Test.kt"); - assertThat(lines).containsExactly("package com.example", "", "@TestClassAnnotation", - "@TestClassAnnotation", "class Test"); + assertThat(lines).containsExactly("package com.example", "", "@TestClassAnnotation", "@TestClassAnnotation", + "class Test"); } @Test @@ -409,12 +409,13 @@ void multipleAnnotationsOnProperty() throws IOException { propertyAnnotations.addToList(ClassName.of("com.example.TestPropertyAnnotation")); propertyAnnotations.addToList(ClassName.of("com.example.TestPropertyAnnotation")); KotlinPropertyDeclaration property = KotlinPropertyDeclaration.val("testProperty") - .annotations(propertyAnnotations) - .returning("String").emptyValue(); + .annotations(propertyAnnotations) + .returning("String") + .emptyValue(); test.addPropertyDeclaration(property); List lines = writeSingleType(sourceCode, "com/example/Test.kt"); - assertThat(lines).containsExactly("package com.example", "", "class Test {", "", - " @TestPropertyAnnotation", " @TestPropertyAnnotation", " val testProperty: String", "", "}"); + assertThat(lines).containsExactly("package com.example", "", "class Test {", "", " @TestPropertyAnnotation", + " @TestPropertyAnnotation", " val testProperty: String", "", "}"); } @Test @@ -426,13 +427,12 @@ void multipleAnnotationsOnFunction() throws IOException { functionAnnotations.addToList(ClassName.of("com.example.TestFunctionAnnotation")); functionAnnotations.addToList(ClassName.of("com.example.TestFunctionAnnotation")); KotlinFunctionDeclaration function = KotlinFunctionDeclaration.function("testFunction") - .annotations(functionAnnotations) - .body(CodeBlock.of("")); + .annotations(functionAnnotations) + .body(CodeBlock.of("")); test.addFunctionDeclaration(function); List lines = writeSingleType(sourceCode, "com/example/Test.kt"); - assertThat(lines).containsExactly("package com.example", "", "class Test {", "", - " @TestFunctionAnnotation", " @TestFunctionAnnotation", " fun testFunction() {", - " }", "", "}"); + assertThat(lines).containsExactly("package com.example", "", "class Test {", "", " @TestFunctionAnnotation", + " @TestFunctionAnnotation", " fun testFunction() {", " }", "", "}"); } @Test From 4df38ddb1c7c5b511ec0cf9e3c229780311b3bd9 Mon Sep 17 00:00:00 2001 From: sijun-yang Date: Wed, 9 Jul 2025 17:57:28 +0900 Subject: [PATCH 16/28] Revert accidental changes Signed-off-by: sijun-yang --- .../generator/language/groovy/GroovyMethodDeclaration.java | 4 ++-- .../generator/language/java/JavaMethodDeclaration.java | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/initializr-generator/src/main/java/io/spring/initializr/generator/language/groovy/GroovyMethodDeclaration.java b/initializr-generator/src/main/java/io/spring/initializr/generator/language/groovy/GroovyMethodDeclaration.java index a20bb8ca5e..038fa07c95 100644 --- a/initializr-generator/src/main/java/io/spring/initializr/generator/language/groovy/GroovyMethodDeclaration.java +++ b/initializr-generator/src/main/java/io/spring/initializr/generator/language/groovy/GroovyMethodDeclaration.java @@ -46,12 +46,12 @@ public final class GroovyMethodDeclaration implements Annotatable { private final CodeBlock code; - public GroovyMethodDeclaration(Builder builder, CodeBlock code) { + private GroovyMethodDeclaration(Builder builder, CodeBlock code) { this.annotations = builder.annotations; this.name = builder.name; this.returnType = builder.returnType; - this.parameters = List.copyOf(builder.parameters); this.modifiers = builder.modifiers; + this.parameters = List.copyOf(builder.parameters); this.code = code; } diff --git a/initializr-generator/src/main/java/io/spring/initializr/generator/language/java/JavaMethodDeclaration.java b/initializr-generator/src/main/java/io/spring/initializr/generator/language/java/JavaMethodDeclaration.java index 0fd8fc9bad..acecee881b 100644 --- a/initializr-generator/src/main/java/io/spring/initializr/generator/language/java/JavaMethodDeclaration.java +++ b/initializr-generator/src/main/java/io/spring/initializr/generator/language/java/JavaMethodDeclaration.java @@ -45,7 +45,7 @@ public final class JavaMethodDeclaration implements Annotatable { private final CodeBlock code; - public JavaMethodDeclaration(Builder builder, CodeBlock code) { + private JavaMethodDeclaration(Builder builder, CodeBlock code) { this.annotations = builder.annotations; this.name = builder.name; this.returnType = builder.returnType; From e8e02721512676afd58a2b71b3f5598883605cdd Mon Sep 17 00:00:00 2001 From: sijun-yang Date: Thu, 10 Jul 2025 18:02:05 +0900 Subject: [PATCH 17/28] Chore Signed-off-by: sijun-yang --- .../generator/language/MultipleAnnotationContainerTests.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/initializr-generator/src/test/java/io/spring/initializr/generator/language/MultipleAnnotationContainerTests.java b/initializr-generator/src/test/java/io/spring/initializr/generator/language/MultipleAnnotationContainerTests.java index c7895f2bb8..544c28c90a 100644 --- a/initializr-generator/src/test/java/io/spring/initializr/generator/language/MultipleAnnotationContainerTests.java +++ b/initializr-generator/src/test/java/io/spring/initializr/generator/language/MultipleAnnotationContainerTests.java @@ -16,11 +16,11 @@ package io.spring.initializr.generator.language; -import org.junit.jupiter.api.Test; - import java.util.List; import java.util.stream.Collectors; +import org.junit.jupiter.api.Test; + import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; From 15cf12ecea007128785fbaf8de5f9343d8ea4597 Mon Sep 17 00:00:00 2001 From: sijun-yang Date: Tue, 15 Jul 2025 16:42:21 +0900 Subject: [PATCH 18/28] Revert changes Signed-off-by: sijun-yang --- .../generator/language/Annotatable.java | 7 +- .../language/AnnotationContainer.java | 42 +++- .../generator/language/AnnotationHolder.java | 79 -------- .../generator/language/CompilationUnit.java | 4 - .../language/MultipleAnnotationContainer.java | 170 ---------------- .../generator/language/Parameter.java | 4 +- .../generator/language/TypeDeclaration.java | 16 +- .../groovy/GroovyCompilationUnit.java | 14 -- .../groovy/GroovyFieldDeclaration.java | 18 +- .../groovy/GroovyMethodDeclaration.java | 18 +- .../groovy/GroovyTypeDeclaration.java | 5 - .../language/java/JavaCompilationUnit.java | 14 -- .../language/java/JavaFieldDeclaration.java | 18 +- .../language/java/JavaMethodDeclaration.java | 18 +- .../language/java/JavaTypeDeclaration.java | 5 - .../kotlin/KotlinCompilationUnit.java | 14 -- .../kotlin/KotlinFunctionDeclaration.java | 18 +- .../kotlin/KotlinPropertyDeclaration.java | 22 +-- .../kotlin/KotlinSourceCodeWriter.java | 1 - .../kotlin/KotlinTypeDeclaration.java | 5 - .../MultipleAnnotationContainerTests.java | 181 ------------------ .../groovy/GroovySourceCodeWriterTests.java | 50 ----- .../java/JavaSourceCodeWriterTests.java | 51 ----- .../kotlin/KotlinSourceCodeWriterTests.java | 49 ----- 24 files changed, 54 insertions(+), 769 deletions(-) delete mode 100644 initializr-generator/src/main/java/io/spring/initializr/generator/language/AnnotationHolder.java delete mode 100644 initializr-generator/src/main/java/io/spring/initializr/generator/language/MultipleAnnotationContainer.java delete mode 100644 initializr-generator/src/test/java/io/spring/initializr/generator/language/MultipleAnnotationContainerTests.java diff --git a/initializr-generator/src/main/java/io/spring/initializr/generator/language/Annotatable.java b/initializr-generator/src/main/java/io/spring/initializr/generator/language/Annotatable.java index f9840e9ad6..9f648aa216 100644 --- a/initializr-generator/src/main/java/io/spring/initializr/generator/language/Annotatable.java +++ b/initializr-generator/src/main/java/io/spring/initializr/generator/language/Annotatable.java @@ -21,15 +21,14 @@ * * @author Andy Wilkinson * @author Stephane Nicoll - * @author Sijun Yang */ public interface Annotatable { /** - * Return the {@link AnnotationHolder} to use to configure the annotations of this + * Return the {@link AnnotationContainer} to use to configure the annotations of this * element. - * @return the annotation holder + * @return the annotation container */ - AnnotationHolder annotations(); + AnnotationContainer annotations(); } diff --git a/initializr-generator/src/main/java/io/spring/initializr/generator/language/AnnotationContainer.java b/initializr-generator/src/main/java/io/spring/initializr/generator/language/AnnotationContainer.java index 07b037b9f6..8526797715 100644 --- a/initializr-generator/src/main/java/io/spring/initializr/generator/language/AnnotationContainer.java +++ b/initializr-generator/src/main/java/io/spring/initializr/generator/language/AnnotationContainer.java @@ -24,12 +24,11 @@ import io.spring.initializr.generator.language.Annotation.Builder; /** - * An {@link AnnotationHolder} implementation that holds a single annotation per type. + * A container for {@linkplain Annotation annotations} defined on an annotated element. * * @author Stephane Nicoll - * @author Sijun Yang */ -public class AnnotationContainer implements AnnotationHolder { +public class AnnotationContainer { private final Map annotations; @@ -41,22 +40,38 @@ private AnnotationContainer(Map annotations) { this.annotations = annotations; } - @Override + /** + * Specify if this container is empty. + * @return {@code true} if no annotation is registered + */ public boolean isEmpty() { return this.annotations.isEmpty(); } - @Override + /** + * 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); } - @Override + /** + * Return the {@link Annotation annotations}. + * @return the annotations + */ public Stream values() { return this.annotations.values().stream().map(Builder::build); } - @Override + /** + * 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 annotation) { Builder builder = this.annotations.computeIfAbsent(className, (key) -> new Builder(className)); if (annotation != null) { @@ -64,17 +79,24 @@ public void add(ClassName className, Consumer annotation) { } } - @Override + /** + * 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); } - @Override + /** + * 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; } - @Override public AnnotationContainer deepCopy() { Map copy = new LinkedHashMap<>(); this.annotations.forEach((className, builder) -> copy.put(className, new Builder(builder))); diff --git a/initializr-generator/src/main/java/io/spring/initializr/generator/language/AnnotationHolder.java b/initializr-generator/src/main/java/io/spring/initializr/generator/language/AnnotationHolder.java deleted file mode 100644 index 0462a3f786..0000000000 --- a/initializr-generator/src/main/java/io/spring/initializr/generator/language/AnnotationHolder.java +++ /dev/null @@ -1,79 +0,0 @@ -/* - * Copyright 2012 - present 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.function.Consumer; -import java.util.stream.Stream; - -import io.spring.initializr.generator.language.Annotation.Builder; - -/** - * A holder for {@linkplain Annotation annotations} defined on an annotated element. - * - * @author Sijun Yang - */ -public interface AnnotationHolder { - - /** - * Specify if this holder is empty. - * @return {@code true} if no annotation is registered - */ - boolean isEmpty(); - - /** - * Specify if this holder has 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 - */ - boolean has(ClassName className); - - /** - * Return the {@link Annotation annotations}. - * @return the annotations - */ - Stream values(); - - /** - * 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} - */ - void add(ClassName className, Consumer annotation); - - /** - * 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 - */ - void add(ClassName className); - - /** - * 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 - */ - boolean remove(ClassName className); - - /** - * Create a deep copy of this annotation holder. - * @return a new annotation holder with the same annotations - */ - AnnotationHolder deepCopy(); - -} diff --git a/initializr-generator/src/main/java/io/spring/initializr/generator/language/CompilationUnit.java b/initializr-generator/src/main/java/io/spring/initializr/generator/language/CompilationUnit.java index 7ff8c06f88..c9ba65a8e9 100644 --- a/initializr-generator/src/main/java/io/spring/initializr/generator/language/CompilationUnit.java +++ b/initializr-generator/src/main/java/io/spring/initializr/generator/language/CompilationUnit.java @@ -74,10 +74,6 @@ public List getTypeDeclarations() { return Collections.unmodifiableList(this.typeDeclarations); } - protected void addTypeDeclaration(T typeDeclaration) { - this.typeDeclarations.add(typeDeclaration); - } - protected abstract T doCreateTypeDeclaration(String name); } diff --git a/initializr-generator/src/main/java/io/spring/initializr/generator/language/MultipleAnnotationContainer.java b/initializr-generator/src/main/java/io/spring/initializr/generator/language/MultipleAnnotationContainer.java deleted file mode 100644 index e3ee45147e..0000000000 --- a/initializr-generator/src/main/java/io/spring/initializr/generator/language/MultipleAnnotationContainer.java +++ /dev/null @@ -1,170 +0,0 @@ -/* - * Copyright 2012 - present 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.ArrayList; -import java.util.LinkedHashMap; -import java.util.List; -import java.util.Map; -import java.util.function.Consumer; -import java.util.stream.Stream; - -import io.spring.initializr.generator.language.Annotation.Builder; - -/** - * An {@link AnnotationHolder} implementation that holds multiple annotations per type. - * - * @author Sijun Yang - */ -public class MultipleAnnotationContainer implements AnnotationHolder { - - private final Map> annotations; - - public MultipleAnnotationContainer() { - this(new LinkedHashMap<>()); - } - - private MultipleAnnotationContainer(Map> annotations) { - this.annotations = annotations; - } - - @Override - public boolean isEmpty() { - return this.annotations.isEmpty() || this.annotations.values().stream().allMatch(List::isEmpty); - } - - @Override - public boolean has(ClassName className) { - List builders = this.annotations.get(className); - return builders != null && !builders.isEmpty(); - } - - @Override - public Stream values() { - return this.annotations.values().stream().flatMap(List::stream).map(Builder::build); - } - - /** - * Add operation is not supported for {@link MultipleAnnotationContainer}. Use - * {@link #addToList(ClassName, Consumer)} instead to explicitly add to the list. - * @throws UnsupportedOperationException always - */ - @Override - public void add(ClassName className, Consumer annotation) { - throw new UnsupportedOperationException( - "Add operation with potential overwrite is not supported for MultipleAnnotationContainer. " - + "Use addToList() to explicitly add annotations to the list."); - } - - /** - * Add operation is not supported for {@link MultipleAnnotationContainer}. Use - * {@link #addToList(ClassName)} instead to explicitly add to the list. - * @throws UnsupportedOperationException always - */ - @Override - public void add(ClassName className) { - throw new UnsupportedOperationException( - "Add operation with potential overwrite is not supported for MultipleAnnotationContainer. " - + "Use addToList() to explicitly add annotations to the list."); - } - - /** - * Add an annotation to the list of annotations with the specified class name. Always - * adds a new annotation. - * @param className the class name of an annotation - * @param annotation a {@link Consumer} to customize the {@link Annotation} - */ - public void addToList(ClassName className, Consumer annotation) { - List builders = this.annotations.computeIfAbsent(className, (key) -> new ArrayList<>()); - Builder builder = new Builder(className); - if (annotation != null) { - annotation.accept(builder); - } - builders.add(builder); - } - - /** - * Add an annotation to the list of annotations with the specified class name. Always - * adds a new annotation. - * @param className the class name of an annotation - */ - public void addToList(ClassName className) { - addToList(className, null); - } - - /** - * Return all annotations with the specified class name. - * @param className the class name of an annotation - * @return a stream of all annotations with the specified class name - */ - public Stream valuesOf(ClassName className) { - List builders = this.annotations.get(className); - if (builders == null || builders.isEmpty()) { - return Stream.empty(); - } - return builders.stream().map(Builder::build); - } - - /** - * Return the number of annotations with the specified class name. - * @param className the class name of an annotation - * @return the count of annotations with the specified class name - */ - public int countOf(ClassName className) { - List builders = this.annotations.get(className); - return (builders != null) ? builders.size() : 0; - } - - /** - * Remove all annotations with the specified class name. - * @param className the class name of the annotation - * @return the number of annotations that were removed - */ - public int removeAll(ClassName className) { - List builders = this.annotations.remove(className); - return (builders != null) ? builders.size() : 0; - } - - @Override - public boolean remove(ClassName className) { - int removedCount = removeAll(className); - return removedCount > 0; - } - - /** - * Check if this container has multiple annotations with the specified class name. - * @param className the class name of an annotation - * @return {@code true} if there are multiple annotations with the specified class - * name - */ - public boolean hasMultiple(ClassName className) { - List builders = this.annotations.get(className); - return builders != null && builders.size() > 1; - } - - @Override - public MultipleAnnotationContainer deepCopy() { - Map> copy = new LinkedHashMap<>(); - this.annotations.forEach((className, builders) -> { - List buildersCopy = new ArrayList<>(); - builders.forEach((builder) -> buildersCopy.add(new Builder(builder))); - copy.put(className, buildersCopy); - }); - return new MultipleAnnotationContainer(copy); - } - -} diff --git a/initializr-generator/src/main/java/io/spring/initializr/generator/language/Parameter.java b/initializr-generator/src/main/java/io/spring/initializr/generator/language/Parameter.java index d92e2d8739..af1acbfcd1 100644 --- a/initializr-generator/src/main/java/io/spring/initializr/generator/language/Parameter.java +++ b/initializr-generator/src/main/java/io/spring/initializr/generator/language/Parameter.java @@ -30,7 +30,7 @@ public final class Parameter implements Annotatable { private final String type; - private final AnnotationHolder annotations; + private final AnnotationContainer annotations; private Parameter(Builder builder) { this.name = builder.name; @@ -94,7 +94,7 @@ public String getType() { } @Override - public AnnotationHolder annotations() { + public AnnotationContainer annotations() { return this.annotations; } diff --git a/initializr-generator/src/main/java/io/spring/initializr/generator/language/TypeDeclaration.java b/initializr-generator/src/main/java/io/spring/initializr/generator/language/TypeDeclaration.java index eed6d1844b..59b01eefa7 100644 --- a/initializr-generator/src/main/java/io/spring/initializr/generator/language/TypeDeclaration.java +++ b/initializr-generator/src/main/java/io/spring/initializr/generator/language/TypeDeclaration.java @@ -28,7 +28,7 @@ */ public class TypeDeclaration implements Annotatable { - private final AnnotationHolder annotations; + private final AnnotationContainer annotations = new AnnotationContainer(); private final String name; @@ -36,22 +36,12 @@ public class TypeDeclaration implements Annotatable { private List implementsClassNames = Collections.emptyList(); - /** - * Creates a new instance. - * @param name the type name - * @param annotations the annotation holder - */ - public TypeDeclaration(String name, AnnotationHolder annotations) { - this.name = name; - this.annotations = annotations; - } - /** * Creates a new instance. * @param name the type name */ public TypeDeclaration(String name) { - this(name, new AnnotationContainer()); + this.name = name; } /** @@ -79,7 +69,7 @@ public void implement(String... names) { } @Override - public AnnotationHolder annotations() { + public AnnotationContainer annotations() { return this.annotations; } diff --git a/initializr-generator/src/main/java/io/spring/initializr/generator/language/groovy/GroovyCompilationUnit.java b/initializr-generator/src/main/java/io/spring/initializr/generator/language/groovy/GroovyCompilationUnit.java index b7c4afffec..0217cde5a3 100644 --- a/initializr-generator/src/main/java/io/spring/initializr/generator/language/groovy/GroovyCompilationUnit.java +++ b/initializr-generator/src/main/java/io/spring/initializr/generator/language/groovy/GroovyCompilationUnit.java @@ -16,7 +16,6 @@ package io.spring.initializr.generator.language.groovy; -import io.spring.initializr.generator.language.AnnotationHolder; import io.spring.initializr.generator.language.CompilationUnit; /** @@ -35,17 +34,4 @@ protected GroovyTypeDeclaration doCreateTypeDeclaration(String name) { return new GroovyTypeDeclaration(name); } - /** - * Creates a new {@link GroovyTypeDeclaration} with the specified name and - * {@link AnnotationHolder}. - * @param name the name of the type declaration - * @param annotations the annotation holder to use - * @return a new GroovyTypeDeclaration instance - */ - public GroovyTypeDeclaration createTypeDeclaration(String name, AnnotationHolder annotations) { - GroovyTypeDeclaration typeDeclaration = new GroovyTypeDeclaration(name, annotations); - addTypeDeclaration(typeDeclaration); - return typeDeclaration; - } - } diff --git a/initializr-generator/src/main/java/io/spring/initializr/generator/language/groovy/GroovyFieldDeclaration.java b/initializr-generator/src/main/java/io/spring/initializr/generator/language/groovy/GroovyFieldDeclaration.java index d12daff309..3050f0d253 100644 --- a/initializr-generator/src/main/java/io/spring/initializr/generator/language/groovy/GroovyFieldDeclaration.java +++ b/initializr-generator/src/main/java/io/spring/initializr/generator/language/groovy/GroovyFieldDeclaration.java @@ -18,7 +18,6 @@ import io.spring.initializr.generator.language.Annotatable; import io.spring.initializr.generator.language.AnnotationContainer; -import io.spring.initializr.generator.language.AnnotationHolder; /** * Declaration of a field written in Groovy. @@ -27,7 +26,7 @@ */ public final class GroovyFieldDeclaration implements Annotatable { - private final AnnotationHolder annotations; + private final AnnotationContainer annotations = new AnnotationContainer(); private final int modifiers; @@ -40,7 +39,6 @@ public final class GroovyFieldDeclaration implements Annotatable { private final boolean initialized; private GroovyFieldDeclaration(Builder builder) { - this.annotations = builder.annotations; this.modifiers = builder.modifiers; this.name = builder.name; this.returnType = builder.returnType; @@ -58,7 +56,7 @@ public static Builder field(String name) { } @Override - public AnnotationHolder annotations() { + public AnnotationContainer annotations() { return this.annotations; } @@ -107,8 +105,6 @@ public boolean isInitialized() { */ public static final class Builder { - private AnnotationHolder annotations = new AnnotationContainer(); - private final String name; private String returnType; @@ -133,16 +129,6 @@ public Builder modifiers(int modifiers) { return this; } - /** - * Sets the annotation holder. - * @param annotations the annotation holder - * @return this for method chaining - */ - public Builder annotations(AnnotationHolder annotations) { - this.annotations = annotations; - return this; - } - /** * Sets the value. * @param value the value diff --git a/initializr-generator/src/main/java/io/spring/initializr/generator/language/groovy/GroovyMethodDeclaration.java b/initializr-generator/src/main/java/io/spring/initializr/generator/language/groovy/GroovyMethodDeclaration.java index 038fa07c95..2dcc58f36a 100644 --- a/initializr-generator/src/main/java/io/spring/initializr/generator/language/groovy/GroovyMethodDeclaration.java +++ b/initializr-generator/src/main/java/io/spring/initializr/generator/language/groovy/GroovyMethodDeclaration.java @@ -23,7 +23,6 @@ import io.spring.initializr.generator.language.Annotatable; import io.spring.initializr.generator.language.AnnotationContainer; -import io.spring.initializr.generator.language.AnnotationHolder; import io.spring.initializr.generator.language.CodeBlock; import io.spring.initializr.generator.language.Parameter; @@ -34,7 +33,7 @@ */ public final class GroovyMethodDeclaration implements Annotatable { - private final AnnotationHolder annotations; + private final AnnotationContainer annotations = new AnnotationContainer(); private final String name; @@ -47,7 +46,6 @@ public final class GroovyMethodDeclaration implements Annotatable { private final CodeBlock code; private GroovyMethodDeclaration(Builder builder, CodeBlock code) { - this.annotations = builder.annotations; this.name = builder.name; this.returnType = builder.returnType; this.modifiers = builder.modifiers; @@ -85,7 +83,7 @@ CodeBlock getCode() { } @Override - public AnnotationHolder annotations() { + public AnnotationContainer annotations() { return this.annotations; } @@ -94,8 +92,6 @@ public AnnotationHolder annotations() { */ public static final class Builder { - private AnnotationHolder annotations = new AnnotationContainer(); - private final String name; private List parameters = new ArrayList<>(); @@ -138,16 +134,6 @@ public Builder parameters(Parameter... parameters) { return this; } - /** - * Sets the annotation holder. - * @param annotations the annotation holder - * @return this for method chaining - */ - public Builder annotations(AnnotationHolder annotations) { - this.annotations = annotations; - return this; - } - /** * Sets the body. * @param code the code for the body diff --git a/initializr-generator/src/main/java/io/spring/initializr/generator/language/groovy/GroovyTypeDeclaration.java b/initializr-generator/src/main/java/io/spring/initializr/generator/language/groovy/GroovyTypeDeclaration.java index d8a63b8e2d..aee0fead04 100644 --- a/initializr-generator/src/main/java/io/spring/initializr/generator/language/groovy/GroovyTypeDeclaration.java +++ b/initializr-generator/src/main/java/io/spring/initializr/generator/language/groovy/GroovyTypeDeclaration.java @@ -19,7 +19,6 @@ import java.util.ArrayList; import java.util.List; -import io.spring.initializr.generator.language.AnnotationHolder; import io.spring.initializr.generator.language.TypeDeclaration; /** @@ -39,10 +38,6 @@ public class GroovyTypeDeclaration extends TypeDeclaration { super(name); } - GroovyTypeDeclaration(String name, AnnotationHolder annotations) { - super(name, annotations); - } - /** * Sets the modifiers. * @param modifiers the modifiers diff --git a/initializr-generator/src/main/java/io/spring/initializr/generator/language/java/JavaCompilationUnit.java b/initializr-generator/src/main/java/io/spring/initializr/generator/language/java/JavaCompilationUnit.java index e9be3128d3..233e403210 100644 --- a/initializr-generator/src/main/java/io/spring/initializr/generator/language/java/JavaCompilationUnit.java +++ b/initializr-generator/src/main/java/io/spring/initializr/generator/language/java/JavaCompilationUnit.java @@ -16,7 +16,6 @@ package io.spring.initializr.generator.language.java; -import io.spring.initializr.generator.language.AnnotationHolder; import io.spring.initializr.generator.language.CompilationUnit; /** @@ -35,17 +34,4 @@ protected JavaTypeDeclaration doCreateTypeDeclaration(String name) { return new JavaTypeDeclaration(name); } - /** - * Creates a new {@link JavaTypeDeclaration} with the specified name and - * {@link AnnotationHolder}. - * @param name the name of the type declaration - * @param annotations the annotation holder to use - * @return a new JavaTypeDeclaration instance - */ - public JavaTypeDeclaration createTypeDeclaration(String name, AnnotationHolder annotations) { - JavaTypeDeclaration typeDeclaration = new JavaTypeDeclaration(name, annotations); - addTypeDeclaration(typeDeclaration); - return typeDeclaration; - } - } diff --git a/initializr-generator/src/main/java/io/spring/initializr/generator/language/java/JavaFieldDeclaration.java b/initializr-generator/src/main/java/io/spring/initializr/generator/language/java/JavaFieldDeclaration.java index fbaec95472..883620b551 100644 --- a/initializr-generator/src/main/java/io/spring/initializr/generator/language/java/JavaFieldDeclaration.java +++ b/initializr-generator/src/main/java/io/spring/initializr/generator/language/java/JavaFieldDeclaration.java @@ -18,7 +18,6 @@ import io.spring.initializr.generator.language.Annotatable; import io.spring.initializr.generator.language.AnnotationContainer; -import io.spring.initializr.generator.language.AnnotationHolder; /** * Declaration of a field written in Java. @@ -27,7 +26,7 @@ */ public final class JavaFieldDeclaration implements Annotatable { - private final AnnotationHolder annotations; + private final AnnotationContainer annotations = new AnnotationContainer(); private final int modifiers; @@ -40,7 +39,6 @@ public final class JavaFieldDeclaration implements Annotatable { private final boolean initialized; private JavaFieldDeclaration(Builder builder) { - this.annotations = builder.annotations; this.modifiers = builder.modifiers; this.name = builder.name; this.returnType = builder.returnType; @@ -58,7 +56,7 @@ public static Builder field(String name) { } @Override - public AnnotationHolder annotations() { + public AnnotationContainer annotations() { return this.annotations; } @@ -107,8 +105,6 @@ public boolean isInitialized() { */ public static final class Builder { - private AnnotationHolder annotations = new AnnotationContainer(); - private final String name; private String returnType; @@ -133,16 +129,6 @@ public Builder modifiers(int modifiers) { return this; } - /** - * Sets the annotation holder. - * @param annotations the annotation holder - * @return this for method chaining - */ - public Builder annotations(AnnotationHolder annotations) { - this.annotations = annotations; - return this; - } - /** * Sets the value. * @param value the value diff --git a/initializr-generator/src/main/java/io/spring/initializr/generator/language/java/JavaMethodDeclaration.java b/initializr-generator/src/main/java/io/spring/initializr/generator/language/java/JavaMethodDeclaration.java index acecee881b..809099a3c5 100644 --- a/initializr-generator/src/main/java/io/spring/initializr/generator/language/java/JavaMethodDeclaration.java +++ b/initializr-generator/src/main/java/io/spring/initializr/generator/language/java/JavaMethodDeclaration.java @@ -22,7 +22,6 @@ import io.spring.initializr.generator.language.Annotatable; import io.spring.initializr.generator.language.AnnotationContainer; -import io.spring.initializr.generator.language.AnnotationHolder; import io.spring.initializr.generator.language.CodeBlock; import io.spring.initializr.generator.language.Parameter; @@ -33,7 +32,7 @@ */ public final class JavaMethodDeclaration implements Annotatable { - private final AnnotationHolder annotations; + private final AnnotationContainer annotations = new AnnotationContainer(); private final String name; @@ -46,7 +45,6 @@ public final class JavaMethodDeclaration implements Annotatable { private final CodeBlock code; private JavaMethodDeclaration(Builder builder, CodeBlock code) { - this.annotations = builder.annotations; this.name = builder.name; this.returnType = builder.returnType; this.modifiers = builder.modifiers; @@ -84,7 +82,7 @@ CodeBlock getCode() { } @Override - public AnnotationHolder annotations() { + public AnnotationContainer annotations() { return this.annotations; } @@ -93,8 +91,6 @@ public AnnotationHolder annotations() { */ public static final class Builder { - private AnnotationHolder annotations = new AnnotationContainer(); - private final String name; private List parameters = new ArrayList<>(); @@ -137,16 +133,6 @@ public Builder parameters(Parameter... parameters) { return this; } - /** - * Sets the annotation holder. - * @param annotations the annotation holder - * @return this for method chaining - */ - public Builder annotations(AnnotationHolder annotations) { - this.annotations = annotations; - return this; - } - /** * Sets the body. * @param code the code for the body diff --git a/initializr-generator/src/main/java/io/spring/initializr/generator/language/java/JavaTypeDeclaration.java b/initializr-generator/src/main/java/io/spring/initializr/generator/language/java/JavaTypeDeclaration.java index 69ea2cc1fc..b91e87a9d9 100644 --- a/initializr-generator/src/main/java/io/spring/initializr/generator/language/java/JavaTypeDeclaration.java +++ b/initializr-generator/src/main/java/io/spring/initializr/generator/language/java/JavaTypeDeclaration.java @@ -19,7 +19,6 @@ import java.util.ArrayList; import java.util.List; -import io.spring.initializr.generator.language.AnnotationHolder; import io.spring.initializr.generator.language.TypeDeclaration; /** @@ -40,10 +39,6 @@ public class JavaTypeDeclaration extends TypeDeclaration { super(name); } - JavaTypeDeclaration(String name, AnnotationHolder annotations) { - super(name, annotations); - } - /** * Sets the modifiers. * @param modifiers the modifiers diff --git a/initializr-generator/src/main/java/io/spring/initializr/generator/language/kotlin/KotlinCompilationUnit.java b/initializr-generator/src/main/java/io/spring/initializr/generator/language/kotlin/KotlinCompilationUnit.java index fc041a52f2..27be7a654d 100644 --- a/initializr-generator/src/main/java/io/spring/initializr/generator/language/kotlin/KotlinCompilationUnit.java +++ b/initializr-generator/src/main/java/io/spring/initializr/generator/language/kotlin/KotlinCompilationUnit.java @@ -19,7 +19,6 @@ import java.util.ArrayList; import java.util.List; -import io.spring.initializr.generator.language.AnnotationHolder; import io.spring.initializr.generator.language.CompilationUnit; /** @@ -40,19 +39,6 @@ protected KotlinTypeDeclaration doCreateTypeDeclaration(String name) { return new KotlinTypeDeclaration(name); } - /** - * Creates a new {@link KotlinTypeDeclaration} with the specified name and - * {@link AnnotationHolder}. - * @param name the name of the type declaration - * @param annotations the annotation holder to use - * @return a new KotlinTypeDeclaration instance - */ - public KotlinTypeDeclaration createTypeDeclaration(String name, AnnotationHolder annotations) { - KotlinTypeDeclaration typeDeclaration = new KotlinTypeDeclaration(name, annotations); - addTypeDeclaration(typeDeclaration); - return typeDeclaration; - } - /** * Adds the given function as a top level function. * @param function the function to add diff --git a/initializr-generator/src/main/java/io/spring/initializr/generator/language/kotlin/KotlinFunctionDeclaration.java b/initializr-generator/src/main/java/io/spring/initializr/generator/language/kotlin/KotlinFunctionDeclaration.java index 77b4b3d565..b579a8d379 100644 --- a/initializr-generator/src/main/java/io/spring/initializr/generator/language/kotlin/KotlinFunctionDeclaration.java +++ b/initializr-generator/src/main/java/io/spring/initializr/generator/language/kotlin/KotlinFunctionDeclaration.java @@ -22,7 +22,6 @@ import io.spring.initializr.generator.language.Annotatable; import io.spring.initializr.generator.language.AnnotationContainer; -import io.spring.initializr.generator.language.AnnotationHolder; import io.spring.initializr.generator.language.CodeBlock; import io.spring.initializr.generator.language.Parameter; @@ -33,7 +32,7 @@ */ public final class KotlinFunctionDeclaration implements Annotatable { - private final AnnotationHolder annotations; + private final AnnotationContainer annotations = new AnnotationContainer(); private final String name; @@ -46,7 +45,6 @@ public final class KotlinFunctionDeclaration implements Annotatable { private final CodeBlock code; private KotlinFunctionDeclaration(Builder builder, CodeBlock code) { - this.annotations = builder.annotations; this.name = builder.name; this.returnType = builder.returnType; this.modifiers = builder.modifiers; @@ -84,7 +82,7 @@ CodeBlock getCode() { } @Override - public AnnotationHolder annotations() { + public AnnotationContainer annotations() { return this.annotations; } @@ -93,8 +91,6 @@ public AnnotationHolder annotations() { */ public static final class Builder { - private AnnotationHolder annotations = new AnnotationContainer(); - private final String name; private List parameters = new ArrayList<>(); @@ -137,16 +133,6 @@ public Builder parameters(Parameter... parameters) { return this; } - /** - * Sets the annotation holder. - * @param annotations the annotation holder - * @return this for method chaining - */ - public Builder annotations(AnnotationHolder annotations) { - this.annotations = annotations; - return this; - } - /** * Sets the body. * @param code the code for the body diff --git a/initializr-generator/src/main/java/io/spring/initializr/generator/language/kotlin/KotlinPropertyDeclaration.java b/initializr-generator/src/main/java/io/spring/initializr/generator/language/kotlin/KotlinPropertyDeclaration.java index 17aecfcce1..3e2a364fbc 100644 --- a/initializr-generator/src/main/java/io/spring/initializr/generator/language/kotlin/KotlinPropertyDeclaration.java +++ b/initializr-generator/src/main/java/io/spring/initializr/generator/language/kotlin/KotlinPropertyDeclaration.java @@ -24,7 +24,6 @@ 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.AnnotationHolder; import io.spring.initializr.generator.language.ClassName; import io.spring.initializr.generator.language.CodeBlock; @@ -35,7 +34,7 @@ */ public final class KotlinPropertyDeclaration implements Annotatable { - private final AnnotationHolder annotations; + private final AnnotationContainer annotations = new AnnotationContainer(); private final boolean isVal; @@ -52,7 +51,6 @@ public final class KotlinPropertyDeclaration implements Annotatable { private final Accessor setter; private KotlinPropertyDeclaration(Builder builder) { - this.annotations = builder.annotations; this.name = builder.name; this.returnType = builder.returnType; this.modifiers = new ArrayList<>(builder.modifiers); @@ -113,7 +111,7 @@ Accessor getSetter() { } @Override - public AnnotationHolder annotations() { + public AnnotationContainer annotations() { return this.annotations; } @@ -124,8 +122,6 @@ public AnnotationHolder annotations() { */ public abstract static class Builder> { - private AnnotationHolder annotations = new AnnotationContainer(); - private final boolean isVal; private final String name; @@ -189,16 +185,6 @@ public T modifiers(KotlinModifier... modifiers) { return self(); } - /** - * Sets the annotation holder. - * @param annotations the annotation holder - * @return this for method chaining - */ - public T annotations(AnnotationHolder annotations) { - this.annotations = annotations; - return self(); - } - /** * Sets no value. * @return the property declaration @@ -322,7 +308,7 @@ public T buildAccessor() { static final class Accessor implements Annotatable { - private final AnnotationHolder annotations; + private final AnnotationContainer annotations; private final CodeBlock code; @@ -336,7 +322,7 @@ CodeBlock getCode() { } @Override - public AnnotationHolder annotations() { + public AnnotationContainer annotations() { return this.annotations; } diff --git a/initializr-generator/src/main/java/io/spring/initializr/generator/language/kotlin/KotlinSourceCodeWriter.java b/initializr-generator/src/main/java/io/spring/initializr/generator/language/kotlin/KotlinSourceCodeWriter.java index ff46ef9f08..7fc79adc1a 100644 --- a/initializr-generator/src/main/java/io/spring/initializr/generator/language/kotlin/KotlinSourceCodeWriter.java +++ b/initializr-generator/src/main/java/io/spring/initializr/generator/language/kotlin/KotlinSourceCodeWriter.java @@ -162,7 +162,6 @@ private String escapeKotlinKeywords(String packageName) { private void writeProperty(IndentingWriter writer, KotlinPropertyDeclaration propertyDeclaration) { writer.println(); - writeAnnotations(writer, propertyDeclaration); writeModifiers(writer, propertyDeclaration.getModifiers()); if (propertyDeclaration.isVal()) { writer.print("val "); diff --git a/initializr-generator/src/main/java/io/spring/initializr/generator/language/kotlin/KotlinTypeDeclaration.java b/initializr-generator/src/main/java/io/spring/initializr/generator/language/kotlin/KotlinTypeDeclaration.java index aa7633bea5..71fb406716 100644 --- a/initializr-generator/src/main/java/io/spring/initializr/generator/language/kotlin/KotlinTypeDeclaration.java +++ b/initializr-generator/src/main/java/io/spring/initializr/generator/language/kotlin/KotlinTypeDeclaration.java @@ -20,7 +20,6 @@ import java.util.Arrays; import java.util.List; -import io.spring.initializr.generator.language.AnnotationHolder; import io.spring.initializr.generator.language.TypeDeclaration; /** @@ -40,10 +39,6 @@ public class KotlinTypeDeclaration extends TypeDeclaration { super(name); } - KotlinTypeDeclaration(String name, AnnotationHolder annotations) { - super(name, annotations); - } - /** * Sets the modifiers. * @param modifiers the modifiers diff --git a/initializr-generator/src/test/java/io/spring/initializr/generator/language/MultipleAnnotationContainerTests.java b/initializr-generator/src/test/java/io/spring/initializr/generator/language/MultipleAnnotationContainerTests.java deleted file mode 100644 index 544c28c90a..0000000000 --- a/initializr-generator/src/test/java/io/spring/initializr/generator/language/MultipleAnnotationContainerTests.java +++ /dev/null @@ -1,181 +0,0 @@ -/* - * Copyright 2012 - present 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.List; -import java.util.stream.Collectors; - -import org.junit.jupiter.api.Test; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.assertThatThrownBy; - -/** - * Tests for {@link MultipleAnnotationContainer}. - * - * @author Sijun Yang - */ -class MultipleAnnotationContainerTests { - - private static final ClassName TEST_CLASS_NAME = ClassName.of("com.example.Test"); - - private static final ClassName OTHER_CLASS_NAME = ClassName.of("com.example.Other"); - - @Test - void isEmptyWithEmptyContainer() { - MultipleAnnotationContainer container = new MultipleAnnotationContainer(); - assertThat(container.isEmpty()).isTrue(); - } - - @Test - void isEmptyWithAnnotation() { - MultipleAnnotationContainer container = new MultipleAnnotationContainer(); - container.addToList(TEST_CLASS_NAME, (builder) -> builder.add("value", "test")); - assertThat(container.isEmpty()).isFalse(); - } - - @Test - void hasWithMatchingAnnotation() { - MultipleAnnotationContainer container = new MultipleAnnotationContainer(); - container.addToList(TEST_CLASS_NAME, (builder) -> builder.add("value", "test")); - assertThat(container.has(TEST_CLASS_NAME)).isTrue(); - } - - @Test - void hasWithNonMatchingAnnotation() { - MultipleAnnotationContainer container = new MultipleAnnotationContainer(); - container.addToList(TEST_CLASS_NAME, (builder) -> builder.add("value", "test")); - assertThat(container.has(OTHER_CLASS_NAME)).isFalse(); - } - - @Test - void valuesShouldReturnAllAnnotations() { - MultipleAnnotationContainer container = new MultipleAnnotationContainer(); - container.addToList(TEST_CLASS_NAME, (builder) -> builder.add("value", "one")); - container.addToList(TEST_CLASS_NAME, (builder) -> builder.add("value", "two")); - List annotations = container.values().collect(Collectors.toList()); - assertThat(annotations).hasSize(2); - assertThat(annotations).allMatch((annotation) -> annotation.getClassName().equals(TEST_CLASS_NAME)); - } - - @Test - void valuesOfShouldReturnMatchingAnnotationsOnly() { - MultipleAnnotationContainer container = new MultipleAnnotationContainer(); - container.addToList(TEST_CLASS_NAME, (builder) -> builder.add("value", "one")); - container.addToList(OTHER_CLASS_NAME, (builder) -> builder.add("name", "other")); - List annotations = container.valuesOf(TEST_CLASS_NAME).collect(Collectors.toList()); - assertThat(annotations).hasSize(1); - assertThat(annotations.get(0).getAttributes()).singleElement().satisfies((attribute) -> { - assertThat(attribute.getName()).isEqualTo("value"); - assertThat(attribute.getValues()).containsExactly("one"); - }); - } - - @Test - void valuesOfShouldReturnEmptyStreamForNonExistingClassName() { - MultipleAnnotationContainer container = new MultipleAnnotationContainer(); - container.addToList(TEST_CLASS_NAME); - assertThat(container.valuesOf(OTHER_CLASS_NAME)).isEmpty(); - } - - @Test - void countOfShouldReturnCorrectCount() { - MultipleAnnotationContainer container = new MultipleAnnotationContainer(); - container.addToList(TEST_CLASS_NAME); - container.addToList(TEST_CLASS_NAME); - assertThat(container.countOf(TEST_CLASS_NAME)).isEqualTo(2); - assertThat(container.countOf(OTHER_CLASS_NAME)).isZero(); - } - - @Test - void removeAllShouldRemoveAllAnnotationsOfClassName() { - MultipleAnnotationContainer container = new MultipleAnnotationContainer(); - container.addToList(TEST_CLASS_NAME); - container.addToList(TEST_CLASS_NAME); - int removed = container.removeAll(TEST_CLASS_NAME); - assertThat(removed).isEqualTo(2); - assertThat(container.has(TEST_CLASS_NAME)).isFalse(); - } - - @Test - void removeShouldReturnFalseIfAllAnnotationRemoved() { - MultipleAnnotationContainer container = new MultipleAnnotationContainer(); - container.addToList(TEST_CLASS_NAME); - container.addToList(TEST_CLASS_NAME); - assertThat(container.remove(TEST_CLASS_NAME)).isTrue(); - assertThat(container.has(TEST_CLASS_NAME)).isFalse(); - } - - @Test - void removeShouldReturnFalseIfNoAnnotationRemoved() { - MultipleAnnotationContainer container = new MultipleAnnotationContainer(); - assertThat(container.remove(TEST_CLASS_NAME)).isFalse(); - } - - @Test - void hasMultipleReturnsTrueForMultipleAnnotations() { - MultipleAnnotationContainer container = new MultipleAnnotationContainer(); - container.addToList(TEST_CLASS_NAME); - container.addToList(TEST_CLASS_NAME); - assertThat(container.hasMultiple(TEST_CLASS_NAME)).isTrue(); - assertThat(container.hasMultiple(OTHER_CLASS_NAME)).isFalse(); - } - - @Test - void addUnsupportedMethodsThrowException() { - MultipleAnnotationContainer container = new MultipleAnnotationContainer(); - assertThatThrownBy(() -> container.add(TEST_CLASS_NAME)).isInstanceOf(UnsupportedOperationException.class); - assertThatThrownBy(() -> container.add(TEST_CLASS_NAME, (builder) -> { - })).isInstanceOf(UnsupportedOperationException.class); - } - - @Test - void deepCopyShouldCreateDistinctObjectReferences() { - MultipleAnnotationContainer container = new MultipleAnnotationContainer(); - container.addToList(TEST_CLASS_NAME, (builder) -> builder.add("value", "test")); - - MultipleAnnotationContainer copy = container.deepCopy(); - - assertThat(copy).isNotSameAs(container); - - List originalAnnotations = container.valuesOf(TEST_CLASS_NAME).collect(Collectors.toList()); - List copiedAnnotations = copy.valuesOf(TEST_CLASS_NAME).collect(Collectors.toList()); - - assertThat(copiedAnnotations).hasSize(originalAnnotations.size()); - for (int i = 0; i < originalAnnotations.size(); i++) { - assertThat(copiedAnnotations.get(i)).isNotSameAs(originalAnnotations.get(i)); - } - } - - @Test - void deepCopyMutationShouldNotAffectOriginal() { - MultipleAnnotationContainer container = new MultipleAnnotationContainer(); - container.addToList(TEST_CLASS_NAME, (builder) -> builder.add("value", "original")); - - MultipleAnnotationContainer copy = container.deepCopy(); - - copy.addToList(TEST_CLASS_NAME, (builder) -> builder.add("value", "new")); - copy.addToList(OTHER_CLASS_NAME, (builder) -> builder.add("other", "test")); - - assertThat(container.countOf(TEST_CLASS_NAME)).isEqualTo(1); - assertThat(container.has(OTHER_CLASS_NAME)).isFalse(); - - assertThat(copy.countOf(TEST_CLASS_NAME)).isEqualTo(2); - assertThat(copy.has(OTHER_CLASS_NAME)).isTrue(); - } - -} diff --git a/initializr-generator/src/test/java/io/spring/initializr/generator/language/groovy/GroovySourceCodeWriterTests.java b/initializr-generator/src/test/java/io/spring/initializr/generator/language/groovy/GroovySourceCodeWriterTests.java index d18074d818..c960402ef5 100644 --- a/initializr-generator/src/test/java/io/spring/initializr/generator/language/groovy/GroovySourceCodeWriterTests.java +++ b/initializr-generator/src/test/java/io/spring/initializr/generator/language/groovy/GroovySourceCodeWriterTests.java @@ -33,7 +33,6 @@ 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.MultipleAnnotationContainer; import io.spring.initializr.generator.language.Parameter; import io.spring.initializr.generator.language.SourceStructure; import org.junit.jupiter.api.Test; @@ -322,55 +321,6 @@ void methodWithParameterAnnotation() throws IOException { " void something(@Service MyService service) {", " }", "", "}"); } - @Test - void multipleAnnotationsOnClass() throws IOException { - GroovySourceCode sourceCode = new GroovySourceCode(); - GroovyCompilationUnit compilationUnit = sourceCode.createCompilationUnit("com.example", "Test"); - MultipleAnnotationContainer classAnnotations = new MultipleAnnotationContainer(); - classAnnotations.addToList(ClassName.of("com.example.TestClassAnnotation")); - classAnnotations.addToList(ClassName.of("com.example.TestClassAnnotation")); - GroovyTypeDeclaration test = compilationUnit.createTypeDeclaration("Test", classAnnotations); - List lines = writeSingleType(sourceCode, "com/example/Test.groovy"); - assertThat(lines).containsExactly("package com.example", "", "@TestClassAnnotation", "@TestClassAnnotation", - "class Test {", "", "}"); - } - - @Test - void multipleAnnotationsOnField() throws IOException { - GroovySourceCode sourceCode = new GroovySourceCode(); - GroovyCompilationUnit compilationUnit = sourceCode.createCompilationUnit("com.example", "Test"); - GroovyTypeDeclaration test = compilationUnit.createTypeDeclaration("Test"); - MultipleAnnotationContainer fieldAnnotations = new MultipleAnnotationContainer(); - fieldAnnotations.addToList(ClassName.of("com.example.TestFiledAnnotation")); - fieldAnnotations.addToList(ClassName.of("com.example.TestFiledAnnotation")); - GroovyFieldDeclaration field = GroovyFieldDeclaration.field("testField") - .annotations(fieldAnnotations) - .returning("java.lang.String"); - test.addFieldDeclaration(field); - List lines = writeSingleType(sourceCode, "com/example/Test.groovy"); - assertThat(lines).containsExactly("package com.example", "", "class Test {", "", " @TestFiledAnnotation", - " @TestFiledAnnotation", " String testField", "", "}"); - } - - @Test - void multipleAnnotationsOnMethod() throws IOException { - GroovySourceCode sourceCode = new GroovySourceCode(); - GroovyCompilationUnit compilationUnit = sourceCode.createCompilationUnit("com.example", "Test"); - GroovyTypeDeclaration test = compilationUnit.createTypeDeclaration("Test"); - MultipleAnnotationContainer methodAnnotations = new MultipleAnnotationContainer(); - methodAnnotations.addToList(ClassName.of("com.example.TestMethodAnnotation")); - methodAnnotations.addToList(ClassName.of("com.example.TestMethodAnnotation")); - GroovyMethodDeclaration method = GroovyMethodDeclaration.method("testMethod") - .annotations(methodAnnotations) - .returning("void") - .parameters() - .body(CodeBlock.of("")); - test.addMethodDeclaration(method); - List lines = writeSingleType(sourceCode, "com/example/Test.groovy"); - assertThat(lines).containsExactly("package com.example", "", "class Test {", "", " @TestMethodAnnotation", - " @TestMethodAnnotation", " void testMethod() {", " }", "", "}"); - } - private List writeSingleType(GroovySourceCode sourceCode, String location) throws IOException { Path source = writeSourceCode(sourceCode).resolve(location); try (InputStream stream = Files.newInputStream(source)) { diff --git a/initializr-generator/src/test/java/io/spring/initializr/generator/language/java/JavaSourceCodeWriterTests.java b/initializr-generator/src/test/java/io/spring/initializr/generator/language/java/JavaSourceCodeWriterTests.java index c727ff8cee..66ffaf0a76 100644 --- a/initializr-generator/src/test/java/io/spring/initializr/generator/language/java/JavaSourceCodeWriterTests.java +++ b/initializr-generator/src/test/java/io/spring/initializr/generator/language/java/JavaSourceCodeWriterTests.java @@ -33,7 +33,6 @@ 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.MultipleAnnotationContainer; import io.spring.initializr.generator.language.Parameter; import io.spring.initializr.generator.language.SourceStructure; import org.junit.jupiter.api.Test; @@ -328,56 +327,6 @@ void methodWithParameterAnnotation() throws IOException { " void something(@Service MyService service) {", " }", "", "}"); } - @Test - void multipleAnnotationsOnClass() throws IOException { - JavaSourceCode sourceCode = new JavaSourceCode(); - JavaCompilationUnit compilationUnit = sourceCode.createCompilationUnit("com.example", "Test"); - MultipleAnnotationContainer classAnnotations = new MultipleAnnotationContainer(); - classAnnotations.addToList(ClassName.of("com.example.TestClassAnnotation")); - classAnnotations.addToList(ClassName.of("com.example.TestClassAnnotation")); - compilationUnit.createTypeDeclaration("Test", classAnnotations); - List lines = writeSingleType(sourceCode, "com/example/Test.java"); - assertThat(lines).containsExactly("package com.example;", "", "@TestClassAnnotation", "@TestClassAnnotation", - "class Test {", "", "}"); - } - - @Test - void multipleAnnotationsOnField() throws IOException { - JavaSourceCode sourceCode = new JavaSourceCode(); - JavaCompilationUnit compilationUnit = sourceCode.createCompilationUnit("com.example", "Test"); - JavaTypeDeclaration test = compilationUnit.createTypeDeclaration("Test"); - MultipleAnnotationContainer fieldAnnotations = new MultipleAnnotationContainer(); - fieldAnnotations.addToList(ClassName.of("com.example.TestFiledAnnotation")); - fieldAnnotations.addToList(ClassName.of("com.example.TestFiledAnnotation")); - JavaFieldDeclaration field = JavaFieldDeclaration.field("testField") - .annotations(fieldAnnotations) - .modifiers(Modifier.PRIVATE) - .returning("java.lang.String"); - test.addFieldDeclaration(field); - List lines = writeSingleType(sourceCode, "com/example/Test.java"); - assertThat(lines).containsExactly("package com.example;", "", "class Test {", "", " @TestFiledAnnotation", - " @TestFiledAnnotation", " private String testField;", "", "}"); - } - - @Test - void multipleAnnotationsOnMethod() throws IOException { - JavaSourceCode sourceCode = new JavaSourceCode(); - JavaCompilationUnit compilationUnit = sourceCode.createCompilationUnit("com.example", "Test"); - JavaTypeDeclaration test = compilationUnit.createTypeDeclaration("Test"); - MultipleAnnotationContainer methodAnnotations = new MultipleAnnotationContainer(); - methodAnnotations.addToList(ClassName.of("com.example.TestMethodAnnotation")); - methodAnnotations.addToList(ClassName.of("com.example.TestMethodAnnotation")); - JavaMethodDeclaration method = JavaMethodDeclaration.method("testMethod") - .annotations(methodAnnotations) - .returning("void") - .parameters() - .body(CodeBlock.of("")); - test.addMethodDeclaration(method); - List lines = writeSingleType(sourceCode, "com/example/Test.java"); - assertThat(lines).containsExactly("package com.example;", "", "class Test {", "", " @TestMethodAnnotation", - " @TestMethodAnnotation", " void testMethod() {", " }", "", "}"); - } - private List writeSingleType(JavaSourceCode sourceCode, String location) throws IOException { Path source = writeSourceCode(sourceCode).resolve(location); try (InputStream stream = Files.newInputStream(source)) { diff --git a/initializr-generator/src/test/java/io/spring/initializr/generator/language/kotlin/KotlinSourceCodeWriterTests.java b/initializr-generator/src/test/java/io/spring/initializr/generator/language/kotlin/KotlinSourceCodeWriterTests.java index 658cbc8b36..431508048d 100644 --- a/initializr-generator/src/test/java/io/spring/initializr/generator/language/kotlin/KotlinSourceCodeWriterTests.java +++ b/initializr-generator/src/test/java/io/spring/initializr/generator/language/kotlin/KotlinSourceCodeWriterTests.java @@ -32,7 +32,6 @@ 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.MultipleAnnotationContainer; import io.spring.initializr.generator.language.Parameter; import io.spring.initializr.generator.language.SourceStructure; import org.junit.jupiter.api.Test; @@ -387,54 +386,6 @@ void functionWithParameterAnnotation() throws IOException { " fun something(@Service service: MyService) {", " }", "", "}"); } - @Test - void multipleAnnotationsOnClass() throws IOException { - KotlinSourceCode sourceCode = new KotlinSourceCode(); - KotlinCompilationUnit compilationUnit = sourceCode.createCompilationUnit("com.example", "Test"); - MultipleAnnotationContainer classAnnotations = new MultipleAnnotationContainer(); - classAnnotations.addToList(ClassName.of("com.example.TestClassAnnotation")); - classAnnotations.addToList(ClassName.of("com.example.TestClassAnnotation")); - KotlinTypeDeclaration test = compilationUnit.createTypeDeclaration("Test", classAnnotations); - List lines = writeSingleType(sourceCode, "com/example/Test.kt"); - assertThat(lines).containsExactly("package com.example", "", "@TestClassAnnotation", "@TestClassAnnotation", - "class Test"); - } - - @Test - void multipleAnnotationsOnProperty() throws IOException { - KotlinSourceCode sourceCode = new KotlinSourceCode(); - KotlinCompilationUnit compilationUnit = sourceCode.createCompilationUnit("com.example", "Test"); - KotlinTypeDeclaration test = compilationUnit.createTypeDeclaration("Test"); - MultipleAnnotationContainer propertyAnnotations = new MultipleAnnotationContainer(); - propertyAnnotations.addToList(ClassName.of("com.example.TestPropertyAnnotation")); - propertyAnnotations.addToList(ClassName.of("com.example.TestPropertyAnnotation")); - KotlinPropertyDeclaration property = KotlinPropertyDeclaration.val("testProperty") - .annotations(propertyAnnotations) - .returning("String") - .emptyValue(); - test.addPropertyDeclaration(property); - List lines = writeSingleType(sourceCode, "com/example/Test.kt"); - assertThat(lines).containsExactly("package com.example", "", "class Test {", "", " @TestPropertyAnnotation", - " @TestPropertyAnnotation", " val testProperty: String", "", "}"); - } - - @Test - void multipleAnnotationsOnFunction() throws IOException { - KotlinSourceCode sourceCode = new KotlinSourceCode(); - KotlinCompilationUnit compilationUnit = sourceCode.createCompilationUnit("com.example", "Test"); - KotlinTypeDeclaration test = compilationUnit.createTypeDeclaration("Test"); - MultipleAnnotationContainer functionAnnotations = new MultipleAnnotationContainer(); - functionAnnotations.addToList(ClassName.of("com.example.TestFunctionAnnotation")); - functionAnnotations.addToList(ClassName.of("com.example.TestFunctionAnnotation")); - KotlinFunctionDeclaration function = KotlinFunctionDeclaration.function("testFunction") - .annotations(functionAnnotations) - .body(CodeBlock.of("")); - test.addFunctionDeclaration(function); - List lines = writeSingleType(sourceCode, "com/example/Test.kt"); - assertThat(lines).containsExactly("package com.example", "", "class Test {", "", " @TestFunctionAnnotation", - " @TestFunctionAnnotation", " fun testFunction() {", " }", "", "}"); - } - @Test void reservedKeywordsStartPackageName() throws IOException { KotlinSourceCode sourceCode = new KotlinSourceCode(); From 2d94c84926330b9c8a6860bfa369573261df0144 Mon Sep 17 00:00:00 2001 From: sijun-yang Date: Tue, 15 Jul 2025 21:32:57 +0900 Subject: [PATCH 19/28] Allow multiple annotations of the same type Signed-off-by: sijun-yang --- .../language/AnnotationContainer.java | 68 +++++++++++++++---- 1 file changed, 55 insertions(+), 13 deletions(-) diff --git a/initializr-generator/src/main/java/io/spring/initializr/generator/language/AnnotationContainer.java b/initializr-generator/src/main/java/io/spring/initializr/generator/language/AnnotationContainer.java index 8526797715..d15df3a6db 100644 --- a/initializr-generator/src/main/java/io/spring/initializr/generator/language/AnnotationContainer.java +++ b/initializr-generator/src/main/java/io/spring/initializr/generator/language/AnnotationContainer.java @@ -30,13 +30,13 @@ */ public class AnnotationContainer { - private final Map annotations; + private final Map annotations; public AnnotationContainer() { this(new LinkedHashMap<>()); } - private AnnotationContainer(Map annotations) { + private AnnotationContainer(Map annotations) { this.annotations = annotations; } @@ -49,12 +49,22 @@ public boolean isEmpty() { } /** - * Specify if this container has a an annotation with the specified {@link ClassName}. + * Specify if this container has 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 this.annotations.containsKey(className.getCanonicalName()); + } + + /** + * Specify if this instance contains an annotation with the specified name. + * @param name the name of the annotation + * @return {@code true} if an annotation with the specified name is present, + * otherwise {@code false} + */ + public boolean has(String name) { + return this.annotations.containsKey(name); } /** @@ -65,18 +75,41 @@ public Stream values() { return this.annotations.values().stream().map(Builder::build); } + /** + * Add an {@link Annotation} with the specific name and {@link Consumer} to + * customize it. If an annotation with that name already exists, the consumer can + * be used to further tune its attributes. + * @param name the name of the annotation + * @param className the class name of the annotation + * @param annotation a {@link Consumer} to further configure the annotation + */ + public void add(String name, ClassName className, Consumer annotation) { + Builder builder = this.annotations.computeIfAbsent(name, + (key) -> new Builder(className)); + if (annotation != null) { + annotation.accept(builder); + } + } + + /** + * Add an {@link Annotation} with the specific name. Does nothing If an annotation + * with that name already exists. + * @param name the name of the annotation + * @param className the class name of the annotation + */ + public void add(String name, ClassName className) { + add(name, className, null); + } + /** * 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 + * 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 annotation) { - Builder builder = this.annotations.computeIfAbsent(className, (key) -> new Builder(className)); - if (annotation != null) { - annotation.accept(builder); - } + add(className.getCanonicalName(), className, annotation); } /** @@ -85,7 +118,7 @@ public void add(ClassName className, Consumer annotation) { * @param className the class name of an annotation */ public void add(ClassName className) { - add(className, null); + add(className.getCanonicalName(), className, null); } /** @@ -94,12 +127,21 @@ public void add(ClassName className) { * @return {@code true} if such an annotation exists, {@code false} otherwise */ public boolean remove(ClassName className) { - return this.annotations.remove(className) != null; + return this.annotations.remove(className.getCanonicalName()) != null; + } + + /** + * Remove the annotation with the specified name. + * @param name the name of the annotation to remove + * @return {@code true} if the annotation was removed, {@code false} otherwise + */ + public boolean remove(String name) { + return this.annotations.remove(name) != null; } public AnnotationContainer deepCopy() { - Map copy = new LinkedHashMap<>(); - this.annotations.forEach((className, builder) -> copy.put(className, new Builder(builder))); + Map copy = new LinkedHashMap<>(); + this.annotations.forEach((name, builder) -> copy.put(name, new Builder(builder))); return new AnnotationContainer(copy); } From 557687bc073cbb4c3eaeec09c9c575694c95d60b Mon Sep 17 00:00:00 2001 From: sijun-yang Date: Tue, 15 Jul 2025 21:35:07 +0900 Subject: [PATCH 20/28] Add tests of multiple annotations of the same type Signed-off-by: sijun-yang --- .../language/AnnotationContainerTests.java | 36 +++++++++++++++++++ 1 file changed, 36 insertions(+) diff --git a/initializr-generator/src/test/java/io/spring/initializr/generator/language/AnnotationContainerTests.java b/initializr-generator/src/test/java/io/spring/initializr/generator/language/AnnotationContainerTests.java index 5a892004de..7e5db61b84 100644 --- a/initializr-generator/src/test/java/io/spring/initializr/generator/language/AnnotationContainerTests.java +++ b/initializr-generator/src/test/java/io/spring/initializr/generator/language/AnnotationContainerTests.java @@ -110,6 +110,42 @@ void addAnnotationSeveralTimeCanReplaceAttribute() { }); } + @Test + void addWithDifferentNamesAddsTwoAnnotations() { + AnnotationContainer container = new AnnotationContainer(); + container.add("test-1", TEST_CLASS_NAME, (annotation) -> annotation.set("id", 1)); + container.add("test-2", TEST_CLASS_NAME, (annotation) -> annotation.set("id", 2)); + assertThat(container.values()).satisfiesExactlyInAnyOrder( + (a) -> assertThat(a.getAttributes()).singleElement() + .satisfies((attribute) -> assertThat(attribute.getValues()).containsOnly(1)), + (a) -> assertThat(a.getAttributes()).singleElement() + .satisfies((attribute) -> assertThat(attribute.getValues()).containsOnly(2))); + } + + @Test + void hasWithName() { + AnnotationContainer container = new AnnotationContainer(); + container.add("test", TEST_CLASS_NAME); + assertThat(container.has("test")).isTrue(); + assertThat(container.has("test-no")).isFalse(); + } + + @Test + void removeWithNameRemovesMatchingAnnotation() { + AnnotationContainer container = new AnnotationContainer(); + container.add("test", TEST_CLASS_NAME); + assertThat(container.remove("test")).isTrue(); + assertThat(container.isEmpty()).isTrue(); + } + + @Test + void removeWithNameWithNonMatchingAnnotation() { + AnnotationContainer container = new AnnotationContainer(); + container.add("test", TEST_CLASS_NAME); + assertThat(container.remove("test-no")).isFalse(); + assertThat(container.isEmpty()).isFalse(); + } + @Test void removeWithMatchingAnnotation() { AnnotationContainer container = new AnnotationContainer(); From d3d2dd856d2cc7ae0290dc9622627b26f3612785 Mon Sep 17 00:00:00 2001 From: sijun-yang Date: Tue, 15 Jul 2025 21:50:44 +0900 Subject: [PATCH 21/28] Fix writeProperty to write annotations Signed-off-by: sijun-yang --- .../generator/language/kotlin/KotlinSourceCodeWriter.java | 1 + 1 file changed, 1 insertion(+) diff --git a/initializr-generator/src/main/java/io/spring/initializr/generator/language/kotlin/KotlinSourceCodeWriter.java b/initializr-generator/src/main/java/io/spring/initializr/generator/language/kotlin/KotlinSourceCodeWriter.java index 7fc79adc1a..ff46ef9f08 100644 --- a/initializr-generator/src/main/java/io/spring/initializr/generator/language/kotlin/KotlinSourceCodeWriter.java +++ b/initializr-generator/src/main/java/io/spring/initializr/generator/language/kotlin/KotlinSourceCodeWriter.java @@ -162,6 +162,7 @@ private String escapeKotlinKeywords(String packageName) { private void writeProperty(IndentingWriter writer, KotlinPropertyDeclaration propertyDeclaration) { writer.println(); + writeAnnotations(writer, propertyDeclaration); writeModifiers(writer, propertyDeclaration.getModifiers()); if (propertyDeclaration.isVal()) { writer.print("val "); From 15b5cf401707614c721134fc83627705c41e8a82 Mon Sep 17 00:00:00 2001 From: sijun-yang Date: Tue, 15 Jul 2025 21:52:49 +0900 Subject: [PATCH 22/28] Add tests about repeatable annotations Signed-off-by: sijun-yang --- .../groovy/GroovySourceCodeWriterTests.java | 43 ++++++++++++++++++ .../java/JavaSourceCodeWriterTests.java | 45 +++++++++++++++++++ .../kotlin/KotlinSourceCodeWriterTests.java | 42 +++++++++++++++++ 3 files changed, 130 insertions(+) diff --git a/initializr-generator/src/test/java/io/spring/initializr/generator/language/groovy/GroovySourceCodeWriterTests.java b/initializr-generator/src/test/java/io/spring/initializr/generator/language/groovy/GroovySourceCodeWriterTests.java index c960402ef5..9fb2e4a68b 100644 --- a/initializr-generator/src/test/java/io/spring/initializr/generator/language/groovy/GroovySourceCodeWriterTests.java +++ b/initializr-generator/src/test/java/io/spring/initializr/generator/language/groovy/GroovySourceCodeWriterTests.java @@ -321,6 +321,49 @@ void methodWithParameterAnnotation() throws IOException { " void something(@Service MyService service) {", " }", "", "}"); } + @Test + void repeatableAnnotationsOnClass() throws IOException { + GroovySourceCode sourceCode = new GroovySourceCode(); + GroovyCompilationUnit compilationUnit = sourceCode.createCompilationUnit("com.example", "Test"); + GroovyTypeDeclaration test = compilationUnit.createTypeDeclaration("Test"); + test.annotations().add("TestAnnotation1", ClassName.of("com.example.TestClassAnnotation")); + test.annotations().add("TestAnnotation2", ClassName.of("com.example.TestClassAnnotation")); + List lines = writeSingleType(sourceCode, "com/example/Test.groovy"); + assertThat(lines).containsExactly("package com.example", "", "@TestClassAnnotation", "@TestClassAnnotation", + "class Test {", "", "}"); + } + + @Test + void repeatableAnnotationsOnField() throws IOException { + GroovySourceCode sourceCode = new GroovySourceCode(); + GroovyCompilationUnit compilationUnit = sourceCode.createCompilationUnit("com.example", "Test"); + GroovyTypeDeclaration test = compilationUnit.createTypeDeclaration("Test"); + GroovyFieldDeclaration field = GroovyFieldDeclaration.field("testField").returning("java.lang.String"); + field.annotations().add("TestAnnotation1", ClassName.of("com.example.TestFiledAnnotation")); + field.annotations().add("TestAnnotation2", ClassName.of("com.example.TestFiledAnnotation")); + test.addFieldDeclaration(field); + List lines = writeSingleType(sourceCode, "com/example/Test.groovy"); + assertThat(lines).containsExactly("package com.example", "", "class Test {", "", " @TestFiledAnnotation", + " @TestFiledAnnotation", " String testField", "", "}"); + } + + @Test + void repeatableAnnotationsOnMethod() throws IOException { + GroovySourceCode sourceCode = new GroovySourceCode(); + GroovyCompilationUnit compilationUnit = sourceCode.createCompilationUnit("com.example", "Test"); + GroovyTypeDeclaration test = compilationUnit.createTypeDeclaration("Test"); + GroovyMethodDeclaration method = GroovyMethodDeclaration.method("testMethod") + .returning("void") + .parameters() + .body(CodeBlock.of("")); + method.annotations().add("TestAnnotation1", ClassName.of("com.example.TestMethodAnnotation")); + method.annotations().add("TestAnnotation2", ClassName.of("com.example.TestMethodAnnotation")); + test.addMethodDeclaration(method); + List lines = writeSingleType(sourceCode, "com/example/Test.groovy"); + assertThat(lines).containsExactly("package com.example", "", "class Test {", "", " @TestMethodAnnotation", + " @TestMethodAnnotation", " void testMethod() {", " }", "", "}"); + } + private List writeSingleType(GroovySourceCode sourceCode, String location) throws IOException { Path source = writeSourceCode(sourceCode).resolve(location); try (InputStream stream = Files.newInputStream(source)) { diff --git a/initializr-generator/src/test/java/io/spring/initializr/generator/language/java/JavaSourceCodeWriterTests.java b/initializr-generator/src/test/java/io/spring/initializr/generator/language/java/JavaSourceCodeWriterTests.java index 66ffaf0a76..19dd92fd31 100644 --- a/initializr-generator/src/test/java/io/spring/initializr/generator/language/java/JavaSourceCodeWriterTests.java +++ b/initializr-generator/src/test/java/io/spring/initializr/generator/language/java/JavaSourceCodeWriterTests.java @@ -327,6 +327,51 @@ void methodWithParameterAnnotation() throws IOException { " void something(@Service MyService service) {", " }", "", "}"); } + @Test + void repeatableAnnotationsOnClass() throws IOException { + JavaSourceCode sourceCode = new JavaSourceCode(); + JavaCompilationUnit compilationUnit = sourceCode.createCompilationUnit("com.example", "Test"); + JavaTypeDeclaration test = compilationUnit.createTypeDeclaration("Test"); + test.annotations().add("TestAnnotation1", ClassName.of("com.example.TestClassAnnotation")); + test.annotations().add("TestAnnotation2", ClassName.of("com.example.TestClassAnnotation")); + List lines = writeSingleType(sourceCode, "com/example/Test.java"); + assertThat(lines).containsExactly("package com.example;", "", "@TestClassAnnotation", "@TestClassAnnotation", + "class Test {", "", "}"); + } + + @Test + void repeatableAnnotationsOnField() throws IOException { + JavaSourceCode sourceCode = new JavaSourceCode(); + JavaCompilationUnit compilationUnit = sourceCode.createCompilationUnit("com.example", "Test"); + JavaTypeDeclaration test = compilationUnit.createTypeDeclaration("Test"); + JavaFieldDeclaration field = JavaFieldDeclaration.field("testField") + .modifiers(Modifier.PRIVATE) + .returning("java.lang.String"); + field.annotations().add("TestAnnotation1", ClassName.of("com.example.TestFiledAnnotation")); + field.annotations().add("TestAnnotation2", ClassName.of("com.example.TestFiledAnnotation")); + test.addFieldDeclaration(field); + List lines = writeSingleType(sourceCode, "com/example/Test.java"); + assertThat(lines).containsExactly("package com.example;", "", "class Test {", "", " @TestFiledAnnotation", + " @TestFiledAnnotation", " private String testField;", "", "}"); + } + + @Test + void repeatableAnnotationsOnMethod() throws IOException { + JavaSourceCode sourceCode = new JavaSourceCode(); + JavaCompilationUnit compilationUnit = sourceCode.createCompilationUnit("com.example", "Test"); + JavaTypeDeclaration test = compilationUnit.createTypeDeclaration("Test"); + JavaMethodDeclaration method = JavaMethodDeclaration.method("testMethod") + .returning("void") + .parameters() + .body(CodeBlock.of("")); + method.annotations().add("TestAnnotation1", ClassName.of("com.example.TestMethodAnnotation")); + method.annotations().add("TestAnnotation2", ClassName.of("com.example.TestMethodAnnotation")); + test.addMethodDeclaration(method); + List lines = writeSingleType(sourceCode, "com/example/Test.java"); + assertThat(lines).containsExactly("package com.example;", "", "class Test {", "", " @TestMethodAnnotation", + " @TestMethodAnnotation", " void testMethod() {", " }", "", "}"); + } + private List writeSingleType(JavaSourceCode sourceCode, String location) throws IOException { Path source = writeSourceCode(sourceCode).resolve(location); try (InputStream stream = Files.newInputStream(source)) { diff --git a/initializr-generator/src/test/java/io/spring/initializr/generator/language/kotlin/KotlinSourceCodeWriterTests.java b/initializr-generator/src/test/java/io/spring/initializr/generator/language/kotlin/KotlinSourceCodeWriterTests.java index 431508048d..d67674b61e 100644 --- a/initializr-generator/src/test/java/io/spring/initializr/generator/language/kotlin/KotlinSourceCodeWriterTests.java +++ b/initializr-generator/src/test/java/io/spring/initializr/generator/language/kotlin/KotlinSourceCodeWriterTests.java @@ -434,6 +434,48 @@ void reservedJavaKeywordsEndPackageName() throws IOException { assertThat(lines).containsExactly("package com.example.`package`"); } + @Test + void repeatableAnnotationsOnClass() throws IOException { + KotlinSourceCode sourceCode = new KotlinSourceCode(); + KotlinCompilationUnit compilationUnit = sourceCode.createCompilationUnit("com.example", "Test"); + KotlinTypeDeclaration test = compilationUnit.createTypeDeclaration("Test"); + test.annotations().add("TestAnnotation1", ClassName.of("com.example.TestClassAnnotation")); + test.annotations().add("TestAnnotation2", ClassName.of("com.example.TestClassAnnotation")); + List lines = writeSingleType(sourceCode, "com/example/Test.kt"); + assertThat(lines).containsExactly("package com.example", "", "@TestClassAnnotation", "@TestClassAnnotation", + "class Test"); + } + + @Test + void repeatableAnnotationsOnField() throws IOException { + KotlinSourceCode sourceCode = new KotlinSourceCode(); + KotlinCompilationUnit compilationUnit = sourceCode.createCompilationUnit("com.example", "Test"); + KotlinTypeDeclaration test = compilationUnit.createTypeDeclaration("Test"); + KotlinPropertyDeclaration field = KotlinPropertyDeclaration.var("testField") + .returning("java.lang.String").empty(); + field.annotations().add("TestAnnotation1", ClassName.of("com.example.TestFiledAnnotation")); + field.annotations().add("TestAnnotation2", ClassName.of("com.example.TestFiledAnnotation")); + test.addPropertyDeclaration(field); + List lines = writeSingleType(sourceCode, "com/example/Test.kt"); + assertThat(lines).containsExactly("package com.example", "", "class Test {", "", " @TestFiledAnnotation", + " @TestFiledAnnotation", " var testField: String", "", "}"); + } + + @Test + void repeatableAnnotationsOnMethod() throws IOException { + KotlinSourceCode sourceCode = new KotlinSourceCode(); + KotlinCompilationUnit compilationUnit = sourceCode.createCompilationUnit("com.example", "Test"); + KotlinTypeDeclaration test = compilationUnit.createTypeDeclaration("Test"); + KotlinFunctionDeclaration method = KotlinFunctionDeclaration.function("testMethod") + .body(CodeBlock.of("")); + method.annotations().add("TestAnnotation1", ClassName.of("com.example.TestMethodAnnotation")); + method.annotations().add("TestAnnotation2", ClassName.of("com.example.TestMethodAnnotation")); + test.addFunctionDeclaration(method); + List lines = writeSingleType(sourceCode, "com/example/Test.kt"); + assertThat(lines).containsExactly("package com.example", "", "class Test {", "", " @TestMethodAnnotation", + " @TestMethodAnnotation", " fun testMethod() {", " }", "", "}"); + } + private List writeSingleType(KotlinSourceCode sourceCode, String location) throws IOException { Path source = writeSourceCode(sourceCode).resolve(location); try (InputStream stream = Files.newInputStream(source)) { From 78cd91a6b1d3f9c3abb7b556c4871ca66e02e3bc Mon Sep 17 00:00:00 2001 From: sijun-yang Date: Tue, 15 Jul 2025 22:15:50 +0900 Subject: [PATCH 23/28] Chore Signed-off-by: sijun-yang --- .../generator/language/AnnotationContainer.java | 14 +++++++------- .../language/AnnotationContainerTests.java | 8 ++++---- .../groovy/GroovySourceCodeWriterTests.java | 6 +++--- .../language/java/JavaSourceCodeWriterTests.java | 10 +++++----- .../kotlin/KotlinSourceCodeWriterTests.java | 6 +++--- 5 files changed, 22 insertions(+), 22 deletions(-) diff --git a/initializr-generator/src/main/java/io/spring/initializr/generator/language/AnnotationContainer.java b/initializr-generator/src/main/java/io/spring/initializr/generator/language/AnnotationContainer.java index d15df3a6db..4e97149925 100644 --- a/initializr-generator/src/main/java/io/spring/initializr/generator/language/AnnotationContainer.java +++ b/initializr-generator/src/main/java/io/spring/initializr/generator/language/AnnotationContainer.java @@ -27,6 +27,7 @@ * A container for {@linkplain Annotation annotations} defined on an annotated element. * * @author Stephane Nicoll + * @author Sijun Yang */ public class AnnotationContainer { @@ -60,8 +61,8 @@ public boolean has(ClassName className) { /** * Specify if this instance contains an annotation with the specified name. * @param name the name of the annotation - * @return {@code true} if an annotation with the specified name is present, - * otherwise {@code false} + * @return {@code true} if an annotation with the specified name is present, otherwise + * {@code false} */ public boolean has(String name) { return this.annotations.containsKey(name); @@ -76,16 +77,15 @@ public Stream values() { } /** - * Add an {@link Annotation} with the specific name and {@link Consumer} to - * customize it. If an annotation with that name already exists, the consumer can - * be used to further tune its attributes. + * Add an {@link Annotation} with the specific name and {@link Consumer} to customize + * it. If an annotation with that name already exists, the consumer can be used to + * further tune its attributes. * @param name the name of the annotation * @param className the class name of the annotation * @param annotation a {@link Consumer} to further configure the annotation */ public void add(String name, ClassName className, Consumer annotation) { - Builder builder = this.annotations.computeIfAbsent(name, - (key) -> new Builder(className)); + Builder builder = this.annotations.computeIfAbsent(name, (key) -> new Builder(className)); if (annotation != null) { annotation.accept(builder); } diff --git a/initializr-generator/src/test/java/io/spring/initializr/generator/language/AnnotationContainerTests.java b/initializr-generator/src/test/java/io/spring/initializr/generator/language/AnnotationContainerTests.java index 7e5db61b84..282ca734d0 100644 --- a/initializr-generator/src/test/java/io/spring/initializr/generator/language/AnnotationContainerTests.java +++ b/initializr-generator/src/test/java/io/spring/initializr/generator/language/AnnotationContainerTests.java @@ -116,10 +116,10 @@ void addWithDifferentNamesAddsTwoAnnotations() { container.add("test-1", TEST_CLASS_NAME, (annotation) -> annotation.set("id", 1)); container.add("test-2", TEST_CLASS_NAME, (annotation) -> annotation.set("id", 2)); assertThat(container.values()).satisfiesExactlyInAnyOrder( - (a) -> assertThat(a.getAttributes()).singleElement() - .satisfies((attribute) -> assertThat(attribute.getValues()).containsOnly(1)), - (a) -> assertThat(a.getAttributes()).singleElement() - .satisfies((attribute) -> assertThat(attribute.getValues()).containsOnly(2))); + (a) -> assertThat(a.getAttributes()).singleElement() + .satisfies((attribute) -> assertThat(attribute.getValues()).containsOnly(1)), + (a) -> assertThat(a.getAttributes()).singleElement() + .satisfies((attribute) -> assertThat(attribute.getValues()).containsOnly(2))); } @Test diff --git a/initializr-generator/src/test/java/io/spring/initializr/generator/language/groovy/GroovySourceCodeWriterTests.java b/initializr-generator/src/test/java/io/spring/initializr/generator/language/groovy/GroovySourceCodeWriterTests.java index 9fb2e4a68b..6f4ee0c40f 100644 --- a/initializr-generator/src/test/java/io/spring/initializr/generator/language/groovy/GroovySourceCodeWriterTests.java +++ b/initializr-generator/src/test/java/io/spring/initializr/generator/language/groovy/GroovySourceCodeWriterTests.java @@ -353,9 +353,9 @@ void repeatableAnnotationsOnMethod() throws IOException { GroovyCompilationUnit compilationUnit = sourceCode.createCompilationUnit("com.example", "Test"); GroovyTypeDeclaration test = compilationUnit.createTypeDeclaration("Test"); GroovyMethodDeclaration method = GroovyMethodDeclaration.method("testMethod") - .returning("void") - .parameters() - .body(CodeBlock.of("")); + .returning("void") + .parameters() + .body(CodeBlock.of("")); method.annotations().add("TestAnnotation1", ClassName.of("com.example.TestMethodAnnotation")); method.annotations().add("TestAnnotation2", ClassName.of("com.example.TestMethodAnnotation")); test.addMethodDeclaration(method); diff --git a/initializr-generator/src/test/java/io/spring/initializr/generator/language/java/JavaSourceCodeWriterTests.java b/initializr-generator/src/test/java/io/spring/initializr/generator/language/java/JavaSourceCodeWriterTests.java index 19dd92fd31..9ec35402de 100644 --- a/initializr-generator/src/test/java/io/spring/initializr/generator/language/java/JavaSourceCodeWriterTests.java +++ b/initializr-generator/src/test/java/io/spring/initializr/generator/language/java/JavaSourceCodeWriterTests.java @@ -345,8 +345,8 @@ void repeatableAnnotationsOnField() throws IOException { JavaCompilationUnit compilationUnit = sourceCode.createCompilationUnit("com.example", "Test"); JavaTypeDeclaration test = compilationUnit.createTypeDeclaration("Test"); JavaFieldDeclaration field = JavaFieldDeclaration.field("testField") - .modifiers(Modifier.PRIVATE) - .returning("java.lang.String"); + .modifiers(Modifier.PRIVATE) + .returning("java.lang.String"); field.annotations().add("TestAnnotation1", ClassName.of("com.example.TestFiledAnnotation")); field.annotations().add("TestAnnotation2", ClassName.of("com.example.TestFiledAnnotation")); test.addFieldDeclaration(field); @@ -361,9 +361,9 @@ void repeatableAnnotationsOnMethod() throws IOException { JavaCompilationUnit compilationUnit = sourceCode.createCompilationUnit("com.example", "Test"); JavaTypeDeclaration test = compilationUnit.createTypeDeclaration("Test"); JavaMethodDeclaration method = JavaMethodDeclaration.method("testMethod") - .returning("void") - .parameters() - .body(CodeBlock.of("")); + .returning("void") + .parameters() + .body(CodeBlock.of("")); method.annotations().add("TestAnnotation1", ClassName.of("com.example.TestMethodAnnotation")); method.annotations().add("TestAnnotation2", ClassName.of("com.example.TestMethodAnnotation")); test.addMethodDeclaration(method); diff --git a/initializr-generator/src/test/java/io/spring/initializr/generator/language/kotlin/KotlinSourceCodeWriterTests.java b/initializr-generator/src/test/java/io/spring/initializr/generator/language/kotlin/KotlinSourceCodeWriterTests.java index d67674b61e..f387c88ad5 100644 --- a/initializr-generator/src/test/java/io/spring/initializr/generator/language/kotlin/KotlinSourceCodeWriterTests.java +++ b/initializr-generator/src/test/java/io/spring/initializr/generator/language/kotlin/KotlinSourceCodeWriterTests.java @@ -452,7 +452,8 @@ void repeatableAnnotationsOnField() throws IOException { KotlinCompilationUnit compilationUnit = sourceCode.createCompilationUnit("com.example", "Test"); KotlinTypeDeclaration test = compilationUnit.createTypeDeclaration("Test"); KotlinPropertyDeclaration field = KotlinPropertyDeclaration.var("testField") - .returning("java.lang.String").empty(); + .returning("java.lang.String") + .empty(); field.annotations().add("TestAnnotation1", ClassName.of("com.example.TestFiledAnnotation")); field.annotations().add("TestAnnotation2", ClassName.of("com.example.TestFiledAnnotation")); test.addPropertyDeclaration(field); @@ -466,8 +467,7 @@ void repeatableAnnotationsOnMethod() throws IOException { KotlinSourceCode sourceCode = new KotlinSourceCode(); KotlinCompilationUnit compilationUnit = sourceCode.createCompilationUnit("com.example", "Test"); KotlinTypeDeclaration test = compilationUnit.createTypeDeclaration("Test"); - KotlinFunctionDeclaration method = KotlinFunctionDeclaration.function("testMethod") - .body(CodeBlock.of("")); + KotlinFunctionDeclaration method = KotlinFunctionDeclaration.function("testMethod").body(CodeBlock.of("")); method.annotations().add("TestAnnotation1", ClassName.of("com.example.TestMethodAnnotation")); method.annotations().add("TestAnnotation2", ClassName.of("com.example.TestMethodAnnotation")); test.addFunctionDeclaration(method); From 2bfa2e725e92a998bdbfadb87f397e8381e69b60 Mon Sep 17 00:00:00 2001 From: sijun-yang Date: Tue, 15 Jul 2025 22:52:34 +0900 Subject: [PATCH 24/28] Improve AnnotationContainerTests assertion Signed-off-by: sijun-yang --- .../initializr/generator/language/AnnotationContainerTests.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/initializr-generator/src/test/java/io/spring/initializr/generator/language/AnnotationContainerTests.java b/initializr-generator/src/test/java/io/spring/initializr/generator/language/AnnotationContainerTests.java index 282ca734d0..04440a2c97 100644 --- a/initializr-generator/src/test/java/io/spring/initializr/generator/language/AnnotationContainerTests.java +++ b/initializr-generator/src/test/java/io/spring/initializr/generator/language/AnnotationContainerTests.java @@ -117,8 +117,10 @@ void addWithDifferentNamesAddsTwoAnnotations() { container.add("test-2", TEST_CLASS_NAME, (annotation) -> annotation.set("id", 2)); assertThat(container.values()).satisfiesExactlyInAnyOrder( (a) -> assertThat(a.getAttributes()).singleElement() + .satisfies((attribute) -> assertThat(attribute.getName()).isEqualTo("id")) .satisfies((attribute) -> assertThat(attribute.getValues()).containsOnly(1)), (a) -> assertThat(a.getAttributes()).singleElement() + .satisfies((attribute) -> assertThat(attribute.getName()).isEqualTo("id")) .satisfies((attribute) -> assertThat(attribute.getValues()).containsOnly(2))); } From 6a68ce06a552e0993e5759a979b51d05cbdd77df Mon Sep 17 00:00:00 2001 From: sijun-yang Date: Mon, 21 Jul 2025 13:39:18 +0900 Subject: [PATCH 25/28] Separate single and repeatable annotation handling Signed-off-by: sijun-yang --- .../language/AnnotationContainer.java | 202 +++++++++++++----- 1 file changed, 151 insertions(+), 51 deletions(-) diff --git a/initializr-generator/src/main/java/io/spring/initializr/generator/language/AnnotationContainer.java b/initializr-generator/src/main/java/io/spring/initializr/generator/language/AnnotationContainer.java index 4e97149925..51a9de758d 100644 --- a/initializr-generator/src/main/java/io/spring/initializr/generator/language/AnnotationContainer.java +++ b/initializr-generator/src/main/java/io/spring/initializr/generator/language/AnnotationContainer.java @@ -16,6 +16,7 @@ package io.spring.initializr.generator.language; +import java.util.Collection; import java.util.LinkedHashMap; import java.util.Map; import java.util.function.Consumer; @@ -23,22 +24,32 @@ import io.spring.initializr.generator.language.Annotation.Builder; +import org.springframework.util.LinkedMultiValueMap; +import org.springframework.util.MultiValueMap; + /** - * A container for {@linkplain Annotation annotations} defined on an annotated element. + * A container for annotations defined on an annotated element. + *

+ * Supports both single and repeatable annotations. Single annotations can be customized + * even after they have been added. * * @author Stephane Nicoll - * @author Sijun Yang + * @author Moritz Halbritter */ public class AnnotationContainer { - private final Map annotations; + private final Map singleAnnotations; + + private final MultiValueMap repeatableAnnotations; public AnnotationContainer() { - this(new LinkedHashMap<>()); + this(new LinkedHashMap<>(), new LinkedMultiValueMap<>()); } - private AnnotationContainer(Map annotations) { - this.annotations = annotations; + private AnnotationContainer(Map singleAnnotations, + MultiValueMap repeatableAnnotations) { + this.singleAnnotations = singleAnnotations; + this.repeatableAnnotations = repeatableAnnotations; } /** @@ -46,59 +57,58 @@ private AnnotationContainer(Map annotations) { * @return {@code true} if no annotation is registered */ public boolean isEmpty() { - return this.annotations.isEmpty(); + return this.singleAnnotations.isEmpty() && this.repeatableAnnotations.isEmpty(); } /** - * Specify if this container has an annotation with the specified {@link ClassName}. + * Specify if this container has an annotation with the specified class name. + * Considers both single and repeatable annotations. * @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.getCanonicalName()); + return this.singleAnnotations.containsKey(className) || this.repeatableAnnotations.containsKey(className); } /** - * Specify if this instance contains an annotation with the specified name. - * @param name the name of the annotation - * @return {@code true} if an annotation with the specified name is present, otherwise - * {@code false} + * Whether this container has a single annotation with the specified class name. + * @param className the class name of an annotation + * @return whether this container has the annotation */ - public boolean has(String name) { - return this.annotations.containsKey(name); + public boolean hasSingle(ClassName className) { + return this.singleAnnotations.containsKey(className); } /** - * Return the {@link Annotation annotations}. - * @return the annotations + * Whether this container has repeatable annotations with the specified class name. + * @param className the class name of an annotation + * @return whether this container has the annotation */ - public Stream values() { - return this.annotations.values().stream().map(Builder::build); + public boolean hasRepeatable(ClassName className) { + return this.repeatableAnnotations.containsKey(className); } /** - * Add an {@link Annotation} with the specific name and {@link Consumer} to customize - * it. If an annotation with that name already exists, the consumer can be used to - * further tune its attributes. - * @param name the name of the annotation - * @param className the class name of the annotation - * @param annotation a {@link Consumer} to further configure the annotation + * Return the annotations. Returns both single and repeatable annotations. + * @return the annotations */ - public void add(String name, ClassName className, Consumer annotation) { - Builder builder = this.annotations.computeIfAbsent(name, (key) -> new Builder(className)); - if (annotation != null) { - annotation.accept(builder); - } + public Stream values() { + return Stream + .concat(this.singleAnnotations.values().stream(), + this.repeatableAnnotations.values().stream().flatMap(Collection::stream)) + .map(Builder::build); } /** - * Add an {@link Annotation} with the specific name. Does nothing If an annotation - * with that name already exists. - * @param name the name of the annotation - * @param className the class name of the annotation + * Add a single annotation with the specified class name. Does nothing If the + * annotation has already been added. + * @param className the class name of an annotation + * @deprecated in favor of {@link #addSingle(ClassName)} and + * {@link #addRepeatable(ClassName)} */ - public void add(String name, ClassName className) { - add(name, className, null); + @Deprecated(forRemoval = true) + public void add(ClassName className) { + add(className, null); } /** @@ -107,42 +117,132 @@ public void add(String name, ClassName className) { * to further tune attributes. * @param className the class name of an annotation * @param annotation a {@link Consumer} to customize the {@link Annotation} + * @deprecated in favor of {@link #addSingle(ClassName, Consumer)} and + * {@link #addRepeatable(ClassName)} */ + @Deprecated(forRemoval = true) public void add(ClassName className, Consumer annotation) { - add(className.getCanonicalName(), className, annotation); + if (hasRepeatable(className)) { + throw new IllegalArgumentException( + "%s has already been used for repeatable annotations".formatted(className)); + } + Builder builder = this.singleAnnotations.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. + * Add a single annotation. * @param className the class name of an annotation + * @return whether the annotation has been added + * @throws IllegalStateException if the annotation has already been used for + * repeatable annotations */ - public void add(ClassName className) { - add(className.getCanonicalName(), className, null); + public boolean addSingle(ClassName className) { + return addSingle(className, null); + } + + /** + * Add a single annotation with the specified class name. If the annotation already + * exists, this method does nothing. + * @param className the class name of an annotation + * @param annotation a {@link Consumer} to customize the annotation + * @return whether the annotation has been added + * @throws IllegalStateException if the annotation has already been used for + * repeatable annotations + */ + public boolean addSingle(ClassName className, Consumer annotation) { + if (hasSingle(className)) { + return false; + } + if (hasRepeatable(className)) { + throw new IllegalStateException("%s has already been used for repeatable annotations".formatted(className)); + } + Builder builder = new Builder(className); + if (annotation != null) { + annotation.accept(builder); + } + this.singleAnnotations.put(className, builder); + return true; } /** - * Remove the annotation with the specified {@link ClassName}. + * Customize a single annotation if it exists. This method does nothing if the + * annotation doesn't exist. + * @param className the class name of an annotation + * @param customizer the customizer for the annotation + */ + public void customizeSingle(ClassName className, Consumer customizer) { + Builder builder = this.singleAnnotations.get(className); + if (builder != null) { + customizer.accept(builder); + } + } + + /** + * Add a repeatable annotation. + * @param className the class name of an annotation + * @throws IllegalStateException if the annotation has already been added as a single + * annotation + */ + public void addRepeatable(ClassName className) { + addRepeatable(className, null); + } + + /** + * Add a repeatable annotation. + * @param className the class name of an annotation + * @param annotation a {@link Consumer} to customize the annotation + * @throws IllegalStateException if the annotation has already been added as a single + * annotation + */ + public void addRepeatable(ClassName className, Consumer annotation) { + if (hasSingle(className)) { + throw new IllegalStateException("%s has already been added as a single annotation".formatted(className)); + } + Builder builder = new Builder(className); + if (annotation != null) { + annotation.accept(builder); + } + this.repeatableAnnotations.add(className, builder); + } + + /** + * Remove the annotation with the specified classname from either the single or the + * repeatable annotations. * @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.getCanonicalName()) != null; + return this.singleAnnotations.remove(className) != null || this.repeatableAnnotations.remove(className) != null; } /** - * Remove the annotation with the specified name. - * @param name the name of the annotation to remove - * @return {@code true} if the annotation was removed, {@code false} otherwise + * Remove a single with the specified classname. + * @param className the class name of an annotation + * @return whether the annotation has been removed + */ + public boolean removeSingle(ClassName className) { + return this.singleAnnotations.remove(className) != null; + } + + /** + * Remove all repeatable annotations with the specified classname. + * @param className the class name of an annotation + * @return whether any annotation has been removed */ - public boolean remove(String name) { - return this.annotations.remove(name) != null; + public boolean removeAllRepeatable(ClassName className) { + return this.repeatableAnnotations.remove(className) != null; } public AnnotationContainer deepCopy() { - Map copy = new LinkedHashMap<>(); - this.annotations.forEach((name, builder) -> copy.put(name, new Builder(builder))); - return new AnnotationContainer(copy); + Map singleAnnotations = new LinkedHashMap<>(); + this.singleAnnotations.forEach((className, builder) -> singleAnnotations.put(className, new Builder(builder))); + MultiValueMap repeatableAnnotations = new LinkedMultiValueMap<>(); + this.repeatableAnnotations.forEach((className, builders) -> builders + .forEach((builder) -> repeatableAnnotations.add(className, new Builder(builder)))); + return new AnnotationContainer(singleAnnotations, repeatableAnnotations); } } From 59744724af6cd6a5ea0b6b3d2e0aea65c0ebd979 Mon Sep 17 00:00:00 2001 From: sijun-yang Date: Mon, 21 Jul 2025 13:55:50 +0900 Subject: [PATCH 26/28] Deprecate AnnotationContainer#add Signed-off-by: sijun-yang --- ...rceCodeProjectGenerationConfiguration.java | 4 +- ...ationDefaultContributorsConfiguration.java | 4 +- ...ationDefaultContributorsConfiguration.java | 4 +- ...ationDefaultContributorsConfiguration.java | 2 +- .../generator/language/Parameter.java | 49 +++++++++++++++++++ .../kotlin/KotlinPropertyDeclaration.java | 2 +- 6 files changed, 57 insertions(+), 8 deletions(-) diff --git a/initializr-generator-spring/src/main/java/io/spring/initializr/generator/spring/code/SourceCodeProjectGenerationConfiguration.java b/initializr-generator-spring/src/main/java/io/spring/initializr/generator/spring/code/SourceCodeProjectGenerationConfiguration.java index 5da16990f3..2a84b3acf1 100644 --- a/initializr-generator-spring/src/main/java/io/spring/initializr/generator/spring/code/SourceCodeProjectGenerationConfiguration.java +++ b/initializr-generator-spring/src/main/java/io/spring/initializr/generator/spring/code/SourceCodeProjectGenerationConfiguration.java @@ -39,13 +39,13 @@ public class SourceCodeProjectGenerationConfiguration { @Bean public MainApplicationTypeCustomizer springBootApplicationAnnotator() { return (typeDeclaration) -> typeDeclaration.annotations() - .add(ClassName.of("org.springframework.boot.autoconfigure.SpringBootApplication")); + .addSingle(ClassName.of("org.springframework.boot.autoconfigure.SpringBootApplication")); } @Bean public TestApplicationTypeCustomizer junitJupiterSpringBootTestTypeCustomizer() { return (typeDeclaration) -> typeDeclaration.annotations() - .add(ClassName.of("org.springframework.boot.test.context.SpringBootTest")); + .addSingle(ClassName.of("org.springframework.boot.test.context.SpringBootTest")); } /** diff --git a/initializr-generator-spring/src/main/java/io/spring/initializr/generator/spring/code/groovy/GroovyProjectGenerationDefaultContributorsConfiguration.java b/initializr-generator-spring/src/main/java/io/spring/initializr/generator/spring/code/groovy/GroovyProjectGenerationDefaultContributorsConfiguration.java index eecbb3bd5c..660d649ddf 100644 --- a/initializr-generator-spring/src/main/java/io/spring/initializr/generator/spring/code/groovy/GroovyProjectGenerationDefaultContributorsConfiguration.java +++ b/initializr-generator-spring/src/main/java/io/spring/initializr/generator/spring/code/groovy/GroovyProjectGenerationDefaultContributorsConfiguration.java @@ -66,7 +66,7 @@ TestApplicationTypeCustomizer junitJupiterTestMethodContr GroovyMethodDeclaration method = GroovyMethodDeclaration.method("contextLoads") .returning("void") .body(CodeBlock.of("")); - method.annotations().add(ClassName.of("org.junit.jupiter.api.Test")); + method.annotations().addSingle(ClassName.of("org.junit.jupiter.api.Test")); typeDeclaration.addMethodDeclaration(method); }; } @@ -93,7 +93,7 @@ ServletInitializerCustomizer javaServletInitializerCustom .parameters( Parameter.of("application", "org.springframework.boot.builder.SpringApplicationBuilder")) .body(CodeBlock.ofStatement("application.sources($L)", description.getApplicationName())); - configure.annotations().add(ClassName.of(Override.class)); + configure.annotations().addSingle(ClassName.of(Override.class)); typeDeclaration.addMethodDeclaration(configure); }; } diff --git a/initializr-generator-spring/src/main/java/io/spring/initializr/generator/spring/code/java/JavaProjectGenerationDefaultContributorsConfiguration.java b/initializr-generator-spring/src/main/java/io/spring/initializr/generator/spring/code/java/JavaProjectGenerationDefaultContributorsConfiguration.java index e09332ffdb..71685307e4 100644 --- a/initializr-generator-spring/src/main/java/io/spring/initializr/generator/spring/code/java/JavaProjectGenerationDefaultContributorsConfiguration.java +++ b/initializr-generator-spring/src/main/java/io/spring/initializr/generator/spring/code/java/JavaProjectGenerationDefaultContributorsConfiguration.java @@ -61,7 +61,7 @@ TestApplicationTypeCustomizer junitJupiterTestMethodContrib JavaMethodDeclaration method = JavaMethodDeclaration.method("contextLoads") .returning("void") .body(CodeBlock.of("")); - method.annotations().add(ClassName.of("org.junit.jupiter.api.Test")); + method.annotations().addSingle(ClassName.of("org.junit.jupiter.api.Test")); typeDeclaration.addMethodDeclaration(method); }; } @@ -85,7 +85,7 @@ ServletInitializerCustomizer javaServletInitializerCustomiz Parameter.of("application", "org.springframework.boot.builder.SpringApplicationBuilder")) .body(CodeBlock.ofStatement("return application.sources($L.class)", description.getApplicationName())); - configure.annotations().add(ClassName.of(Override.class)); + configure.annotations().addSingle(ClassName.of(Override.class)); typeDeclaration.addMethodDeclaration(configure); }; } diff --git a/initializr-generator-spring/src/main/java/io/spring/initializr/generator/spring/code/kotlin/KotlinProjectGenerationDefaultContributorsConfiguration.java b/initializr-generator-spring/src/main/java/io/spring/initializr/generator/spring/code/kotlin/KotlinProjectGenerationDefaultContributorsConfiguration.java index dd8dc57926..27fdc33e5e 100644 --- a/initializr-generator-spring/src/main/java/io/spring/initializr/generator/spring/code/kotlin/KotlinProjectGenerationDefaultContributorsConfiguration.java +++ b/initializr-generator-spring/src/main/java/io/spring/initializr/generator/spring/code/kotlin/KotlinProjectGenerationDefaultContributorsConfiguration.java @@ -57,7 +57,7 @@ TestApplicationTypeCustomizer junitJupiterTestMethodContr return (typeDeclaration) -> { KotlinFunctionDeclaration function = KotlinFunctionDeclaration.function("contextLoads") .body(CodeBlock.of("")); - function.annotations().add(ClassName.of("org.junit.jupiter.api.Test")); + function.annotations().addSingle(ClassName.of("org.junit.jupiter.api.Test")); typeDeclaration.addFunctionDeclaration(function); }; } diff --git a/initializr-generator/src/main/java/io/spring/initializr/generator/language/Parameter.java b/initializr-generator/src/main/java/io/spring/initializr/generator/language/Parameter.java index af1acbfcd1..e333e95203 100644 --- a/initializr-generator/src/main/java/io/spring/initializr/generator/language/Parameter.java +++ b/initializr-generator/src/main/java/io/spring/initializr/generator/language/Parameter.java @@ -145,7 +145,10 @@ public Builder type(String type) { * Annotate the parameter with the specified annotation. * @param className the class of the annotation * @return this for method chaining + * @deprecated in favor of {@link #singleAnnotate(ClassName)} and + * {@link #repeatableAnnotate(ClassName)} */ + @Deprecated(forRemoval = true) public Builder annotate(ClassName className) { return annotate(className, null); } @@ -156,12 +159,58 @@ public Builder annotate(ClassName className) { * @param className the class of the annotation * @param annotation a consumer of the builder * @return this for method chaining + * @deprecated in favor of {@link #singleAnnotate(ClassName, Consumer)} and + * {@link #singleAnnotate(ClassName)} */ + @Deprecated(forRemoval = true) + @SuppressWarnings("removal") public Builder annotate(ClassName className, Consumer annotation) { this.annotations.add(className, annotation); return this; } + /** + * Annotate the parameter with the specified single annotation. + * @param className the class of the annotation + * @return this for method chaining + */ + public Builder singleAnnotate(ClassName className) { + return singleAnnotate(className, null); + } + + /** + * Annotate the parameter with the specified single annotation, customized by the + * specified consumer. + * @param className the class of the annotation + * @param annotation a consumer of the builder + * @return this for method chaining + */ + public Builder singleAnnotate(ClassName className, Consumer annotation) { + this.annotations.addSingle(className, annotation); + return this; + } + + /** + * Annotate the parameter with the specified repeatable annotation. + * @param className the class of the annotation + * @return this for method chaining + */ + public Builder repeatableAnnotate(ClassName className) { + return repeatableAnnotate(className, null); + } + + /** + * Annotate the parameter with the specified repeatable annotation, customized by + * the specified consumer. + * @param className the class of the annotation + * @param annotation a consumer of the builder + * @return this for method chaining + */ + public Builder repeatableAnnotate(ClassName className, Consumer annotation) { + this.annotations.addRepeatable(className, annotation); + return this; + } + public Parameter build() { return new Parameter(this); } diff --git a/initializr-generator/src/main/java/io/spring/initializr/generator/language/kotlin/KotlinPropertyDeclaration.java b/initializr-generator/src/main/java/io/spring/initializr/generator/language/kotlin/KotlinPropertyDeclaration.java index 3e2a364fbc..4f9610e029 100644 --- a/initializr-generator/src/main/java/io/spring/initializr/generator/language/kotlin/KotlinPropertyDeclaration.java +++ b/initializr-generator/src/main/java/io/spring/initializr/generator/language/kotlin/KotlinPropertyDeclaration.java @@ -281,7 +281,7 @@ public AccessorBuilder withAnnotation(ClassName className) { * @return this for method chaining */ public AccessorBuilder withAnnotation(ClassName className, Consumer annotation) { - this.annotations.add(className, annotation); + this.annotations.addSingle(className, annotation); return this; } From 3435d7f10edd4d4789669761f15a678f3e7e47b9 Mon Sep 17 00:00:00 2001 From: sijun-yang Date: Mon, 21 Jul 2025 13:56:18 +0900 Subject: [PATCH 27/28] Add tests Signed-off-by: sijun-yang --- .../language/AnnotationContainerTests.java | 313 ++++++++++++++++-- .../groovy/GroovySourceCodeWriterTests.java | 45 ++- .../java/JavaSourceCodeWriterTests.java | 47 ++- .../kotlin/KotlinSourceCodeWriterTests.java | 43 ++- 4 files changed, 352 insertions(+), 96 deletions(-) diff --git a/initializr-generator/src/test/java/io/spring/initializr/generator/language/AnnotationContainerTests.java b/initializr-generator/src/test/java/io/spring/initializr/generator/language/AnnotationContainerTests.java index 04440a2c97..144af01f8f 100644 --- a/initializr-generator/src/test/java/io/spring/initializr/generator/language/AnnotationContainerTests.java +++ b/initializr-generator/src/test/java/io/spring/initializr/generator/language/AnnotationContainerTests.java @@ -19,11 +19,13 @@ import org.junit.jupiter.api.Test; import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatIllegalStateException; /** * Tests for {@link AnnotationContainer}. * * @author Stephane Nicoll + * @author Sijun Yang */ class AnnotationContainerTests { @@ -31,6 +33,8 @@ class AnnotationContainerTests { private static final ClassName NESTED_CLASS_NAME = ClassName.of("com.example.Nested"); + private static final ClassName ANOTHER_CLASS_NAME = ClassName.of("com.example.Another"); + @Test void isEmptyWithEmptyContainer() { AnnotationContainer container = new AnnotationContainer(); @@ -38,6 +42,7 @@ void isEmptyWithEmptyContainer() { } @Test + @SuppressWarnings("removal") void isEmptyWithAnnotation() { AnnotationContainer container = new AnnotationContainer(); container.add(TEST_CLASS_NAME, (annotation) -> annotation.add("value", "test")); @@ -45,6 +50,21 @@ void isEmptyWithAnnotation() { } @Test + void isEmptyWithSingleAnnotation() { + AnnotationContainer container = new AnnotationContainer(); + container.addSingle(TEST_CLASS_NAME); + assertThat(container.isEmpty()).isFalse(); + } + + @Test + void isEmptyWithRepeatableAnnotation() { + AnnotationContainer container = new AnnotationContainer(); + container.addRepeatable(TEST_CLASS_NAME); + assertThat(container.isEmpty()).isFalse(); + } + + @Test + @SuppressWarnings("removal") void hasWithMatchingAnnotation() { AnnotationContainer container = new AnnotationContainer(); container.add(TEST_CLASS_NAME, (annotation) -> annotation.add("value", "test")); @@ -52,6 +72,7 @@ void hasWithMatchingAnnotation() { } @Test + @SuppressWarnings("removal") void hasWithNonMatchingAnnotation() { AnnotationContainer container = new AnnotationContainer(); container.add(TEST_CLASS_NAME, (annotation) -> annotation.add("value", "test")); @@ -59,6 +80,28 @@ void hasWithNonMatchingAnnotation() { } @Test + void hasWithMatchingSingleAnnotation() { + AnnotationContainer container = new AnnotationContainer(); + container.addSingle(TEST_CLASS_NAME); + assertThat(container.has(TEST_CLASS_NAME)).isTrue(); + } + + @Test + void hasWithMatchingRepeatableAnnotation() { + AnnotationContainer container = new AnnotationContainer(); + container.addRepeatable(TEST_CLASS_NAME); + assertThat(container.has(TEST_CLASS_NAME)).isTrue(); + } + + @Test + void hasWithNonMatchingSingleAnnotation() { + AnnotationContainer container = new AnnotationContainer(); + container.addSingle(TEST_CLASS_NAME); + assertThat(container.has(ANOTHER_CLASS_NAME)).isFalse(); + } + + @Test + @SuppressWarnings("removal") void valuesWithSimpleAnnotation() { AnnotationContainer container = new AnnotationContainer(); container.add(TEST_CLASS_NAME, (annotation) -> annotation.add("value", "test")); @@ -73,6 +116,36 @@ void valuesWithSimpleAnnotation() { } @Test + void valuesWithSingleAnnotation() { + AnnotationContainer container = new AnnotationContainer(); + container.addSingle(TEST_CLASS_NAME, (builder) -> builder.add("value", "test")); + assertThat(container.values()).singleElement().satisfies((annotation) -> { + assertThat(annotation.getClassName()).isEqualTo(TEST_CLASS_NAME); + assertThat(annotation.getAttributes()).singleElement().satisfies((attribute) -> { + assertThat(attribute.getName()).isEqualTo("value"); + assertThat(attribute.getValues()).containsExactly("test"); + }); + }); + } + + @Test + void valuesWithRepeatableAnnotations() { + AnnotationContainer container = new AnnotationContainer(); + container.addRepeatable(TEST_CLASS_NAME, (builder) -> builder.add("value", "test1")); + container.addRepeatable(TEST_CLASS_NAME, (builder) -> builder.add("value", "test2")); + assertThat(container.values()).hasSize(2); + } + + @Test + void valuesWithMixedAnnotations() { + AnnotationContainer container = new AnnotationContainer(); + container.addSingle(TEST_CLASS_NAME); + container.addRepeatable(ANOTHER_CLASS_NAME); + assertThat(container.values()).hasSize(2); + } + + @Test + @SuppressWarnings("removal") void addAnnotationSeveralTimeReuseConfiguration() { AnnotationContainer container = new AnnotationContainer(); container.add(TEST_CLASS_NAME, (annotation) -> annotation.add("value", "test")); @@ -88,6 +161,7 @@ void addAnnotationSeveralTimeReuseConfiguration() { } @Test + @SuppressWarnings("removal") void addAnnotationSeveralTimeCanReplaceAttribute() { AnnotationContainer container = new AnnotationContainer(); container.add(TEST_CLASS_NAME, @@ -111,57 +185,244 @@ void addAnnotationSeveralTimeCanReplaceAttribute() { } @Test - void addWithDifferentNamesAddsTwoAnnotations() { + @SuppressWarnings("removal") + 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 + @SuppressWarnings("removal") + void removeWithNonMatchingAnnotation() { AnnotationContainer container = new AnnotationContainer(); - container.add("test-1", TEST_CLASS_NAME, (annotation) -> annotation.set("id", 1)); - container.add("test-2", TEST_CLASS_NAME, (annotation) -> annotation.set("id", 2)); - assertThat(container.values()).satisfiesExactlyInAnyOrder( - (a) -> assertThat(a.getAttributes()).singleElement() - .satisfies((attribute) -> assertThat(attribute.getName()).isEqualTo("id")) - .satisfies((attribute) -> assertThat(attribute.getValues()).containsOnly(1)), - (a) -> assertThat(a.getAttributes()).singleElement() - .satisfies((attribute) -> assertThat(attribute.getName()).isEqualTo("id")) - .satisfies((attribute) -> assertThat(attribute.getValues()).containsOnly(2))); + container.add(TEST_CLASS_NAME, (annotation) -> annotation.add("value", "test")); + assertThat(container.remove(ClassName.of("com.example.Another"))).isFalse(); + assertThat(container.isEmpty()).isFalse(); } @Test - void hasWithName() { + void removeWithSingleAnnotation() { AnnotationContainer container = new AnnotationContainer(); - container.add("test", TEST_CLASS_NAME); - assertThat(container.has("test")).isTrue(); - assertThat(container.has("test-no")).isFalse(); + container.addSingle(TEST_CLASS_NAME); + assertThat(container.remove(TEST_CLASS_NAME)).isTrue(); + assertThat(container.isEmpty()).isTrue(); } @Test - void removeWithNameRemovesMatchingAnnotation() { + void removeWithRepeatableAnnotation() { AnnotationContainer container = new AnnotationContainer(); - container.add("test", TEST_CLASS_NAME); - assertThat(container.remove("test")).isTrue(); + container.addRepeatable(TEST_CLASS_NAME); + assertThat(container.remove(TEST_CLASS_NAME)).isTrue(); assertThat(container.isEmpty()).isTrue(); } @Test - void removeWithNameWithNonMatchingAnnotation() { + void removeWithNonMatchingSingleAnnotation() { AnnotationContainer container = new AnnotationContainer(); - container.add("test", TEST_CLASS_NAME); - assertThat(container.remove("test-no")).isFalse(); + container.addSingle(TEST_CLASS_NAME); + assertThat(container.remove(ANOTHER_CLASS_NAME)).isFalse(); assertThat(container.isEmpty()).isFalse(); } @Test - void removeWithMatchingAnnotation() { + void removeWithNonMatchingRepeatableAnnotation() { AnnotationContainer container = new AnnotationContainer(); - container.add(TEST_CLASS_NAME, (annotation) -> annotation.add("value", "test")); - assertThat(container.remove(TEST_CLASS_NAME)).isTrue(); + container.addRepeatable(TEST_CLASS_NAME); + assertThat(container.remove(ANOTHER_CLASS_NAME)).isFalse(); + assertThat(container.isEmpty()).isFalse(); + } + + @Test + void hasSingleWithSingleAnnotation() { + AnnotationContainer container = new AnnotationContainer(); + container.addSingle(TEST_CLASS_NAME); + assertThat(container.hasSingle(TEST_CLASS_NAME)).isTrue(); + assertThat(container.hasSingle(ANOTHER_CLASS_NAME)).isFalse(); + } + + @Test + void hasSingleWithRepeatableAnnotation() { + AnnotationContainer container = new AnnotationContainer(); + container.addRepeatable(TEST_CLASS_NAME); + assertThat(container.hasSingle(TEST_CLASS_NAME)).isFalse(); + } + + @Test + void addSingle() { + AnnotationContainer container = new AnnotationContainer(); + assertThat(container.addSingle(TEST_CLASS_NAME)).isTrue(); + assertThat(container.hasSingle(TEST_CLASS_NAME)).isTrue(); + } + + @Test + void addSingleTwiceReturnsFalse() { + AnnotationContainer container = new AnnotationContainer(); + container.addSingle(TEST_CLASS_NAME); + assertThat(container.addSingle(TEST_CLASS_NAME)).isFalse(); + } + + @Test + void addSingleWithBuilder() { + AnnotationContainer container = new AnnotationContainer(); + container.addSingle(TEST_CLASS_NAME, (builder) -> builder.add("value", 123)); + assertThat(container.values()).singleElement().satisfies((annotation) -> { + assertThat(annotation.getClassName()).isEqualTo(TEST_CLASS_NAME); + assertThat(annotation.getAttributes()).singleElement().satisfies((attribute) -> { + assertThat(attribute.getName()).isEqualTo("value"); + assertThat(attribute.getValues()).containsExactly(123); + }); + }); + } + + @Test + void customizeSingle() { + AnnotationContainer container = new AnnotationContainer(); + container.addSingle(TEST_CLASS_NAME, (builder) -> builder.add("value", "test")); + container.customizeSingle(TEST_CLASS_NAME, (builder) -> builder.add("value", "another")); + assertThat(container.values()).singleElement() + .satisfies((annotation) -> assertThat(annotation.getAttributes().get(0).getValues()).containsExactly("test", + "another")); + } + + @Test + void customizeSingleCanReplaceAttribute() { + AnnotationContainer container = new AnnotationContainer(); + container.addSingle(TEST_CLASS_NAME, + (builder) -> builder.add("value", Annotation.of(NESTED_CLASS_NAME).add("counter", 42).build())); + container.customizeSingle(TEST_CLASS_NAME, + (builder) -> builder.set("value", Annotation.of(NESTED_CLASS_NAME).add("counter", 24).build())); + assertThat(container.values()).singleElement().satisfies((annotation) -> { + 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 customizeSingleOnNonMatchingAnnotationDoesNothing() { + AnnotationContainer container = new AnnotationContainer(); + container.addSingle(NESTED_CLASS_NAME); + container.customizeSingle(ANOTHER_CLASS_NAME, (builder) -> builder.add("value", "test")); + assertThat(container.values()).singleElement() + .satisfies((annotation) -> assertThat(annotation.getAttributes()).isEmpty()); + } + + @Test + void removeSingle() { + AnnotationContainer container = new AnnotationContainer(); + container.addSingle(TEST_CLASS_NAME); + assertThat(container.removeSingle(TEST_CLASS_NAME)).isTrue(); assertThat(container.isEmpty()).isTrue(); } @Test - void removeWithNonMatchingAnnotation() { + void removeSingleWithNonMatchingAnnotation() { AnnotationContainer container = new AnnotationContainer(); - container.add(TEST_CLASS_NAME, (annotation) -> annotation.add("value", "test")); - assertThat(container.remove(ClassName.of("com.example.Another"))).isFalse(); + container.addSingle(TEST_CLASS_NAME); + assertThat(container.removeSingle(ANOTHER_CLASS_NAME)).isFalse(); + assertThat(container.isEmpty()).isFalse(); + } + + @Test + void hasRepeatableWithSingleAnnotation() { + AnnotationContainer container = new AnnotationContainer(); + container.addSingle(TEST_CLASS_NAME); + assertThat(container.hasRepeatable(TEST_CLASS_NAME)).isFalse(); + } + + @Test + void hasRepeatableWithRepeatableAnnotation() { + AnnotationContainer container = new AnnotationContainer(); + container.addRepeatable(TEST_CLASS_NAME); + assertThat(container.hasRepeatable(TEST_CLASS_NAME)).isTrue(); + assertThat(container.hasRepeatable(ANOTHER_CLASS_NAME)).isFalse(); + } + + @Test + void addRepeatable() { + AnnotationContainer container = new AnnotationContainer(); + container.addRepeatable(TEST_CLASS_NAME); + assertThat(container.hasRepeatable(TEST_CLASS_NAME)).isTrue(); + assertThat(container.values()).hasSize(1); + } + + @Test + void addRepeatableTwice() { + AnnotationContainer container = new AnnotationContainer(); + container.addRepeatable(TEST_CLASS_NAME); + container.addRepeatable(TEST_CLASS_NAME); + assertThat(container.hasRepeatable(TEST_CLASS_NAME)).isTrue(); + assertThat(container.values()).hasSize(2); + } + + @Test + void addRepeatableWithBuilder() { + AnnotationContainer container = new AnnotationContainer(); + container.addRepeatable(TEST_CLASS_NAME, (builder) -> builder.add("value", "test1")); + container.addRepeatable(TEST_CLASS_NAME, (builder) -> builder.add("value", "test2")); + assertThat(container.values()).satisfiesExactly( + (annotation) -> assertThat(annotation.getAttributes().get(0).getValues()).containsExactly("test1"), + (annotation) -> assertThat(annotation.getAttributes().get(0).getValues()).containsExactly("test2")); + } + + @Test + void removeAllRepeatable() { + AnnotationContainer container = new AnnotationContainer(); + container.addRepeatable(TEST_CLASS_NAME); + container.addRepeatable(TEST_CLASS_NAME); + assertThat(container.removeAllRepeatable(TEST_CLASS_NAME)).isTrue(); + assertThat(container.isEmpty()).isTrue(); + } + + @Test + void removeAllRepeatableWithNonMatchingAnnotation() { + AnnotationContainer container = new AnnotationContainer(); + container.addRepeatable(TEST_CLASS_NAME); + assertThat(container.removeAllRepeatable(ANOTHER_CLASS_NAME)).isFalse(); assertThat(container.isEmpty()).isFalse(); } + @Test + void addSingleWhenAlreadyRepeatable() { + AnnotationContainer container = new AnnotationContainer(); + container.addRepeatable(TEST_CLASS_NAME); + assertThatIllegalStateException() + .isThrownBy(() -> container.addSingle(TEST_CLASS_NAME, (builder) -> builder.add("value", "test"))) + .withMessageContaining("has already been used for repeatable annotations"); + } + + @Test + void addRepeatableWhenAlreadySingle() { + AnnotationContainer container = new AnnotationContainer(); + container.addSingle(TEST_CLASS_NAME); + assertThatIllegalStateException() + .isThrownBy(() -> container.addRepeatable(TEST_CLASS_NAME, (builder) -> builder.add("value", "test"))) + .withMessageContaining("has already been added as a single annotation"); + } + + @Test + void deepCopy() { + AnnotationContainer original = new AnnotationContainer(); + original.addSingle(TEST_CLASS_NAME, (builder) -> builder.add("value", "test")); + original.addRepeatable(ANOTHER_CLASS_NAME, (builder) -> builder.add("value", "test")); + AnnotationContainer copy = original.deepCopy(); + assertThat(copy).isNotSameAs(original); + assertThat(copy.values()).hasSize(2); + original.customizeSingle(TEST_CLASS_NAME, (builder) -> builder.add("value", "another")); + original.addRepeatable(ANOTHER_CLASS_NAME); + assertThat(copy.values()).hasSize(2); + assertThat(copy.values()).satisfiesExactly( + (annotation) -> assertThat(annotation.getAttributes().get(0).getValues()).containsExactly("test"), + (annotation) -> assertThat(annotation.getAttributes().get(0).getValues()).containsExactly("test")); + } + } diff --git a/initializr-generator/src/test/java/io/spring/initializr/generator/language/groovy/GroovySourceCodeWriterTests.java b/initializr-generator/src/test/java/io/spring/initializr/generator/language/groovy/GroovySourceCodeWriterTests.java index 6f4ee0c40f..6476e2830b 100644 --- a/initializr-generator/src/test/java/io/spring/initializr/generator/language/groovy/GroovySourceCodeWriterTests.java +++ b/initializr-generator/src/test/java/io/spring/initializr/generator/language/groovy/GroovySourceCodeWriterTests.java @@ -167,7 +167,7 @@ void springBootApplication() throws IOException { GroovySourceCode sourceCode = new GroovySourceCode(); GroovyCompilationUnit compilationUnit = sourceCode.createCompilationUnit("com.example", "Test"); GroovyTypeDeclaration test = compilationUnit.createTypeDeclaration("Test"); - test.annotations().add(ClassName.of("org.springframework.boot.autoconfigure.SpringBootApplication")); + test.annotations().addSingle(ClassName.of("org.springframework.boot.autoconfigure.SpringBootApplication")); test.addMethodDeclaration(GroovyMethodDeclaration.method("main") .modifiers(Modifier.PUBLIC | Modifier.STATIC) .returning("void") @@ -244,7 +244,7 @@ void fieldAnnotation() throws IOException { GroovyCompilationUnit compilationUnit = sourceCode.createCompilationUnit("com.example", "Test"); GroovyTypeDeclaration test = compilationUnit.createTypeDeclaration("Test"); GroovyFieldDeclaration field = GroovyFieldDeclaration.field("testString").returning("java.lang.String"); - field.annotations().add(ClassName.of("org.springframework.beans.factory.annotation.Autowired")); + field.annotations().addSingle(ClassName.of("org.springframework.beans.factory.annotation.Autowired")); test.addFieldDeclaration(field); List lines = writeSingleType(sourceCode, "com/example/Test.groovy"); assertThat(lines).containsExactly("package com.example", "", @@ -283,7 +283,7 @@ private List writeClassAnnotation(String annotationClassName, Consumer lines = writeSingleType(sourceCode, "com/example/Test.groovy"); assertThat(lines).containsExactly("package com.example", "", "import com.example.test.TestAnnotation", "", @@ -312,7 +312,7 @@ void methodWithParameterAnnotation() throws IOException { .returning("void") .parameters(Parameter.builder("service") .type(ClassName.of("com.example.another.MyService")) - .annotate(ClassName.of("com.example.stereotype.Service")) + .singleAnnotate(ClassName.of("com.example.stereotype.Service")) .build()) .body(CodeBlock.of(""))); List lines = writeSingleType(sourceCode, "com/example/Test.groovy"); @@ -322,46 +322,45 @@ void methodWithParameterAnnotation() throws IOException { } @Test - void repeatableAnnotationsOnClass() throws IOException { + void repeatableClassAnnotations() throws IOException { GroovySourceCode sourceCode = new GroovySourceCode(); GroovyCompilationUnit compilationUnit = sourceCode.createCompilationUnit("com.example", "Test"); GroovyTypeDeclaration test = compilationUnit.createTypeDeclaration("Test"); - test.annotations().add("TestAnnotation1", ClassName.of("com.example.TestClassAnnotation")); - test.annotations().add("TestAnnotation2", ClassName.of("com.example.TestClassAnnotation")); + test.annotations().addRepeatable(ClassName.of("com.example.Repeatable")); + test.annotations().addRepeatable(ClassName.of("com.example.Repeatable")); List lines = writeSingleType(sourceCode, "com/example/Test.groovy"); - assertThat(lines).containsExactly("package com.example", "", "@TestClassAnnotation", "@TestClassAnnotation", - "class Test {", "", "}"); + assertThat(lines).containsExactly("package com.example", "", "@Repeatable", "@Repeatable", "class Test {", "", + "}"); } @Test - void repeatableAnnotationsOnField() throws IOException { + void repeatableFieldAnnotations() throws IOException { GroovySourceCode sourceCode = new GroovySourceCode(); GroovyCompilationUnit compilationUnit = sourceCode.createCompilationUnit("com.example", "Test"); GroovyTypeDeclaration test = compilationUnit.createTypeDeclaration("Test"); - GroovyFieldDeclaration field = GroovyFieldDeclaration.field("testField").returning("java.lang.String"); - field.annotations().add("TestAnnotation1", ClassName.of("com.example.TestFiledAnnotation")); - field.annotations().add("TestAnnotation2", ClassName.of("com.example.TestFiledAnnotation")); + GroovyFieldDeclaration field = GroovyFieldDeclaration.field("myField").returning("java.lang.String"); + field.annotations().addRepeatable(ClassName.of("com.example.Repeatable")); + field.annotations().addRepeatable(ClassName.of("com.example.Repeatable")); test.addFieldDeclaration(field); List lines = writeSingleType(sourceCode, "com/example/Test.groovy"); - assertThat(lines).containsExactly("package com.example", "", "class Test {", "", " @TestFiledAnnotation", - " @TestFiledAnnotation", " String testField", "", "}"); + assertThat(lines).containsExactly("package com.example", "", "class Test {", "", " @Repeatable", + " @Repeatable", " String myField", "", "}"); } @Test - void repeatableAnnotationsOnMethod() throws IOException { + void repeatableMethodAnnotations() throws IOException { GroovySourceCode sourceCode = new GroovySourceCode(); GroovyCompilationUnit compilationUnit = sourceCode.createCompilationUnit("com.example", "Test"); GroovyTypeDeclaration test = compilationUnit.createTypeDeclaration("Test"); - GroovyMethodDeclaration method = GroovyMethodDeclaration.method("testMethod") + GroovyMethodDeclaration method = GroovyMethodDeclaration.method("myMethod") .returning("void") - .parameters() .body(CodeBlock.of("")); - method.annotations().add("TestAnnotation1", ClassName.of("com.example.TestMethodAnnotation")); - method.annotations().add("TestAnnotation2", ClassName.of("com.example.TestMethodAnnotation")); + method.annotations().addRepeatable(ClassName.of("com.example.Repeatable")); + method.annotations().addRepeatable(ClassName.of("com.example.Repeatable")); test.addMethodDeclaration(method); List lines = writeSingleType(sourceCode, "com/example/Test.groovy"); - assertThat(lines).containsExactly("package com.example", "", "class Test {", "", " @TestMethodAnnotation", - " @TestMethodAnnotation", " void testMethod() {", " }", "", "}"); + assertThat(lines).containsExactly("package com.example", "", "class Test {", "", " " + "@Repeatable", + " @Repeatable", " void myMethod() {", " }", "", "}"); } private List writeSingleType(GroovySourceCode sourceCode, String location) throws IOException { diff --git a/initializr-generator/src/test/java/io/spring/initializr/generator/language/java/JavaSourceCodeWriterTests.java b/initializr-generator/src/test/java/io/spring/initializr/generator/language/java/JavaSourceCodeWriterTests.java index 9ec35402de..6a2e18544c 100644 --- a/initializr-generator/src/test/java/io/spring/initializr/generator/language/java/JavaSourceCodeWriterTests.java +++ b/initializr-generator/src/test/java/io/spring/initializr/generator/language/java/JavaSourceCodeWriterTests.java @@ -182,7 +182,7 @@ void fieldAnnotation() throws IOException { JavaFieldDeclaration field = JavaFieldDeclaration.field("testString") .modifiers(Modifier.PRIVATE) .returning("java.lang.String"); - field.annotations().add(ClassName.of("org.springframework.beans.factory.annotation.Autowired")); + field.annotations().addSingle(ClassName.of("org.springframework.beans.factory.annotation.Autowired")); test.addFieldDeclaration(field); List lines = writeSingleType(sourceCode, "com/example/Test.java"); assertThat(lines).containsExactly("package com.example;", "", @@ -242,7 +242,7 @@ void springBootApplication() throws IOException { JavaSourceCode sourceCode = new JavaSourceCode(); JavaCompilationUnit compilationUnit = sourceCode.createCompilationUnit("com.example", "Test"); JavaTypeDeclaration test = compilationUnit.createTypeDeclaration("Test"); - test.annotations().add(ClassName.of("org.springframework.boot.autoconfigure.SpringBootApplication")); + test.annotations().addSingle(ClassName.of("org.springframework.boot.autoconfigure.SpringBootApplication")); test.addMethodDeclaration(JavaMethodDeclaration.method("main") .modifiers(Modifier.PUBLIC | Modifier.STATIC) .returning("void") @@ -289,7 +289,7 @@ private List writeClassAnnotation(String annotationClassName, Consumer lines = writeSingleType(sourceCode, "com/example/Test.java"); assertThat(lines).containsExactly("package com.example;", "", "import com.example.test.TestAnnotation;", "", @@ -318,7 +318,7 @@ void methodWithParameterAnnotation() throws IOException { .returning("void") .parameters(Parameter.builder("service") .type(ClassName.of("com.example.another.MyService")) - .annotate(ClassName.of("com.example.stereotype.Service")) + .singleAnnotate(ClassName.of("com.example.stereotype.Service")) .build()) .body(CodeBlock.of(""))); List lines = writeSingleType(sourceCode, "com/example/Test.java"); @@ -328,48 +328,45 @@ void methodWithParameterAnnotation() throws IOException { } @Test - void repeatableAnnotationsOnClass() throws IOException { + void repeatableClassAnnotations() throws IOException { JavaSourceCode sourceCode = new JavaSourceCode(); JavaCompilationUnit compilationUnit = sourceCode.createCompilationUnit("com.example", "Test"); JavaTypeDeclaration test = compilationUnit.createTypeDeclaration("Test"); - test.annotations().add("TestAnnotation1", ClassName.of("com.example.TestClassAnnotation")); - test.annotations().add("TestAnnotation2", ClassName.of("com.example.TestClassAnnotation")); + test.annotations().addRepeatable(ClassName.of("com.example.Repeatable")); + test.annotations().addRepeatable(ClassName.of("com.example.Repeatable")); List lines = writeSingleType(sourceCode, "com/example/Test.java"); - assertThat(lines).containsExactly("package com.example;", "", "@TestClassAnnotation", "@TestClassAnnotation", - "class Test {", "", "}"); + assertThat(lines).containsExactly("package com.example;", "", "@Repeatable", "@Repeatable", "class Test {", "", + "}"); } @Test - void repeatableAnnotationsOnField() throws IOException { + void repeatableFieldAnnotations() throws IOException { JavaSourceCode sourceCode = new JavaSourceCode(); JavaCompilationUnit compilationUnit = sourceCode.createCompilationUnit("com.example", "Test"); JavaTypeDeclaration test = compilationUnit.createTypeDeclaration("Test"); - JavaFieldDeclaration field = JavaFieldDeclaration.field("testField") - .modifiers(Modifier.PRIVATE) - .returning("java.lang.String"); - field.annotations().add("TestAnnotation1", ClassName.of("com.example.TestFiledAnnotation")); - field.annotations().add("TestAnnotation2", ClassName.of("com.example.TestFiledAnnotation")); + JavaFieldDeclaration field = JavaFieldDeclaration.field("myField").returning("java.lang.String"); + field.annotations().addRepeatable(ClassName.of("com.example.Repeatable")); + field.annotations().addRepeatable(ClassName.of("com.example.Repeatable")); test.addFieldDeclaration(field); List lines = writeSingleType(sourceCode, "com/example/Test.java"); - assertThat(lines).containsExactly("package com.example;", "", "class Test {", "", " @TestFiledAnnotation", - " @TestFiledAnnotation", " private String testField;", "", "}"); + assertThat(lines).containsExactly("package com.example;", "", "class Test {", "", " @Repeatable", + " @Repeatable", " String myField;", "", "}"); } @Test - void repeatableAnnotationsOnMethod() throws IOException { + void repeatableMethodAnnotations() throws IOException { JavaSourceCode sourceCode = new JavaSourceCode(); JavaCompilationUnit compilationUnit = sourceCode.createCompilationUnit("com.example", "Test"); JavaTypeDeclaration test = compilationUnit.createTypeDeclaration("Test"); - JavaMethodDeclaration method = JavaMethodDeclaration.method("testMethod") + JavaMethodDeclaration method = JavaMethodDeclaration.method("myMethod") .returning("void") - .parameters() .body(CodeBlock.of("")); - method.annotations().add("TestAnnotation1", ClassName.of("com.example.TestMethodAnnotation")); - method.annotations().add("TestAnnotation2", ClassName.of("com.example.TestMethodAnnotation")); + method.annotations().addRepeatable(ClassName.of("com.example.Repeatable")); + method.annotations().addRepeatable(ClassName.of("com.example.Repeatable")); test.addMethodDeclaration(method); List lines = writeSingleType(sourceCode, "com/example/Test.java"); - assertThat(lines).containsExactly("package com.example;", "", "class Test {", "", " @TestMethodAnnotation", - " @TestMethodAnnotation", " void testMethod() {", " }", "", "}"); + assertThat(lines).containsExactly("package com.example;", "", "class Test {", "", " @Repeatable", + " @Repeatable", " void myMethod() {", " }", "", "}"); } private List writeSingleType(JavaSourceCode sourceCode, String location) throws IOException { diff --git a/initializr-generator/src/test/java/io/spring/initializr/generator/language/kotlin/KotlinSourceCodeWriterTests.java b/initializr-generator/src/test/java/io/spring/initializr/generator/language/kotlin/KotlinSourceCodeWriterTests.java index f387c88ad5..d1e3a811a2 100644 --- a/initializr-generator/src/test/java/io/spring/initializr/generator/language/kotlin/KotlinSourceCodeWriterTests.java +++ b/initializr-generator/src/test/java/io/spring/initializr/generator/language/kotlin/KotlinSourceCodeWriterTests.java @@ -310,7 +310,7 @@ void springBootApplication() throws IOException { KotlinSourceCode sourceCode = new KotlinSourceCode(); KotlinCompilationUnit compilationUnit = sourceCode.createCompilationUnit("com.example", "Test"); KotlinTypeDeclaration test = compilationUnit.createTypeDeclaration("Test"); - test.annotations().add(ClassName.of("org.springframework.boot.autoconfigure.SpringBootApplication")); + test.annotations().addSingle(ClassName.of("org.springframework.boot.autoconfigure.SpringBootApplication")); compilationUnit.addTopLevelFunction(KotlinFunctionDeclaration.function("main") .parameters(Parameter.of("args", "Array")) .body(CodeBlock.ofStatement("$T<$L>(*args)", "org.springframework.boot.runApplication", "Test"))); @@ -352,7 +352,7 @@ private List writeClassAnnotation(String annotationClassName, Consumer lines = writeSingleType(sourceCode, "com/example/Test.kt"); assertThat(lines).containsExactly("package com.example", "", "import com.example.test.TestAnnotation", "", @@ -377,7 +377,7 @@ void functionWithParameterAnnotation() throws IOException { test.addFunctionDeclaration(KotlinFunctionDeclaration.function("something") .parameters(Parameter.builder("service") .type(ClassName.of("com.example.another.MyService")) - .annotate(ClassName.of("com.example.stereotype.Service")) + .singleAnnotate(ClassName.of("com.example.stereotype.Service")) .build()) .body(CodeBlock.of(""))); List lines = writeSingleType(sourceCode, "com/example/Test.kt"); @@ -435,45 +435,44 @@ void reservedJavaKeywordsEndPackageName() throws IOException { } @Test - void repeatableAnnotationsOnClass() throws IOException { + void repeatableClassAnnotations() throws IOException { KotlinSourceCode sourceCode = new KotlinSourceCode(); KotlinCompilationUnit compilationUnit = sourceCode.createCompilationUnit("com.example", "Test"); KotlinTypeDeclaration test = compilationUnit.createTypeDeclaration("Test"); - test.annotations().add("TestAnnotation1", ClassName.of("com.example.TestClassAnnotation")); - test.annotations().add("TestAnnotation2", ClassName.of("com.example.TestClassAnnotation")); + test.annotations().addRepeatable(ClassName.of("com.example.Repeatable")); + test.annotations().addRepeatable(ClassName.of("com.example.Repeatable")); List lines = writeSingleType(sourceCode, "com/example/Test.kt"); - assertThat(lines).containsExactly("package com.example", "", "@TestClassAnnotation", "@TestClassAnnotation", - "class Test"); + assertThat(lines).containsExactly("package com.example", "", "@Repeatable", "@Repeatable", "class Test"); } @Test - void repeatableAnnotationsOnField() throws IOException { + void repeatablePropertyAnnotations() throws IOException { KotlinSourceCode sourceCode = new KotlinSourceCode(); KotlinCompilationUnit compilationUnit = sourceCode.createCompilationUnit("com.example", "Test"); KotlinTypeDeclaration test = compilationUnit.createTypeDeclaration("Test"); - KotlinPropertyDeclaration field = KotlinPropertyDeclaration.var("testField") + KotlinPropertyDeclaration property = KotlinPropertyDeclaration.var("myProperty") .returning("java.lang.String") .empty(); - field.annotations().add("TestAnnotation1", ClassName.of("com.example.TestFiledAnnotation")); - field.annotations().add("TestAnnotation2", ClassName.of("com.example.TestFiledAnnotation")); - test.addPropertyDeclaration(field); + property.annotations().addRepeatable(ClassName.of("com.example.Repeatable")); + property.annotations().addRepeatable(ClassName.of("com.example.Repeatable")); + test.addPropertyDeclaration(property); List lines = writeSingleType(sourceCode, "com/example/Test.kt"); - assertThat(lines).containsExactly("package com.example", "", "class Test {", "", " @TestFiledAnnotation", - " @TestFiledAnnotation", " var testField: String", "", "}"); + assertThat(lines).containsExactly("package com.example", "", "class Test {", "", " @Repeatable", + " @Repeatable", " var myProperty: String", "", "}"); } @Test - void repeatableAnnotationsOnMethod() throws IOException { + void repeatableMethodAnnotations() throws IOException { KotlinSourceCode sourceCode = new KotlinSourceCode(); KotlinCompilationUnit compilationUnit = sourceCode.createCompilationUnit("com.example", "Test"); KotlinTypeDeclaration test = compilationUnit.createTypeDeclaration("Test"); - KotlinFunctionDeclaration method = KotlinFunctionDeclaration.function("testMethod").body(CodeBlock.of("")); - method.annotations().add("TestAnnotation1", ClassName.of("com.example.TestMethodAnnotation")); - method.annotations().add("TestAnnotation2", ClassName.of("com.example.TestMethodAnnotation")); + KotlinFunctionDeclaration method = KotlinFunctionDeclaration.function("myMethod").body(CodeBlock.of("")); + method.annotations().addRepeatable(ClassName.of("com.example.Repeatable")); + method.annotations().addRepeatable(ClassName.of("com.example.Repeatable")); test.addFunctionDeclaration(method); List lines = writeSingleType(sourceCode, "com/example/Test.kt"); - assertThat(lines).containsExactly("package com.example", "", "class Test {", "", " @TestMethodAnnotation", - " @TestMethodAnnotation", " fun testMethod() {", " }", "", "}"); + assertThat(lines).containsExactly("package com.example", "", "class Test {", "", " @Repeatable", + " @Repeatable", " fun myMethod() {", " }", "", "}"); } private List writeSingleType(KotlinSourceCode sourceCode, String location) throws IOException { From 8327127ad881f7e9e4c55fcf29422126f65c980b Mon Sep 17 00:00:00 2001 From: sijun-yang Date: Mon, 21 Jul 2025 14:26:41 +0900 Subject: [PATCH 28/28] Chore Signed-off-by: sijun-yang --- .../initializr/generator/language/AnnotationContainer.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/initializr-generator/src/main/java/io/spring/initializr/generator/language/AnnotationContainer.java b/initializr-generator/src/main/java/io/spring/initializr/generator/language/AnnotationContainer.java index 51a9de758d..0e52e43fe2 100644 --- a/initializr-generator/src/main/java/io/spring/initializr/generator/language/AnnotationContainer.java +++ b/initializr-generator/src/main/java/io/spring/initializr/generator/language/AnnotationContainer.java @@ -209,8 +209,8 @@ public void addRepeatable(ClassName className, Consumer annotation) { } /** - * Remove the annotation with the specified classname from either the single or the - * repeatable annotations. + * Remove the annotation with the specified classname from either the single + * annotation or the repeatable annotations. * @param className the class name of the annotation * @return {@code true} if such an annotation exists, {@code false} otherwise */