From 1816c77c51132b6fff6bd2f275c993bc7e1250c6 Mon Sep 17 00:00:00 2001 From: Phillip Webb Date: Wed, 13 Apr 2022 18:16:14 -0700 Subject: [PATCH] Add generation context interface Add `GenerationContext` interface and `DefaultGenerationContext` implementation as the central entry point for AOT processing. See gh-28414 --- .../generate/DefaultGenerationContext.java | 102 +++++++++++++++++ .../aot/generate/GenerationContext.java | 76 +++++++++++++ .../DefaultGenerationContextTests.java | 105 ++++++++++++++++++ 3 files changed, 283 insertions(+) create mode 100644 spring-core/src/main/java/org/springframework/aot/generate/DefaultGenerationContext.java create mode 100644 spring-core/src/main/java/org/springframework/aot/generate/GenerationContext.java create mode 100644 spring-core/src/test/java/org/springframework/aot/generate/DefaultGenerationContextTests.java diff --git a/spring-core/src/main/java/org/springframework/aot/generate/DefaultGenerationContext.java b/spring-core/src/main/java/org/springframework/aot/generate/DefaultGenerationContext.java new file mode 100644 index 0000000000..4698d28dbb --- /dev/null +++ b/spring-core/src/main/java/org/springframework/aot/generate/DefaultGenerationContext.java @@ -0,0 +1,102 @@ +/* + * Copyright 2002-2022 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.aot.generate; + +import java.io.IOException; + +import org.springframework.aot.hint.RuntimeHints; +import org.springframework.util.Assert; + +/** + * Default implementation of {@link GenerationContext}. + * + * @author Phillip Webb + * @author Stephane Nicoll + * @since 6.0 + */ +public class DefaultGenerationContext implements GenerationContext { + + private final ClassNameGenerator classNameGenerator; + + private final GeneratedClasses generatedClasses; + + private final GeneratedFiles generatedFiles; + + private final RuntimeHints runtimeHints; + + + /** + * Create a new {@link DefaultGenerationContext} instance backed by the + * specified {@code generatedFiles}. + * @param generatedFiles the generated files + */ + public DefaultGenerationContext(GeneratedFiles generatedFiles) { + this(new ClassNameGenerator(), generatedFiles, new RuntimeHints()); + } + + /** + * Create a new {@link DefaultGenerationContext} instance backed by the + * specified items. + * @param classNameGenerator the class name generator + * @param generatedFiles the generated files + * @param runtimeHints the runtime hints + */ + public DefaultGenerationContext(ClassNameGenerator classNameGenerator, + GeneratedFiles generatedFiles, RuntimeHints runtimeHints) { + Assert.notNull(classNameGenerator, "'classNameGenerator' must not be null"); + Assert.notNull(generatedFiles, "'generatedFiles' must not be null"); + Assert.notNull(runtimeHints, "'runtimeHints' must not be null"); + this.classNameGenerator = classNameGenerator; + this.generatedClasses = new GeneratedClasses(classNameGenerator); + this.generatedFiles = generatedFiles; + this.runtimeHints = runtimeHints; + } + + + @Override + public ClassNameGenerator getClassNameGenerator() { + return this.classNameGenerator; + } + + @Override + public GeneratedClasses getClassGenerator() { + return this.generatedClasses; + } + + @Override + public GeneratedFiles getGeneratedFiles() { + return this.generatedFiles; + } + + @Override + public RuntimeHints getRuntimeHints() { + return this.runtimeHints; + } + + /** + * Write any generated content out to the generated files. + */ + public void writeGeneratedContent() { + try { + this.generatedClasses.writeTo(this.generatedFiles); + } + catch (IOException ex) { + throw new IllegalStateException(ex); + } + } + +} diff --git a/spring-core/src/main/java/org/springframework/aot/generate/GenerationContext.java b/spring-core/src/main/java/org/springframework/aot/generate/GenerationContext.java new file mode 100644 index 0000000000..3aadb51b39 --- /dev/null +++ b/spring-core/src/main/java/org/springframework/aot/generate/GenerationContext.java @@ -0,0 +1,76 @@ +/* + * Copyright 2002-2022 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.aot.generate; + +import org.springframework.aot.hint.JavaSerializationHints; +import org.springframework.aot.hint.ProxyHints; +import org.springframework.aot.hint.ReflectionHints; +import org.springframework.aot.hint.ResourceHints; +import org.springframework.aot.hint.RuntimeHints; + +/** + * Central interface used for code generation. + *

+ * A generation context provides: + *

+ * + * @author Phillip Webb + * @author Stephane Nicoll + * @since 6.0 + */ +public interface GenerationContext { + + /** + * Return the {@link ClassNameGenerator} being used by the context. Allows + * new class names to be generated before they are added to the + * {@link #getGeneratedFiles() generated files}. + * @return the class name generator + * @see #getGeneratedFiles() + */ + ClassNameGenerator getClassNameGenerator(); + + /** + * Return the {@link GeneratedClasses} being used by the context. Allows a + * single generated class to be shared across multiple AOT processors. All + * generated classes are written at the end of AOT processing. + * @return the generated classes + */ + ClassGenerator getClassGenerator(); + + /** + * Return the {@link GeneratedFiles} being used by the context. Used to + * write resource, java source or class bytecode files. + * @return the generated files + */ + GeneratedFiles getGeneratedFiles(); + + /** + * Return the {@link RuntimeHints} being used by the context. Used to record + * {@link ReflectionHints reflection}, {@link ResourceHints resource}, + * {@link JavaSerializationHints serialization} and {@link ProxyHints proxy} + * hints so that the application can run as a native image. + * @return the runtime hints + */ + RuntimeHints getRuntimeHints(); + +} diff --git a/spring-core/src/test/java/org/springframework/aot/generate/DefaultGenerationContextTests.java b/spring-core/src/test/java/org/springframework/aot/generate/DefaultGenerationContextTests.java new file mode 100644 index 0000000000..a6d84e87eb --- /dev/null +++ b/spring-core/src/test/java/org/springframework/aot/generate/DefaultGenerationContextTests.java @@ -0,0 +1,105 @@ +/* + * Copyright 2002-2022 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.aot.generate; + +import org.junit.jupiter.api.Test; + +import org.springframework.aot.hint.RuntimeHints; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException; + +/** + * Tests for {@link DefaultGenerationContext}. + * + * @author Phillip Webb + * @author Stephane Nicoll + */ +class DefaultGenerationContextTests { + + private final ClassNameGenerator classNameGenerator = new ClassNameGenerator(); + + private final GeneratedFiles generatedFiles = new InMemoryGeneratedFiles(); + + private final RuntimeHints runtimeHints = new RuntimeHints(); + + + @Test + void createWithOnlyGeneratedFilesCreatesContext() { + DefaultGenerationContext context = new DefaultGenerationContext( + this.generatedFiles); + assertThat(context.getClassNameGenerator()) + .isInstanceOf(ClassNameGenerator.class); + assertThat(context.getGeneratedFiles()).isSameAs(this.generatedFiles); + assertThat(context.getRuntimeHints()).isInstanceOf(RuntimeHints.class); + } + + @Test + void createCreatesContext() { + DefaultGenerationContext context = new DefaultGenerationContext( + this.classNameGenerator, this.generatedFiles, this.runtimeHints); + assertThat(context.getClassNameGenerator()).isNotNull(); + assertThat(context.getGeneratedFiles()).isNotNull(); + assertThat(context.getRuntimeHints()).isNotNull(); + } + + @Test + void createWhenClassNameGeneratorIsNullThrowsException() { + assertThatIllegalArgumentException() + .isThrownBy(() -> new DefaultGenerationContext(null, this.generatedFiles, + this.runtimeHints)) + .withMessage("'classNameGenerator' must not be null"); + } + + @Test + void createWhenGeneratedFilesIsNullThrowsException() { + assertThatIllegalArgumentException() + .isThrownBy(() -> new DefaultGenerationContext(this.classNameGenerator, + null, this.runtimeHints)) + .withMessage("'generatedFiles' must not be null"); + } + + @Test + void createWhenRuntimeHintsIsNullThrowsException() { + assertThatIllegalArgumentException() + .isThrownBy(() -> new DefaultGenerationContext(this.classNameGenerator, + this.generatedFiles, null)) + .withMessage("'runtimeHints' must not be null"); + } + + @Test + void getClassNameGeneratorReturnsClassNameGenerator() { + DefaultGenerationContext context = new DefaultGenerationContext( + this.classNameGenerator, this.generatedFiles, this.runtimeHints); + assertThat(context.getClassNameGenerator()).isSameAs(this.classNameGenerator); + } + + @Test + void getGeneratedFilesReturnsGeneratedFiles() { + DefaultGenerationContext context = new DefaultGenerationContext( + this.classNameGenerator, this.generatedFiles, this.runtimeHints); + assertThat(context.getGeneratedFiles()).isSameAs(this.generatedFiles); + } + + @Test + void getRuntimeHintsReturnsRuntimeHints() { + DefaultGenerationContext context = new DefaultGenerationContext( + this.classNameGenerator, this.generatedFiles, this.runtimeHints); + assertThat(context.getRuntimeHints()).isSameAs(this.runtimeHints); + } + +}