Add GeneratedFiles interface and support classes
Add a `GeneratedFiles` interface that can be used to add generated source, class and resource files. An in-memory implementation is provided for testing and a filesystem implementation is provided to actually save the files to disk. See gh-28414
This commit is contained in:
parent
f2cf78c525
commit
99173fbd4f
|
@ -0,0 +1,56 @@
|
|||
/*
|
||||
* 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.ByteArrayInputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
|
||||
import org.springframework.core.io.InputStreamSource;
|
||||
import org.springframework.util.function.ThrowingConsumer;
|
||||
|
||||
/**
|
||||
* Adapter class to convert a {@link ThrowingConsumer} of {@link Appendable} to
|
||||
* an {@link InputStreamSource}.
|
||||
*
|
||||
* @author Phillip Webb
|
||||
* @since 6.0
|
||||
*/
|
||||
class AppendableConsumerInputStreamSource implements InputStreamSource {
|
||||
|
||||
private final ThrowingConsumer<Appendable> content;
|
||||
|
||||
|
||||
AppendableConsumerInputStreamSource(ThrowingConsumer<Appendable> content) {
|
||||
this.content = content;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public InputStream getInputStream() throws IOException {
|
||||
return new ByteArrayInputStream(toString().getBytes(StandardCharsets.UTF_8));
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
StringBuilder buffer = new StringBuilder();
|
||||
this.content.accept(buffer);
|
||||
return buffer.toString();
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,101 @@
|
|||
/*
|
||||
* 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 java.io.InputStream;
|
||||
import java.nio.file.FileSystem;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.Arrays;
|
||||
import java.util.Objects;
|
||||
import java.util.function.Function;
|
||||
|
||||
import org.springframework.core.io.InputStreamSource;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* {@link GeneratedFiles} implementation that stores generated files using a
|
||||
* {@link FileSystem}.
|
||||
*
|
||||
* @author Phillip Webb
|
||||
* @since 6.0
|
||||
*/
|
||||
public class FileSystemGeneratedFiles implements GeneratedFiles {
|
||||
|
||||
private final Function<Kind, Path> roots;
|
||||
|
||||
|
||||
/**
|
||||
* Create a new {@link FileSystemGeneratedFiles} instance with all files
|
||||
* stored under the specific {@code root}. The following subdirectories are
|
||||
* created for the different file {@link Kind kinds}:
|
||||
* <ul>
|
||||
* <li>{@code sources}</li>
|
||||
* <li>{@code resources}</li>
|
||||
* <li>{@code classes}</li>
|
||||
* </ul>
|
||||
* @param root the root path
|
||||
* @see #FileSystemGeneratedFiles(Function)
|
||||
*/
|
||||
public FileSystemGeneratedFiles(Path root) {
|
||||
this(conventionRoots(root));
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new {@link FileSystemGeneratedFiles} instance with all files
|
||||
* stored under the root provided by the given {@link Function}.
|
||||
* @param roots a function that returns the root to use for the given
|
||||
* {@link Kind}
|
||||
*/
|
||||
public FileSystemGeneratedFiles(Function<Kind, Path> roots) {
|
||||
Assert.notNull(roots, "'roots' must not be null");
|
||||
Assert.isTrue(Arrays.stream(Kind.values()).map(roots).noneMatch(Objects::isNull),
|
||||
"'roots' must return a value for all file kinds");
|
||||
this.roots = roots;
|
||||
}
|
||||
|
||||
|
||||
private static Function<Kind, Path> conventionRoots(Path root) {
|
||||
Assert.notNull(root, "'root' must not be null");
|
||||
return kind -> switch (kind) {
|
||||
case SOURCE -> root.resolve("sources");
|
||||
case RESOURCE -> root.resolve("resources");
|
||||
case CLASS -> root.resolve("classes");
|
||||
};
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addFile(Kind kind, String path, InputStreamSource content) {
|
||||
Assert.notNull(kind, "'kind' must not be null");
|
||||
Assert.hasLength(path, "'path' must not be empty");
|
||||
Assert.notNull(content, "'kind' must not be null");
|
||||
Path root = this.roots.apply(kind).toAbsolutePath().normalize();
|
||||
Path relativePath = root.resolve(path).toAbsolutePath().normalize();
|
||||
Assert.isTrue(relativePath.startsWith(root), () -> "'path' must be relative");
|
||||
try {
|
||||
try (InputStream inputStream = content.getInputStream()) {
|
||||
Files.createDirectories(relativePath.getParent());
|
||||
Files.copy(inputStream, relativePath);
|
||||
}
|
||||
}
|
||||
catch (IOException ex) {
|
||||
throw new IllegalStateException(ex);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,207 @@
|
|||
/*
|
||||
* 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.core.io.InputStreamSource;
|
||||
import org.springframework.javapoet.JavaFile;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.ClassUtils;
|
||||
import org.springframework.util.function.ThrowingConsumer;
|
||||
|
||||
/**
|
||||
* Interface that can be used to add {@link Kind#SOURCE source},
|
||||
* {@link Kind#RESOURCE resource} or {@link Kind#CLASS class} files generated
|
||||
* during ahead-of-time processing. Source and resource files are written using
|
||||
* UTF-8 encoding.
|
||||
*
|
||||
* @author Phillip Webb
|
||||
* @author Brian Clozel
|
||||
* @author Stephane Nicoll
|
||||
* @since 6.0
|
||||
* @see InMemoryGeneratedFiles
|
||||
* @see FileSystemGeneratedFiles
|
||||
*/
|
||||
public interface GeneratedFiles {
|
||||
|
||||
/**
|
||||
* Add a generated {@link Kind#SOURCE source file} with content from the
|
||||
* given {@link JavaFile}.
|
||||
* @param javaFile the java file to add
|
||||
*/
|
||||
default void addSourceFile(JavaFile javaFile) {
|
||||
String className = javaFile.packageName + "." + javaFile.typeSpec.name;
|
||||
addSourceFile(className, javaFile::writeTo);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a generated {@link Kind#SOURCE source file} with content from the
|
||||
* given {@link CharSequence}.
|
||||
* @param className the class name that should be used to determine the path
|
||||
* of the file
|
||||
* @param content the contents of the file
|
||||
*/
|
||||
default void addSourceFile(String className, CharSequence content) {
|
||||
addSourceFile(className, appendable -> appendable.append(content));
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a generated {@link Kind#SOURCE source file} with content written to
|
||||
* an {@link Appendable} passed to the given {@link ThrowingConsumer}.
|
||||
* @param className the class name that should be used to determine the path
|
||||
* of the file
|
||||
* @param content a {@link ThrowingConsumer} that accepts an
|
||||
* {@link Appendable} which will receive the file contents
|
||||
*/
|
||||
default void addSourceFile(String className, ThrowingConsumer<Appendable> content) {
|
||||
addFile(Kind.SOURCE, getClassNamePath(className), content);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a generated {@link Kind#SOURCE source file} with content from the
|
||||
* given {@link InputStreamSource}.
|
||||
* @param className the class name that should be used to determine the path
|
||||
* of the file
|
||||
* @param content an {@link InputStreamSource} that will provide an input
|
||||
* stream containing the file contents
|
||||
*/
|
||||
default void addSourceFile(String className, InputStreamSource content) {
|
||||
addFile(Kind.SOURCE, getClassNamePath(className), content);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a generated {@link Kind#RESOURCE resource file} with content from the
|
||||
* given {@link CharSequence}.
|
||||
* @param path the relative path of the file
|
||||
* @param content the contents of the file
|
||||
*/
|
||||
default void addResourceFile(String path, CharSequence content) {
|
||||
addResourceFile(path, appendable -> appendable.append(content));
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a generated {@link Kind#RESOURCE resource file} with content written
|
||||
* to an {@link Appendable} passed to the given {@link ThrowingConsumer}.
|
||||
* @param path the relative path of the file
|
||||
* @param content a {@link ThrowingConsumer} that accepts an
|
||||
* {@link Appendable} which will receive the file contents
|
||||
*/
|
||||
default void addResourceFile(String path, ThrowingConsumer<Appendable> content) {
|
||||
addFile(Kind.RESOURCE, path, content);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a generated {@link Kind#RESOURCE resource file} with content from the
|
||||
* given {@link InputStreamSource}.
|
||||
* @param path the relative path of the file
|
||||
* @param content an {@link InputStreamSource} that will provide an input
|
||||
* stream containing the file contents
|
||||
*/
|
||||
default void addResourceFile(String path, InputStreamSource content) {
|
||||
addFile(Kind.RESOURCE, path, content);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a generated {@link Kind#CLASS class file} with content from the given
|
||||
* {@link InputStreamSource}.
|
||||
* @param path the relative path of the file
|
||||
* @param content an {@link InputStreamSource} that will provide an input
|
||||
* stream containing the file contents
|
||||
*/
|
||||
default void addClassFile(String path, InputStreamSource content) {
|
||||
addFile(Kind.CLASS, path, content);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a generated file of the specified {@link Kind} with content from the
|
||||
* given {@link CharSequence}.
|
||||
* @param kind the kind of file being written
|
||||
* @param path the relative path of the file
|
||||
* @param content the contents of the file
|
||||
*/
|
||||
default void addFile(Kind kind, String path, CharSequence content) {
|
||||
addFile(kind, path, appendable -> appendable.append(content));
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a generated file of the specified {@link Kind} with content written
|
||||
* to an {@link Appendable} passed to the given {@link ThrowingConsumer}.
|
||||
* @param kind the kind of file being written
|
||||
* @param path the relative path of the file
|
||||
* @param content a {@link ThrowingConsumer} that accepts an
|
||||
* {@link Appendable} which will receive the file contents
|
||||
*/
|
||||
default void addFile(Kind kind, String path, ThrowingConsumer<Appendable> content) {
|
||||
Assert.notNull(content, "'content' must not be null");
|
||||
addFile(kind, path, new AppendableConsumerInputStreamSource(content));
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a generated file of the specified {@link Kind} with content from the
|
||||
* given {@link InputStreamSource}.
|
||||
* @param kind the kind of file being written
|
||||
* @param path the relative path of the file
|
||||
* @param content an {@link InputStreamSource} that will provide an input
|
||||
* stream containing the file contents
|
||||
*/
|
||||
void addFile(Kind kind, String path, InputStreamSource content);
|
||||
|
||||
private static String getClassNamePath(String className) {
|
||||
Assert.hasLength(className, "'className' must not be empty");
|
||||
Assert.isTrue(isJavaIdentifier(className),
|
||||
"'className' must be a valid identifier");
|
||||
return ClassUtils.convertClassNameToResourcePath(className) + ".java";
|
||||
}
|
||||
|
||||
private static boolean isJavaIdentifier(String className) {
|
||||
char[] chars = className.toCharArray();
|
||||
for (int i = 0; i < chars.length; i++) {
|
||||
if (i == 0 && !Character.isJavaIdentifierStart(chars[i])) {
|
||||
return false;
|
||||
}
|
||||
if (i > 0 && chars[i] != '.' && !Character.isJavaIdentifierPart(chars[i])) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* The various kinds of generated files that are supported.
|
||||
*/
|
||||
enum Kind {
|
||||
|
||||
/**
|
||||
* A source file containing Java code that should be compiled.
|
||||
*/
|
||||
SOURCE,
|
||||
|
||||
/**
|
||||
* A resource file that should be directly added to final application.
|
||||
* For example, a {@code .properties} file.
|
||||
*/
|
||||
RESOURCE,
|
||||
|
||||
/**
|
||||
* A class file containing bytecode. For example, the result of a proxy
|
||||
* generated using cglib.
|
||||
*/
|
||||
CLASS
|
||||
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,95 @@
|
|||
/*
|
||||
* 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 java.nio.charset.StandardCharsets;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import org.springframework.core.io.InputStreamSource;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* {@link GeneratedFiles} implementation that keeps generated files in-memory.
|
||||
*
|
||||
* @author Phillip Webb
|
||||
* @since 6.0
|
||||
*/
|
||||
public class InMemoryGeneratedFiles implements GeneratedFiles {
|
||||
|
||||
private final Map<Kind, Map<String, InputStreamSource>> files = new HashMap<>();
|
||||
|
||||
|
||||
@Override
|
||||
public void addFile(Kind kind, String path, InputStreamSource content) {
|
||||
Assert.notNull(kind, "'kind' must not be null");
|
||||
Assert.hasLength(path, "'path' must not be empty");
|
||||
Assert.notNull(content, "'content' must not be null");
|
||||
Map<String, InputStreamSource> paths = this.files.computeIfAbsent(kind,
|
||||
key -> new LinkedHashMap<>());
|
||||
Assert.state(!paths.containsKey(path),
|
||||
() -> "Path '" + path + "' already in use");
|
||||
paths.put(path, content);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a {@link Map} of the generated files of a specific {@link Kind}.
|
||||
* @param kind the kind of generated file
|
||||
* @return a {@link Map} of paths to {@link InputStreamSource} instances
|
||||
*/
|
||||
public Map<String, InputStreamSource> getGeneratedFiles(Kind kind) {
|
||||
Assert.notNull(kind, "'kind' must not be null");
|
||||
return Collections
|
||||
.unmodifiableMap(this.files.getOrDefault(kind, Collections.emptyMap()));
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the content of the specified file.
|
||||
* @param kind the kind of generated file
|
||||
* @param path the path of the file
|
||||
* @return the file content or {@code null} if no file could be found
|
||||
* @throws IOException on read error
|
||||
*/
|
||||
@Nullable
|
||||
public String getGeneratedFileContent(Kind kind, String path) throws IOException {
|
||||
InputStreamSource source = getGeneratedFile(kind, path);
|
||||
if (source != null) {
|
||||
return new String(source.getInputStream().readAllBytes(),
|
||||
StandardCharsets.UTF_8);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the {@link InputStreamSource} of specified file.
|
||||
* @param kind the kind of generated file
|
||||
* @param path the path of the file
|
||||
* @return the file source or {@code null} if no file could be found
|
||||
*/
|
||||
@Nullable
|
||||
public InputStreamSource getGeneratedFile(Kind kind, String path) {
|
||||
Assert.notNull(kind, "'kind' must not be null");
|
||||
Assert.hasLength(path, "'path' must not be empty");
|
||||
Map<String, InputStreamSource> paths = this.files.get(kind);
|
||||
return (paths != null) ? paths.get(path) : null;
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,10 @@
|
|||
/**
|
||||
* Support classes for components that contribute generated code equivalent to a
|
||||
* runtime behavior.
|
||||
*/
|
||||
@NonNullApi
|
||||
@NonNullFields
|
||||
package org.springframework.aot.generate;
|
||||
|
||||
import org.springframework.lang.NonNullApi;
|
||||
import org.springframework.lang.NonNullFields;
|
|
@ -0,0 +1,112 @@
|
|||
/*
|
||||
* 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.nio.charset.StandardCharsets;
|
||||
import java.nio.file.Path;
|
||||
import java.util.function.Function;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.io.TempDir;
|
||||
|
||||
import org.springframework.aot.generate.GeneratedFiles.Kind;
|
||||
import org.springframework.core.io.ByteArrayResource;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
|
||||
|
||||
/**
|
||||
* Tests for {@link FileSystemGeneratedFiles}.
|
||||
*
|
||||
* @author Phillip Webb
|
||||
*/
|
||||
class FileSystemGeneratedFilesTests {
|
||||
|
||||
@TempDir
|
||||
Path root;
|
||||
|
||||
@Test
|
||||
void addFilesCopiesToFileSystem() {
|
||||
FileSystemGeneratedFiles generatedFiles = new FileSystemGeneratedFiles(this.root);
|
||||
generatedFiles.addSourceFile("com.example.Test", "{}");
|
||||
generatedFiles.addResourceFile("META-INF/test", "test");
|
||||
generatedFiles.addClassFile("com/example/TestProxy.class",
|
||||
new ByteArrayResource("!".getBytes(StandardCharsets.UTF_8)));
|
||||
assertThat(this.root.resolve("sources/com/example/Test.java")).content()
|
||||
.isEqualTo("{}");
|
||||
assertThat(this.root.resolve("resources/META-INF/test")).content()
|
||||
.isEqualTo("test");
|
||||
assertThat(this.root.resolve("classes/com/example/TestProxy.class")).content()
|
||||
.isEqualTo("!");
|
||||
}
|
||||
|
||||
@Test
|
||||
void addFilesWithCustomRootsCopiesToFileSystem() {
|
||||
FileSystemGeneratedFiles generatedFiles = new FileSystemGeneratedFiles(
|
||||
kind -> this.root.resolve("the-" + kind));
|
||||
generatedFiles.addSourceFile("com.example.Test", "{}");
|
||||
generatedFiles.addResourceFile("META-INF/test", "test");
|
||||
generatedFiles.addClassFile("com/example/TestProxy.class",
|
||||
new ByteArrayResource("!".getBytes(StandardCharsets.UTF_8)));
|
||||
assertThat(this.root.resolve("the-SOURCE/com/example/Test.java")).content()
|
||||
.isEqualTo("{}");
|
||||
assertThat(this.root.resolve("the-RESOURCE/META-INF/test")).content()
|
||||
.isEqualTo("test");
|
||||
assertThat(this.root.resolve("the-CLASS/com/example/TestProxy.class")).content()
|
||||
.isEqualTo("!");
|
||||
}
|
||||
|
||||
@Test
|
||||
void createWhenRootIsNullThrowsException() {
|
||||
assertThatIllegalArgumentException()
|
||||
.isThrownBy(() -> new FileSystemGeneratedFiles((Path) null))
|
||||
.withMessage("'root' must not be null");
|
||||
}
|
||||
|
||||
@Test
|
||||
void createWhenRootsIsNullThrowsException() {
|
||||
assertThatIllegalArgumentException()
|
||||
.isThrownBy(
|
||||
() -> new FileSystemGeneratedFiles((Function<Kind, Path>) null))
|
||||
.withMessage("'roots' must not be null");
|
||||
}
|
||||
|
||||
@Test
|
||||
void createWhenRootsResultsInNullThrowsException() {
|
||||
assertThatIllegalArgumentException()
|
||||
.isThrownBy(
|
||||
() -> new FileSystemGeneratedFiles(kind -> (kind != Kind.CLASS)
|
||||
? this.root.resolve(kind.toString()) : null))
|
||||
.withMessage("'roots' must return a value for all file kinds");
|
||||
}
|
||||
|
||||
@Test
|
||||
void addFileWhenPathIsOutsideOfRootThrowsException() {
|
||||
FileSystemGeneratedFiles generatedFiles = new FileSystemGeneratedFiles(this.root);
|
||||
assertPathMustBeRelative(generatedFiles, "/test");
|
||||
assertPathMustBeRelative(generatedFiles, "../test");
|
||||
assertPathMustBeRelative(generatedFiles, "test/../../test");
|
||||
}
|
||||
|
||||
private void assertPathMustBeRelative(FileSystemGeneratedFiles generatedFiles,
|
||||
String path) {
|
||||
assertThatIllegalArgumentException()
|
||||
.isThrownBy(() -> generatedFiles.addResourceFile(path, "test"))
|
||||
.withMessage("'path' must be relative");
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,171 @@
|
|||
/*
|
||||
* 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.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
|
||||
import javax.lang.model.element.Modifier;
|
||||
|
||||
import org.assertj.core.api.AbstractStringAssert;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import org.springframework.aot.generate.GeneratedFiles.Kind;
|
||||
import org.springframework.core.io.ByteArrayResource;
|
||||
import org.springframework.core.io.InputStreamSource;
|
||||
import org.springframework.core.io.Resource;
|
||||
import org.springframework.javapoet.JavaFile;
|
||||
import org.springframework.javapoet.MethodSpec;
|
||||
import org.springframework.javapoet.TypeSpec;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
|
||||
|
||||
/**
|
||||
* Tests for {@link GeneratedFiles}.
|
||||
*
|
||||
* @author Phillip Webb
|
||||
*/
|
||||
class GeneratedFilesTests {
|
||||
|
||||
private final TestGeneratedFiles generatedFiles = new TestGeneratedFiles();
|
||||
|
||||
@Test
|
||||
void addSourceFileWithJavaFileAddsFile() throws Exception {
|
||||
MethodSpec main = MethodSpec.methodBuilder("main")
|
||||
.addModifiers(Modifier.PUBLIC, Modifier.STATIC).returns(void.class)
|
||||
.addParameter(String[].class, "args")
|
||||
.addStatement("$T.out.println($S)", System.class, "Hello, World!")
|
||||
.build();
|
||||
TypeSpec helloWorld = TypeSpec.classBuilder("HelloWorld")
|
||||
.addModifiers(Modifier.PUBLIC, Modifier.FINAL).addMethod(main).build();
|
||||
JavaFile javaFile = JavaFile.builder("com.example", helloWorld).build();
|
||||
this.generatedFiles.addSourceFile(javaFile);
|
||||
assertThatFileAdded(Kind.SOURCE, "com/example/HelloWorld.java")
|
||||
.contains("Hello, World!");
|
||||
}
|
||||
|
||||
@Test
|
||||
void addSourceFileWithCharSequenceAddsFile() throws Exception {
|
||||
this.generatedFiles.addSourceFile("com.example.HelloWorld", "{}");
|
||||
assertThatFileAdded(Kind.SOURCE, "com/example/HelloWorld.java").isEqualTo("{}");
|
||||
}
|
||||
|
||||
@Test
|
||||
void addSourceFileWithCharSequenceWhenClassNameIsEmptyThrowsException() {
|
||||
assertThatIllegalArgumentException()
|
||||
.isThrownBy(() -> this.generatedFiles.addSourceFile("", "{}"))
|
||||
.withMessage("'className' must not be empty");
|
||||
}
|
||||
|
||||
@Test
|
||||
void addSourceFileWithCharSequenceWhenClassNameIsInvalidThrowsException() {
|
||||
assertThatIllegalArgumentException()
|
||||
.isThrownBy(() -> this.generatedFiles
|
||||
.addSourceFile("com/example/HelloWorld.java", "{}"))
|
||||
.withMessage("'className' must be a valid identifier");
|
||||
}
|
||||
|
||||
@Test
|
||||
void addSourceFileWithConsumedAppendableAddsFile() throws Exception {
|
||||
this.generatedFiles.addSourceFile("com.example.HelloWorld",
|
||||
appendable -> appendable.append("{}"));
|
||||
assertThatFileAdded(Kind.SOURCE, "com/example/HelloWorld.java").isEqualTo("{}");
|
||||
}
|
||||
|
||||
@Test
|
||||
void addSourceFileWithInputStreamSourceAddsFile() throws Exception {
|
||||
Resource resource = new ByteArrayResource("{}".getBytes(StandardCharsets.UTF_8));
|
||||
this.generatedFiles.addSourceFile("com.example.HelloWorld", resource);
|
||||
assertThatFileAdded(Kind.SOURCE, "com/example/HelloWorld.java").isEqualTo("{}");
|
||||
}
|
||||
|
||||
@Test
|
||||
void addResourceFileWithCharSequenceAddsFile() throws Exception {
|
||||
this.generatedFiles.addResourceFile("META-INF/file", "test");
|
||||
assertThatFileAdded(Kind.RESOURCE, "META-INF/file").isEqualTo("test");
|
||||
}
|
||||
|
||||
@Test
|
||||
void addResourceFileWithConsumedAppendableAddsFile() throws Exception {
|
||||
this.generatedFiles.addResourceFile("META-INF/file",
|
||||
appendable -> appendable.append("test"));
|
||||
assertThatFileAdded(Kind.RESOURCE, "META-INF/file").isEqualTo("test");
|
||||
}
|
||||
|
||||
@Test
|
||||
void addResourceFileWithInputStreamSourceAddsFile() throws IOException {
|
||||
Resource resource = new ByteArrayResource(
|
||||
"test".getBytes(StandardCharsets.UTF_8));
|
||||
this.generatedFiles.addResourceFile("META-INF/file", resource);
|
||||
assertThatFileAdded(Kind.RESOURCE, "META-INF/file").isEqualTo("test");
|
||||
}
|
||||
|
||||
@Test
|
||||
void addClassFileWithInputStreamSourceAddsFile() throws IOException {
|
||||
Resource resource = new ByteArrayResource(
|
||||
"test".getBytes(StandardCharsets.UTF_8));
|
||||
this.generatedFiles.addClassFile("com/example/HelloWorld.class", resource);
|
||||
assertThatFileAdded(Kind.CLASS, "com/example/HelloWorld.class").isEqualTo("test");
|
||||
}
|
||||
|
||||
@Test
|
||||
void addFileWithCharSequenceAddsFile() throws Exception {
|
||||
this.generatedFiles.addFile(Kind.RESOURCE, "META-INF/file", "test");
|
||||
assertThatFileAdded(Kind.RESOURCE, "META-INF/file").isEqualTo("test");
|
||||
}
|
||||
|
||||
@Test
|
||||
void addFileWithConsumedAppendableAddsFile() throws IOException {
|
||||
this.generatedFiles.addFile(Kind.SOURCE, "com/example/HelloWorld.java",
|
||||
appendable -> appendable.append("{}"));
|
||||
assertThatFileAdded(Kind.SOURCE, "com/example/HelloWorld.java").isEqualTo("{}");
|
||||
}
|
||||
|
||||
private AbstractStringAssert<?> assertThatFileAdded(Kind kind, String path)
|
||||
throws IOException {
|
||||
return this.generatedFiles.assertThatFileAdded(kind, path);
|
||||
}
|
||||
|
||||
static class TestGeneratedFiles implements GeneratedFiles {
|
||||
|
||||
private Kind kind;
|
||||
|
||||
private String path;
|
||||
|
||||
private InputStreamSource content;
|
||||
|
||||
@Override
|
||||
public void addFile(Kind kind, String path, InputStreamSource content) {
|
||||
this.kind = kind;
|
||||
this.path = path;
|
||||
this.content = content;
|
||||
}
|
||||
|
||||
AbstractStringAssert<?> assertThatFileAdded(Kind kind, String path)
|
||||
throws IOException {
|
||||
assertThat(this.kind).as("kind").isEqualTo(kind);
|
||||
assertThat(this.path).as("path").isEqualTo(path);
|
||||
ByteArrayOutputStream out = new ByteArrayOutputStream();
|
||||
this.content.getInputStream().transferTo(out);
|
||||
return assertThat(out.toString(StandardCharsets.UTF_8));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,87 @@
|
|||
/*
|
||||
* 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.generate.GeneratedFiles.Kind;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.Assertions.assertThatIllegalStateException;
|
||||
|
||||
/**
|
||||
* Tests for {@link InMemoryGeneratedFiles}.
|
||||
*
|
||||
* @author Phillip Webb
|
||||
*/
|
||||
class InMemoryGeneratedFilesTests {
|
||||
|
||||
private final InMemoryGeneratedFiles generatedFiles = new InMemoryGeneratedFiles();
|
||||
|
||||
@Test
|
||||
void addFileAddsInMemoryFile() throws Exception {
|
||||
this.generatedFiles.addResourceFile("META-INF/test", "test");
|
||||
assertThat(this.generatedFiles.getGeneratedFileContent(Kind.RESOURCE,
|
||||
"META-INF/test")).isEqualTo("test");
|
||||
}
|
||||
|
||||
@Test
|
||||
void addFileWhenFileAlreadyAddedThrowsException() {
|
||||
this.generatedFiles.addResourceFile("META-INF/test", "test");
|
||||
assertThatIllegalStateException().isThrownBy(
|
||||
() -> this.generatedFiles.addResourceFile("META-INF/test", "test"))
|
||||
.withMessage("Path 'META-INF/test' already in use");
|
||||
}
|
||||
|
||||
@Test
|
||||
void getGeneratedFilesReturnsFiles() throws Exception {
|
||||
this.generatedFiles.addResourceFile("META-INF/test1", "test1");
|
||||
this.generatedFiles.addResourceFile("META-INF/test2", "test2");
|
||||
assertThat(this.generatedFiles.getGeneratedFiles(Kind.RESOURCE))
|
||||
.containsKeys("META-INF/test1", "META-INF/test2");
|
||||
}
|
||||
|
||||
@Test
|
||||
void getGeneratedFileContentWhenFileExistsReturnsContent() throws Exception {
|
||||
this.generatedFiles.addResourceFile("META-INF/test", "test");
|
||||
assertThat(this.generatedFiles.getGeneratedFileContent(Kind.RESOURCE,
|
||||
"META-INF/test")).isEqualTo("test");
|
||||
}
|
||||
|
||||
@Test
|
||||
void getGeneratedFileContentWhenFileIsMissingReturnsNull() throws Exception {
|
||||
this.generatedFiles.addResourceFile("META-INF/test", "test");
|
||||
assertThat(this.generatedFiles.getGeneratedFileContent(Kind.RESOURCE,
|
||||
"META-INF/missing")).isNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
void getGeneratedFileWhenFileExistsReturnsInputStreamSource() {
|
||||
this.generatedFiles.addResourceFile("META-INF/test", "test");
|
||||
assertThat(this.generatedFiles.getGeneratedFile(Kind.RESOURCE, "META-INF/test"))
|
||||
.isNotNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
void getGeneratedFileWhenFileIsMissingReturnsNull() {
|
||||
this.generatedFiles.addResourceFile("META-INF/test", "test");
|
||||
assertThat(
|
||||
this.generatedFiles.getGeneratedFile(Kind.RESOURCE, "META-INF/missing"))
|
||||
.isNull();
|
||||
}
|
||||
|
||||
}
|
Loading…
Reference in New Issue