Skip to content

Commit 2650da2

Browse files
committed
Provide more control over registration of GeneratedFiles
This commit provides an advanced handling of generated files that provides more control over files registration. The callback provides a FileHandler that can determine if the file already exists and its content. The caller can then chose to override the content or leave it as it is. Closes gh-31331
1 parent e6b77d3 commit 2650da2

File tree

6 files changed

+236
-40
lines changed

6 files changed

+236
-40
lines changed

spring-core/src/main/java/org/springframework/aot/generate/FileSystemGeneratedFiles.java

+48-10
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2022 the original author or authors.
2+
* Copyright 2002-2024 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -18,24 +18,29 @@
1818

1919
import java.io.IOException;
2020
import java.io.InputStream;
21+
import java.nio.file.CopyOption;
2122
import java.nio.file.FileSystem;
2223
import java.nio.file.Files;
2324
import java.nio.file.Path;
25+
import java.nio.file.StandardCopyOption;
2426
import java.util.Arrays;
2527
import java.util.Objects;
2628
import java.util.function.Function;
2729

30+
import org.springframework.core.io.FileSystemResource;
2831
import org.springframework.core.io.InputStreamSource;
2932
import org.springframework.util.Assert;
33+
import org.springframework.util.function.ThrowingConsumer;
3034

3135
/**
3236
* {@link GeneratedFiles} implementation that stores generated files using a
3337
* {@link FileSystem}.
3438
*
3539
* @author Phillip Webb
40+
* @author Stephane Nicoll
3641
* @since 6.0
3742
*/
38-
public class FileSystemGeneratedFiles implements GeneratedFiles {
43+
public class FileSystemGeneratedFiles extends GeneratedFiles {
3944

4045
private final Function<Kind, Path> roots;
4146

@@ -80,21 +85,54 @@ private static Function<Kind, Path> conventionRoots(Path root) {
8085
}
8186

8287
@Override
83-
public void addFile(Kind kind, String path, InputStreamSource content) {
88+
public void handleFile(Kind kind, String path, ThrowingConsumer<FileHandler> handler) {
89+
FileSystemFileHandler fileHandler = new FileSystemFileHandler(toPath(kind, path));
90+
handler.accept(fileHandler);
91+
}
92+
93+
private Path toPath(Kind kind, String path) {
8494
Assert.notNull(kind, "'kind' must not be null");
8595
Assert.hasLength(path, "'path' must not be empty");
86-
Assert.notNull(content, "'content' must not be null");
8796
Path root = this.roots.apply(kind).toAbsolutePath().normalize();
8897
Path relativePath = root.resolve(path).toAbsolutePath().normalize();
8998
Assert.isTrue(relativePath.startsWith(root), "'path' must be relative");
90-
try {
91-
try (InputStream inputStream = content.getInputStream()) {
92-
Files.createDirectories(relativePath.getParent());
93-
Files.copy(inputStream, relativePath);
99+
return relativePath;
100+
}
101+
102+
static final class FileSystemFileHandler extends FileHandler {
103+
104+
private final Path path;
105+
106+
FileSystemFileHandler(Path path) {
107+
super(Files.exists(path), () -> new FileSystemResource(path));
108+
this.path = path;
109+
}
110+
111+
@Override
112+
protected void copy(InputStreamSource content, boolean override) {
113+
if (override) {
114+
copy(content, StandardCopyOption.REPLACE_EXISTING);
115+
}
116+
else {
117+
copy(content);
94118
}
95119
}
96-
catch (IOException ex) {
97-
throw new IllegalStateException(ex);
120+
121+
private void copy(InputStreamSource content, CopyOption... copyOptions) {
122+
try {
123+
try (InputStream inputStream = content.getInputStream()) {
124+
Files.createDirectories(this.path.getParent());
125+
Files.copy(inputStream, this.path, copyOptions);
126+
}
127+
}
128+
catch (IOException ex) {
129+
throw new IllegalStateException(ex);
130+
}
131+
}
132+
133+
@Override
134+
public String toString() {
135+
return this.path.toString();
98136
}
99137
}
100138

spring-core/src/main/java/org/springframework/aot/generate/GeneratedFiles.java

+89-14
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2023 the original author or authors.
2+
* Copyright 2002-2024 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -16,8 +16,11 @@
1616

1717
package org.springframework.aot.generate;
1818

19+
import java.util.function.Supplier;
20+
1921
import org.springframework.core.io.InputStreamSource;
2022
import org.springframework.javapoet.JavaFile;
23+
import org.springframework.lang.Nullable;
2124
import org.springframework.util.Assert;
2225
import org.springframework.util.ClassUtils;
2326
import org.springframework.util.StringUtils;
@@ -36,14 +39,14 @@
3639
* @see InMemoryGeneratedFiles
3740
* @see FileSystemGeneratedFiles
3841
*/
39-
public interface GeneratedFiles {
42+
public abstract class GeneratedFiles {
4043

4144
/**
4245
* Add a generated {@link Kind#SOURCE source file} with content from the
4346
* given {@link JavaFile}.
4447
* @param javaFile the java file to add
4548
*/
46-
default void addSourceFile(JavaFile javaFile) {
49+
public void addSourceFile(JavaFile javaFile) {
4750
validatePackage(javaFile.packageName, javaFile.typeSpec.name);
4851
String className = javaFile.packageName + "." + javaFile.typeSpec.name;
4952
addSourceFile(className, javaFile::writeTo);
@@ -56,7 +59,7 @@ default void addSourceFile(JavaFile javaFile) {
5659
* of the file
5760
* @param content the contents of the file
5861
*/
59-
default void addSourceFile(String className, CharSequence content) {
62+
public void addSourceFile(String className, CharSequence content) {
6063
addSourceFile(className, appendable -> appendable.append(content));
6164
}
6265

@@ -68,7 +71,7 @@ default void addSourceFile(String className, CharSequence content) {
6871
* @param content a {@link ThrowingConsumer} that accepts an
6972
* {@link Appendable} which will receive the file contents
7073
*/
71-
default void addSourceFile(String className, ThrowingConsumer<Appendable> content) {
74+
public void addSourceFile(String className, ThrowingConsumer<Appendable> content) {
7275
addFile(Kind.SOURCE, getClassNamePath(className), content);
7376
}
7477

@@ -80,7 +83,7 @@ default void addSourceFile(String className, ThrowingConsumer<Appendable> conten
8083
* @param content an {@link InputStreamSource} that will provide an input
8184
* stream containing the file contents
8285
*/
83-
default void addSourceFile(String className, InputStreamSource content) {
86+
public void addSourceFile(String className, InputStreamSource content) {
8487
addFile(Kind.SOURCE, getClassNamePath(className), content);
8588
}
8689

@@ -90,7 +93,7 @@ default void addSourceFile(String className, InputStreamSource content) {
9093
* @param path the relative path of the file
9194
* @param content the contents of the file
9295
*/
93-
default void addResourceFile(String path, CharSequence content) {
96+
public void addResourceFile(String path, CharSequence content) {
9497
addResourceFile(path, appendable -> appendable.append(content));
9598
}
9699

@@ -101,7 +104,7 @@ default void addResourceFile(String path, CharSequence content) {
101104
* @param content a {@link ThrowingConsumer} that accepts an
102105
* {@link Appendable} which will receive the file contents
103106
*/
104-
default void addResourceFile(String path, ThrowingConsumer<Appendable> content) {
107+
public void addResourceFile(String path, ThrowingConsumer<Appendable> content) {
105108
addFile(Kind.RESOURCE, path, content);
106109
}
107110

@@ -112,7 +115,7 @@ default void addResourceFile(String path, ThrowingConsumer<Appendable> content)
112115
* @param content an {@link InputStreamSource} that will provide an input
113116
* stream containing the file contents
114117
*/
115-
default void addResourceFile(String path, InputStreamSource content) {
118+
public void addResourceFile(String path, InputStreamSource content) {
116119
addFile(Kind.RESOURCE, path, content);
117120
}
118121

@@ -123,7 +126,7 @@ default void addResourceFile(String path, InputStreamSource content) {
123126
* @param content an {@link InputStreamSource} that will provide an input
124127
* stream containing the file contents
125128
*/
126-
default void addClassFile(String path, InputStreamSource content) {
129+
public void addClassFile(String path, InputStreamSource content) {
127130
addFile(Kind.CLASS, path, content);
128131
}
129132

@@ -134,7 +137,7 @@ default void addClassFile(String path, InputStreamSource content) {
134137
* @param path the relative path of the file
135138
* @param content the contents of the file
136139
*/
137-
default void addFile(Kind kind, String path, CharSequence content) {
140+
public void addFile(Kind kind, String path, CharSequence content) {
138141
addFile(kind, path, appendable -> appendable.append(content));
139142
}
140143

@@ -146,7 +149,7 @@ default void addFile(Kind kind, String path, CharSequence content) {
146149
* @param content a {@link ThrowingConsumer} that accepts an
147150
* {@link Appendable} which will receive the file contents
148151
*/
149-
default void addFile(Kind kind, String path, ThrowingConsumer<Appendable> content) {
152+
public void addFile(Kind kind, String path, ThrowingConsumer<Appendable> content) {
150153
Assert.notNull(content, "'content' must not be null");
151154
addFile(kind, path, new AppendableConsumerInputStreamSource(content));
152155
}
@@ -159,7 +162,21 @@ default void addFile(Kind kind, String path, ThrowingConsumer<Appendable> conten
159162
* @param content an {@link InputStreamSource} that will provide an input
160163
* stream containing the file contents
161164
*/
162-
void addFile(Kind kind, String path, InputStreamSource content);
165+
public void addFile(Kind kind, String path, InputStreamSource content) {
166+
Assert.notNull(kind, "'kind' must not be null");
167+
Assert.hasLength(path, "'path' must not be empty");
168+
Assert.notNull(content, "'content' must not be null");
169+
handleFile(kind, path, handler -> handler.create(content));
170+
}
171+
172+
/**
173+
* Add a generated file of the specified {@link Kind} with the given
174+
* {@linkplain FileHandler handler}.
175+
* @param kind the kind of file being written
176+
* @param path the relative path of the file
177+
* @param handler a consumer of a {@link FileHandler} for the file
178+
*/
179+
public abstract void handleFile(Kind kind, String path, ThrowingConsumer<FileHandler> handler);
163180

164181
private static String getClassNamePath(String className) {
165182
Assert.hasLength(className, "'className' must not be empty");
@@ -194,7 +211,7 @@ private static boolean isJavaIdentifier(String className) {
194211
/**
195212
* The various kinds of generated files that are supported.
196213
*/
197-
enum Kind {
214+
public enum Kind {
198215

199216
/**
200217
* A source file containing Java code that should be compiled.
@@ -215,4 +232,62 @@ enum Kind {
215232

216233
}
217234

235+
/**
236+
* Provide access to a particular file and offer convenient method to save
237+
* or override its content.
238+
*/
239+
public abstract static class FileHandler {
240+
241+
private final boolean exists;
242+
243+
private final Supplier<InputStreamSource> existingContent;
244+
245+
protected FileHandler(boolean exists, Supplier<InputStreamSource> existingContent) {
246+
this.exists = exists;
247+
this.existingContent = existingContent;
248+
}
249+
250+
/**
251+
* Specify whether the file already exists.
252+
* @return {@code true} if the file already exists
253+
*/
254+
public boolean exists() {
255+
return this.exists;
256+
}
257+
258+
/**
259+
* Return an {@link InputStreamSource} for the content of the file or
260+
* {@code null} if the file does not exist.
261+
*/
262+
@Nullable
263+
public InputStreamSource getContent() {
264+
return (exists() ? this.existingContent.get() : null);
265+
}
266+
267+
/**
268+
* Create a file with the given {@linkplain InputStreamSource content}.
269+
* @throws IllegalStateException if the file already exists
270+
*/
271+
public void create(InputStreamSource content) {
272+
Assert.notNull(content, "'content' must not be null");
273+
if (exists()) {
274+
throw new IllegalStateException("%s already exists".formatted(this));
275+
}
276+
copy(content, false);
277+
}
278+
279+
/**
280+
* Override the content of the file handled by this instance using the
281+
* given {@linkplain InputStreamSource content}. If the file does not
282+
* exist, it is created.
283+
*/
284+
public void override(InputStreamSource content) {
285+
Assert.notNull(content, "'content' must not be null");
286+
copy(content, true);
287+
}
288+
289+
protected abstract void copy(InputStreamSource content, boolean override);
290+
291+
}
292+
218293
}

spring-core/src/main/java/org/springframework/aot/generate/InMemoryGeneratedFiles.java

+29-8
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2023 the original author or authors.
2+
* Copyright 2002-2024 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -26,27 +26,25 @@
2626
import org.springframework.core.io.InputStreamSource;
2727
import org.springframework.lang.Nullable;
2828
import org.springframework.util.Assert;
29+
import org.springframework.util.function.ThrowingConsumer;
2930

3031
/**
3132
* {@link GeneratedFiles} implementation that keeps generated files in-memory.
3233
*
3334
* @author Phillip Webb
35+
* @author Stephane Nicoll
3436
* @since 6.0
3537
*/
36-
public class InMemoryGeneratedFiles implements GeneratedFiles {
38+
public class InMemoryGeneratedFiles extends GeneratedFiles {
3739

3840
private final Map<Kind, Map<String, InputStreamSource>> files = new HashMap<>();
3941

4042

4143
@Override
42-
public void addFile(Kind kind, String path, InputStreamSource content) {
43-
Assert.notNull(kind, "'kind' must not be null");
44-
Assert.hasLength(path, "'path' must not be empty");
45-
Assert.notNull(content, "'content' must not be null");
44+
public void handleFile(Kind kind, String path, ThrowingConsumer<FileHandler> handler) {
4645
Map<String, InputStreamSource> paths = this.files.computeIfAbsent(kind,
4746
key -> new LinkedHashMap<>());
48-
Assert.state(!paths.containsKey(path), () -> "Path '" + path + "' already in use");
49-
paths.put(path, content);
47+
handler.accept(new InMemoryFileHandler(paths, path));
5048
}
5149

5250
/**
@@ -89,4 +87,27 @@ public InputStreamSource getGeneratedFile(Kind kind, String path) {
8987
return (paths != null ? paths.get(path) : null);
9088
}
9189

90+
private static class InMemoryFileHandler extends FileHandler {
91+
92+
private final Map<String, InputStreamSource> paths;
93+
94+
private final String key;
95+
96+
InMemoryFileHandler(Map<String, InputStreamSource> paths, String key) {
97+
super(paths.containsKey(key), () -> paths.get(key));
98+
this.paths = paths;
99+
this.key = key;
100+
}
101+
102+
@Override
103+
protected void copy(InputStreamSource content, boolean override) {
104+
this.paths.put(this.key, content);
105+
}
106+
107+
@Override
108+
public String toString() {
109+
return this.key;
110+
}
111+
}
112+
92113
}

0 commit comments

Comments
 (0)