Skip to content

Commit 7255a8b

Browse files
snicollwilkinsona
andcommitted
Polish "Add module to support testing of generated code"
See gh-28120 Co-authored-by: Andy Wilkinson <[email protected]>
1 parent 653dc59 commit 7255a8b

File tree

16 files changed

+146
-47
lines changed

16 files changed

+146
-47
lines changed

spring-core-test/spring-core-test.gradle

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,3 @@
1-
import com.github.jengelman.gradle.plugins.shadow.tasks.ShadowJar
2-
31
description = "Spring Core Test"
42

53
dependencies {

spring-core-test/src/main/java/org/springframework/aot/test/generator/compile/Compiled.java

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
import org.springframework.aot.test.generator.file.ResourceFiles;
2626
import org.springframework.aot.test.generator.file.SourceFile;
2727
import org.springframework.aot.test.generator.file.SourceFiles;
28+
import org.springframework.lang.Nullable;
2829
import org.springframework.util.Assert;
2930

3031
/**
@@ -42,6 +43,7 @@ public class Compiled {
4243

4344
private final ResourceFiles resourceFiles;
4445

46+
@Nullable
4547
private List<Class<?>> compiledClasses;
4648

4749

@@ -107,8 +109,7 @@ public ResourceFiles getResourceFiles() {
107109
* @throws IllegalStateException if no instance can be found or instantiated
108110
*/
109111
public <T> T getInstance(Class<T> type) {
110-
List<Class<?>> matching = getAllCompiledClasses().stream().filter(
111-
candidate -> type.isAssignableFrom(candidate)).toList();
112+
List<Class<?>> matching = getAllCompiledClasses().stream().filter(type::isAssignableFrom).toList();
112113
Assert.state(!matching.isEmpty(), () -> "No instance found of type " + type.getName());
113114
Assert.state(matching.size() == 1, () -> "Multiple instances found of type " + type.getName());
114115
return newInstance(matching.get(0));

spring-core-test/src/main/java/org/springframework/aot/test/generator/compile/DynamicClassFileObject.java

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,6 @@
1717
package org.springframework.aot.test.generator.compile;
1818

1919
import java.io.ByteArrayOutputStream;
20-
import java.io.IOException;
2120
import java.io.OutputStream;
2221
import java.net.URI;
2322

@@ -32,7 +31,7 @@
3231
*/
3332
class DynamicClassFileObject extends SimpleJavaFileObject {
3433

35-
private volatile byte[] bytes;
34+
private volatile byte[] bytes = new byte[0];
3635

3736

3837
DynamicClassFileObject(String className) {
@@ -42,7 +41,7 @@ class DynamicClassFileObject extends SimpleJavaFileObject {
4241

4342

4443
@Override
45-
public OutputStream openOutputStream() throws IOException {
44+
public OutputStream openOutputStream() {
4645
return new JavaClassOutputStream();
4746
}
4847

@@ -54,7 +53,7 @@ byte[] getBytes() {
5453
class JavaClassOutputStream extends ByteArrayOutputStream {
5554

5655
@Override
57-
public void close() throws IOException {
56+
public void close() {
5857
DynamicClassFileObject.this.bytes = toByteArray();
5958
}
6059

spring-core-test/src/main/java/org/springframework/aot/test/generator/compile/DynamicClassLoader.java

Lines changed: 29 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
import java.lang.System.Logger.Level;
2424
import java.lang.invoke.MethodHandles;
2525
import java.lang.invoke.MethodHandles.Lookup;
26+
import java.lang.reflect.Modifier;
2627
import java.net.MalformedURLException;
2728
import java.net.URL;
2829
import java.net.URLConnection;
@@ -35,17 +36,18 @@
3536
import org.springframework.aot.test.generator.file.ResourceFiles;
3637
import org.springframework.aot.test.generator.file.SourceFile;
3738
import org.springframework.aot.test.generator.file.SourceFiles;
39+
import org.springframework.lang.Nullable;
3840

3941
/**
4042
* {@link ClassLoader} used to expose dynamically generated content.
4143
*
4244
* @author Phillip Webb
45+
* @author Andy Wilkinson
4346
* @since 6.0
4447
*/
4548
public class DynamicClassLoader extends ClassLoader {
4649

47-
private static final Logger logger = System.getLogger(
48-
DynamicClassLoader.class.getName());
50+
private static final Logger logger = System.getLogger(DynamicClassLoader.class.getName());
4951

5052

5153
private final SourceFiles sourceFiles;
@@ -54,10 +56,13 @@ public class DynamicClassLoader extends ClassLoader {
5456

5557
private final Map<String, DynamicClassFileObject> classFiles;
5658

59+
private final ClassLoader sourceLoader;
5760

58-
public DynamicClassLoader(ClassLoader parent, SourceFiles sourceFiles,
61+
62+
public DynamicClassLoader(ClassLoader sourceLoader, SourceFiles sourceFiles,
5963
ResourceFiles resourceFiles, Map<String, DynamicClassFileObject> classFiles) {
60-
super(parent);
64+
super(sourceLoader.getParent());
65+
this.sourceLoader = sourceLoader;
6166
this.sourceFiles = sourceFiles;
6267
this.resourceFiles = resourceFiles;
6368
this.classFiles = classFiles;
@@ -70,7 +75,22 @@ protected Class<?> findClass(String name) throws ClassNotFoundException {
7075
if (classFile != null) {
7176
return defineClass(name, classFile);
7277
}
73-
return super.findClass(name);
78+
try {
79+
Class<?> fromSourceLoader = this.sourceLoader.loadClass(name);
80+
if (Modifier.isPublic(fromSourceLoader.getModifiers())) {
81+
return fromSourceLoader;
82+
}
83+
}
84+
catch (Exception ex) {
85+
// Continue
86+
}
87+
try (InputStream classStream = this.sourceLoader.getResourceAsStream(name.replace(".", "/") + ".class")) {
88+
byte[] bytes = classStream.readAllBytes();
89+
return defineClass(name, bytes, 0, bytes.length, null);
90+
}
91+
catch (IOException ex) {
92+
throw new ClassNotFoundException(name);
93+
}
7494
}
7595

7696
private Class<?> defineClass(String name, DynamicClassFileObject classFile) {
@@ -101,6 +121,7 @@ protected Enumeration<URL> findResources(String name) throws IOException {
101121
}
102122

103123
@Override
124+
@Nullable
104125
protected URL findResource(String name) {
105126
ResourceFile file = this.resourceFiles.get(name);
106127
if (file != null) {
@@ -118,10 +139,11 @@ protected URL findResource(String name) {
118139

119140
private static class SingletonEnumeration<E> implements Enumeration<E> {
120141

142+
@Nullable
121143
private E element;
122144

123145

124-
SingletonEnumeration(E element) {
146+
SingletonEnumeration(@Nullable E element) {
125147
this.element = element;
126148
}
127149

@@ -132,6 +154,7 @@ public boolean hasMoreElements() {
132154
}
133155

134156
@Override
157+
@Nullable
135158
public E nextElement() {
136159
E next = this.element;
137160
this.element = null;

spring-core-test/src/main/java/org/springframework/aot/test/generator/compile/DynamicJavaFileManager.java

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,6 @@ class DynamicJavaFileManager extends ForwardingJavaFileManager<JavaFileManager>
4242
new LinkedHashMap<>());
4343

4444

45-
4645
DynamicJavaFileManager(JavaFileManager fileManager, ClassLoader classLoader) {
4746
super(fileManager);
4847
this.classLoader = classLoader;

spring-core-test/src/main/java/org/springframework/aot/test/generator/compile/DynamicJavaFileObject.java

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,6 @@
1616

1717
package org.springframework.aot.test.generator.compile;
1818

19-
import java.io.IOException;
2019
import java.net.URI;
2120

2221
import javax.tools.JavaFileObject;
@@ -43,7 +42,7 @@ class DynamicJavaFileObject extends SimpleJavaFileObject {
4342

4443

4544
@Override
46-
public CharSequence getCharContent(boolean ignoreEncodingErrors) throws IOException {
45+
public CharSequence getCharContent(boolean ignoreEncodingErrors) {
4746
return this.sourceFile.getContent();
4847
}
4948

spring-core-test/src/main/java/org/springframework/aot/test/generator/compile/TestCompiler.java

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@
3333
import org.springframework.aot.test.generator.file.SourceFile;
3434
import org.springframework.aot.test.generator.file.SourceFiles;
3535
import org.springframework.aot.test.generator.file.WritableContent;
36+
import org.springframework.lang.Nullable;
3637

3738
/**
3839
* Utility that can be used to dynamically compile and test Java source code.
@@ -43,6 +44,7 @@
4344
*/
4445
public final class TestCompiler {
4546

47+
@Nullable
4648
private final ClassLoader classLoader;
4749

4850
private final JavaCompiler compiler;
@@ -52,7 +54,7 @@ public final class TestCompiler {
5254
private final ResourceFiles resourceFiles;
5355

5456

55-
private TestCompiler(ClassLoader classLoader, JavaCompiler compiler,
57+
private TestCompiler(@Nullable ClassLoader classLoader, JavaCompiler compiler,
5658
SourceFiles sourceFiles, ResourceFiles resourceFiles) {
5759
this.classLoader = classLoader;
5860
this.compiler = compiler;
@@ -186,14 +188,14 @@ public void compile(Consumer<Compiled> compiled) throws CompilationException {
186188
}
187189

188190
private DynamicClassLoader compile() {
189-
ClassLoader classLoader = (this.classLoader != null) ? this.classLoader
191+
ClassLoader classLoaderToUse = (this.classLoader != null) ? this.classLoader
190192
: Thread.currentThread().getContextClassLoader();
191193
List<DynamicJavaFileObject> compilationUnits = this.sourceFiles.stream().map(
192194
DynamicJavaFileObject::new).toList();
193195
StandardJavaFileManager standardFileManager = this.compiler.getStandardFileManager(
194196
null, null, null);
195197
DynamicJavaFileManager fileManager = new DynamicJavaFileManager(
196-
standardFileManager, classLoader);
198+
standardFileManager, classLoaderToUse);
197199
if (!this.sourceFiles.isEmpty()) {
198200
Errors errors = new Errors();
199201
CompilationTask task = this.compiler.getTask(null, fileManager, errors, null,
@@ -203,7 +205,7 @@ private DynamicClassLoader compile() {
203205
throw new CompilationException("Unable to compile source" + errors);
204206
}
205207
}
206-
return new DynamicClassLoader(this.classLoader, this.sourceFiles,
208+
return new DynamicClassLoader(classLoaderToUse, this.sourceFiles,
207209
this.resourceFiles, fileManager.getClassFiles());
208210
}
209211

@@ -223,8 +225,8 @@ public void report(Diagnostic<? extends JavaFileObject> diagnostic) {
223225
this.message.append(" ");
224226
this.message.append(diagnostic.getSource().getName());
225227
this.message.append(" ");
226-
this.message.append(
227-
diagnostic.getLineNumber() + ":" + diagnostic.getColumnNumber());
228+
this.message.append(diagnostic.getLineNumber()).append(":")
229+
.append(diagnostic.getColumnNumber());
228230
}
229231
}
230232

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
/**
2+
* Support classes for compiling and testing generated code.
3+
*/
4+
@NonNullApi
5+
@NonNullFields
6+
package org.springframework.aot.test.generator.compile;
7+
8+
import org.springframework.lang.NonNullApi;
9+
import org.springframework.lang.NonNullFields;

spring-core-test/src/main/java/org/springframework/aot/test/generator/file/DynamicFile.java

Lines changed: 4 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@
1919
import java.io.IOException;
2020
import java.util.Objects;
2121

22-
import org.assertj.core.util.Strings;
22+
import org.springframework.util.Assert;
2323

2424
/**
2525
* Abstract base class for dynamically generated files.
@@ -29,7 +29,7 @@
2929
* @see SourceFile
3030
* @see ResourceFile
3131
*/
32-
public abstract sealed class DynamicFile permits SourceFile,ResourceFile {
32+
public abstract sealed class DynamicFile permits SourceFile, ResourceFile {
3333

3434

3535
private final String path;
@@ -38,21 +38,14 @@ public abstract sealed class DynamicFile permits SourceFile,ResourceFile {
3838

3939

4040
protected DynamicFile(String path, String content) {
41-
if (Strings.isNullOrEmpty(content)) {
42-
throw new IllegalArgumentException("'path' must not to be empty");
43-
}
44-
if (Strings.isNullOrEmpty(content)) {
45-
throw new IllegalArgumentException("'content' must not to be empty");
46-
}
41+
Assert.hasText(path, "Path must not be empty");
42+
Assert.hasText(content, "Content must not be empty");
4743
this.path = path;
4844
this.content = content;
4945
}
5046

5147

5248
protected static String toString(WritableContent writableContent) {
53-
if (writableContent == null) {
54-
throw new IllegalArgumentException("'writableContent' must not to be empty");
55-
}
5649
try {
5750
StringBuilder stringBuilder = new StringBuilder();
5851
writableContent.writeTo(stringBuilder);

spring-core-test/src/main/java/org/springframework/aot/test/generator/file/DynamicFileAssert.java

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,8 @@
1818

1919
import org.assertj.core.api.AbstractAssert;
2020

21+
import org.springframework.lang.Nullable;
22+
2123
import static org.assertj.core.api.Assertions.assertThat;
2224

2325
/**
@@ -42,7 +44,7 @@ public A contains(CharSequence... values) {
4244
return this.myself;
4345
}
4446

45-
public A isEqualTo(Object expected) {
47+
public A isEqualTo(@Nullable Object expected) {
4648
if (expected instanceof DynamicFile) {
4749
return super.isEqualTo(expected);
4850
}

0 commit comments

Comments
 (0)