diff --git a/spring-context/src/main/java/org/springframework/context/aot/AbstractAotProcessor.java b/spring-context/src/main/java/org/springframework/context/aot/AbstractAotProcessor.java index d51d8af1f2d6..282e700256a9 100644 --- a/spring-context/src/main/java/org/springframework/context/aot/AbstractAotProcessor.java +++ b/spring-context/src/main/java/org/springframework/context/aot/AbstractAotProcessor.java @@ -24,6 +24,7 @@ import org.springframework.aot.hint.RuntimeHints; import org.springframework.aot.nativex.FileNativeConfigurationWriter; import org.springframework.lang.Nullable; +import org.springframework.util.Assert; import org.springframework.util.FileSystemUtils; /** @@ -49,6 +50,7 @@ public abstract class AbstractAotProcessor { /** * Create a new processor instance with the supplied {@linkplain Settings settings}. + * @see Settings#builder() */ protected AbstractAotProcessor(Settings settings) { this.settings = settings; @@ -102,114 +104,163 @@ protected void writeHints(RuntimeHints hints) { /** * Common settings for AOT processors. */ - public static class Settings { + public static final class Settings { - @Nullable - private Path sourceOutput; + private final Path sourceOutput; - @Nullable - private Path resourceOutput; + private final Path resourceOutput; - @Nullable - private Path classOutput; + private final Path classOutput; - @Nullable - private String groupId; + private final String groupId; - @Nullable - private String artifactId; + private final String artifactId; - /** - * Set the output directory for generated sources. - * @param sourceOutput the location of generated sources - * @return this settings object for method chaining - */ - public Settings setSourceOutput(Path sourceOutput) { + private Settings(Path sourceOutput, Path resourceOutput, Path classOutput, String groupId, String artifactId) { this.sourceOutput = sourceOutput; - return this; + this.resourceOutput = resourceOutput; + this.classOutput = classOutput; + this.groupId = groupId; + this.artifactId = artifactId; } + /** - * Get the output directory for generated sources. + * Create a new {@link Builder} for {@link Settings}. */ - @Nullable - public Path getSourceOutput() { - return this.sourceOutput; + public static Builder builder() { + return new Builder(); } + /** - * Set the output directory for generated resources. - * @param resourceOutput the location of generated resources - * @return this settings object for method chaining + * Get the output directory for generated sources. */ - public Settings setResourceOutput(Path resourceOutput) { - this.resourceOutput = resourceOutput; - return this; + public Path getSourceOutput() { + return this.sourceOutput; } /** * Get the output directory for generated resources. */ - @Nullable public Path getResourceOutput() { return this.resourceOutput; } - /** - * Set the output directory for generated classes. - * @param classOutput the location of generated classes - * @return this settings object for method chaining - */ - public Settings setClassOutput(Path classOutput) { - this.classOutput = classOutput; - return this; - } - /** * Get the output directory for generated classes. */ - @Nullable public Path getClassOutput() { return this.classOutput; } - /** - * Set the group ID of the application. - * @param groupId the group ID of the application, used to locate - * {@code native-image.properties} - * @return this settings object for method chaining - */ - public Settings setGroupId(String groupId) { - this.groupId = groupId; - return this; - } - /** * Get the group ID of the application. */ - @Nullable public String getGroupId() { return this.groupId; } /** - * Set the artifact ID of the application. - * @param artifactId the artifact ID of the application, used to locate - * {@code native-image.properties} - * @return this settings object for method chaining + * Get the artifact ID of the application. */ - public Settings setArtifactId(String artifactId) { - this.artifactId = artifactId; - return this; + public String getArtifactId() { + return this.artifactId; } + /** - * Get the artifact ID of the application. + * Fluent builder API for {@link Settings}. */ - @Nullable - public String getArtifactId() { - return this.artifactId; + public static final class Builder { + + @Nullable + private Path sourceOutput; + + @Nullable + private Path resourceOutput; + + @Nullable + private Path classOutput; + + @Nullable + private String groupId; + + @Nullable + private String artifactId; + + + private Builder() { + // internal constructor + } + + + /** + * Set the output directory for generated sources. + * @param sourceOutput the location of generated sources + * @return this builder for method chaining + */ + public Builder sourceOutput(Path sourceOutput) { + this.sourceOutput = sourceOutput; + return this; + } + + /** + * Set the output directory for generated resources. + * @param resourceOutput the location of generated resources + * @return this builder for method chaining + */ + public Builder resourceOutput(Path resourceOutput) { + this.resourceOutput = resourceOutput; + return this; + } + + /** + * Set the output directory for generated classes. + * @param classOutput the location of generated classes + * @return this builder for method chaining + */ + public Builder classOutput(Path classOutput) { + this.classOutput = classOutput; + return this; + } + + /** + * Set the group ID of the application. + * @param groupId the group ID of the application, used to locate + * {@code native-image.properties} + * @return this builder for method chaining + */ + public Builder groupId(String groupId) { + this.groupId = groupId; + return this; + } + + /** + * Set the artifact ID of the application. + * @param artifactId the artifact ID of the application, used to locate + * {@code native-image.properties} + * @return this builder for method chaining + */ + public Builder artifactId(String artifactId) { + this.artifactId = artifactId; + return this; + } + + /** + * Build the {@link Settings} configured in this {@code Builder}. + */ + public Settings build() { + Assert.notNull(this.sourceOutput, "'sourceOutput' must not be null"); + Assert.notNull(this.resourceOutput, "'resourceOutput' must not be null"); + Assert.notNull(this.classOutput, "'classOutput' must not be null"); + Assert.hasText(this.groupId, "'groupId' must not be null or empty"); + Assert.hasText(this.artifactId, "'artifactId' must not be null or empty"); + return new Settings(this.sourceOutput, this.resourceOutput, this.classOutput, + this.groupId, this.artifactId); + } + } } diff --git a/spring-context/src/test/java/org/springframework/context/aot/AotProcessorTests.java b/spring-context/src/test/java/org/springframework/context/aot/AotProcessorTests.java new file mode 100644 index 000000000000..e4436a1b952c --- /dev/null +++ b/spring-context/src/test/java/org/springframework/context/aot/AotProcessorTests.java @@ -0,0 +1,126 @@ +/* + * Copyright 2002-2022 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.context.aot; + +import java.nio.file.Path; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.io.TempDir; + +import org.springframework.context.aot.AbstractAotProcessor.Settings; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException; + +/** + * Tests for {@link AbstractAotProcessor}, settings, and builder. + * + * @author Sam Brannen + * @since 6.0 + */ +class AotProcessorTests { + + @Test + void builderRejectsMissingSourceOutput() { + assertThatIllegalArgumentException() + .isThrownBy(() -> Settings.builder().build()) + .withMessageContaining("'sourceOutput'"); + } + + @Test + void builderRejectsMissingResourceOutput(@TempDir Path tempDir) { + assertThatIllegalArgumentException() + .isThrownBy(() -> Settings.builder().sourceOutput(tempDir).build()) + .withMessageContaining("'resourceOutput'"); + } + + @Test + void builderRejectsMissingClassOutput(@TempDir Path tempDir) { + assertThatIllegalArgumentException() + .isThrownBy(() -> Settings.builder() + .sourceOutput(tempDir) + .resourceOutput(tempDir) + .build()) + .withMessageContaining("'classOutput'"); + } + + @Test + void builderRejectsMissingGroupdId(@TempDir Path tempDir) { + assertThatIllegalArgumentException() + .isThrownBy(() -> Settings.builder() + .sourceOutput(tempDir) + .resourceOutput(tempDir) + .classOutput(tempDir) + .build()) + .withMessageContaining("'groupId'"); + } + + @Test + void builderRejectsEmptyGroupdId(@TempDir Path tempDir) { + assertThatIllegalArgumentException() + .isThrownBy(() -> Settings.builder() + .sourceOutput(tempDir) + .resourceOutput(tempDir) + .classOutput(tempDir) + .groupId(" ") + .build()) + .withMessageContaining("'groupId'"); + } + + @Test + void builderRejectsMissingArtifactId(@TempDir Path tempDir) { + assertThatIllegalArgumentException() + .isThrownBy(() -> Settings.builder() + .sourceOutput(tempDir) + .resourceOutput(tempDir) + .classOutput(tempDir) + .groupId("my-group") + .build()) + .withMessageContaining("'artifactId'"); + } + + @Test + void builderRejectsEmptyArtifactId(@TempDir Path tempDir) { + assertThatIllegalArgumentException() + .isThrownBy(() -> Settings.builder() + .sourceOutput(tempDir) + .resourceOutput(tempDir) + .classOutput(tempDir) + .groupId("my-group") + .artifactId(" ") + .build()) + .withMessageContaining("'artifactId'"); + } + + @Test + void builderAcceptsRequiredSettings(@TempDir Path tempDir) { + Settings settings = Settings.builder() + .sourceOutput(tempDir) + .resourceOutput(tempDir) + .classOutput(tempDir) + .groupId("my-group") + .artifactId("my-artifact") + .build(); + assertThat(settings).isNotNull(); + assertThat(settings.getSourceOutput()).isEqualTo(tempDir); + assertThat(settings.getResourceOutput()).isEqualTo(tempDir); + assertThat(settings.getClassOutput()).isEqualTo(tempDir); + assertThat(settings.getGroupId()).isEqualTo("my-group"); + assertThat(settings.getArtifactId()).isEqualTo("my-artifact"); + } + +} diff --git a/spring-context/src/test/java/org/springframework/context/aot/ContextAotProcessorTests.java b/spring-context/src/test/java/org/springframework/context/aot/ContextAotProcessorTests.java index 5baeea5bb727..384f54d59fcc 100644 --- a/spring-context/src/test/java/org/springframework/context/aot/ContextAotProcessorTests.java +++ b/spring-context/src/test/java/org/springframework/context/aot/ContextAotProcessorTests.java @@ -128,12 +128,13 @@ private static class DemoContextAotProcessor extends ContextAotProcessor { private static Settings createSettings(Path sourceOutput, Path resourceOutput, Path classOutput, String groupId, String artifactId) { - return new Settings() - .setSourceOutput(sourceOutput) - .setResourceOutput(resourceOutput) - .setClassOutput(classOutput) - .setArtifactId(artifactId) - .setGroupId(groupId); + return Settings.builder() + .sourceOutput(sourceOutput) + .resourceOutput(resourceOutput) + .classOutput(classOutput) + .artifactId(artifactId) + .groupId(groupId) + .build(); } @Override diff --git a/spring-test/src/test/java/org/springframework/test/context/aot/TestAotProcessorTests.java b/spring-test/src/test/java/org/springframework/test/context/aot/TestAotProcessorTests.java index eee73dae0629..c0bf253e9530 100644 --- a/spring-test/src/test/java/org/springframework/test/context/aot/TestAotProcessorTests.java +++ b/spring-test/src/test/java/org/springframework/test/context/aot/TestAotProcessorTests.java @@ -109,12 +109,13 @@ private static class DemoTestAotProcessor extends TestAotProcessor { private static Settings createSettings(Path sourceOutput, Path resourceOutput, Path classOutput, String groupId, String artifactId) { - return new Settings() - .setSourceOutput(sourceOutput) - .setResourceOutput(resourceOutput) - .setClassOutput(classOutput) - .setArtifactId(artifactId) - .setGroupId(groupId); + return Settings.builder() + .sourceOutput(sourceOutput) + .resourceOutput(resourceOutput) + .classOutput(classOutput) + .artifactId(artifactId) + .groupId(groupId) + .build(); } }