Skip to content

Commit 30cd14d

Browse files
committed
Add module to support testing of generated code
Closes gh-28120
2 parents b5695b9 + 7255a8b commit 30cd14d

38 files changed

+3262
-1
lines changed

build.gradle

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,7 @@ configure(allprojects) { project ->
7474
dependency "com.google.code.gson:gson:2.8.9"
7575
dependency "com.google.protobuf:protobuf-java-util:3.19.3"
7676
dependency "com.googlecode.protobuf-java-format:protobuf-java-format:1.4"
77+
dependency "com.thoughtworks.qdox:qdox:2.0.1"
7778
dependency("com.thoughtworks.xstream:xstream:1.4.18") {
7879
exclude group: "xpp3", name: "xpp3_min"
7980
exclude group: "xmlpull", name: "xmlpull"

framework-bom/framework-bom.gradle

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ group = "org.springframework"
77

88
dependencies {
99
constraints {
10-
parent.moduleProjects.sort { "$it.name" }.each {
10+
parent.moduleProjects.findAll{ it.name != 'spring-core-test' }.sort{ "$it.name" }.each {
1111
api it
1212
}
1313
}

settings.gradle

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ include "spring-context"
1818
include "spring-context-indexer"
1919
include "spring-context-support"
2020
include "spring-core"
21+
include "spring-core-test"
2122
include "spring-expression"
2223
include "spring-instrument"
2324
include "spring-jcl"
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
description = "Spring Core Test"
2+
3+
dependencies {
4+
api(project(":spring-core"))
5+
api("org.assertj:assertj-core")
6+
api("com.thoughtworks.qdox:qdox")
7+
}
8+
9+
tasks.withType(PublishToMavenRepository).configureEach {
10+
it.enabled = false
11+
}
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
/*
2+
* Copyright 2002-2022 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.aot.test.generator.compile;
18+
19+
/**
20+
* Exception thrown when code cannot compile.
21+
*
22+
* @author Phillip Webb
23+
* @since 6.0
24+
*/
25+
@SuppressWarnings("serial")
26+
public class CompilationException extends RuntimeException {
27+
28+
CompilationException(String message) {
29+
super(message);
30+
}
31+
32+
}
Lines changed: 170 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,170 @@
1+
/*
2+
* Copyright 2002-2022 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.aot.test.generator.compile;
18+
19+
import java.lang.reflect.Constructor;
20+
import java.util.ArrayList;
21+
import java.util.Collections;
22+
import java.util.List;
23+
24+
import org.springframework.aot.test.generator.file.ResourceFile;
25+
import org.springframework.aot.test.generator.file.ResourceFiles;
26+
import org.springframework.aot.test.generator.file.SourceFile;
27+
import org.springframework.aot.test.generator.file.SourceFiles;
28+
import org.springframework.lang.Nullable;
29+
import org.springframework.util.Assert;
30+
31+
/**
32+
* Fully compiled results provided from a {@link TestCompiler}.
33+
*
34+
* @author Phillip Webb
35+
* @since 6.0
36+
*/
37+
public class Compiled {
38+
39+
40+
private final ClassLoader classLoader;
41+
42+
private final SourceFiles sourceFiles;
43+
44+
private final ResourceFiles resourceFiles;
45+
46+
@Nullable
47+
private List<Class<?>> compiledClasses;
48+
49+
50+
Compiled(ClassLoader classLoader, SourceFiles sourceFiles,
51+
ResourceFiles resourceFiles) {
52+
this.classLoader = classLoader;
53+
this.sourceFiles = sourceFiles;
54+
this.resourceFiles = resourceFiles;
55+
}
56+
57+
58+
/**
59+
* Return the classloader containing the compiled content and access to the
60+
* resources.
61+
* @return the classLoader
62+
*/
63+
public ClassLoader getClassLoader() {
64+
return this.classLoader;
65+
}
66+
67+
/**
68+
* Return the single source file that was compiled.
69+
* @return the single source file
70+
* @throws IllegalStateException if the compiler wasn't passed exactly one
71+
* file
72+
*/
73+
public SourceFile getSourceFile() {
74+
return this.sourceFiles.getSingle();
75+
}
76+
77+
/**
78+
* Return all source files that were compiled.
79+
* @return the source files used by the compiler
80+
*/
81+
public SourceFiles getSourceFiles() {
82+
return this.sourceFiles;
83+
}
84+
85+
/**
86+
* Return the single resource file that was used when compiled.
87+
* @return the single resource file
88+
* @throws IllegalStateException if the compiler wasn't passed exactly one
89+
* file
90+
*/
91+
public ResourceFile getResourceFile() {
92+
return this.resourceFiles.getSingle();
93+
}
94+
95+
/**
96+
* Return all resource files that were compiled.
97+
* @return the resource files used by the compiler
98+
*/
99+
public ResourceFiles getResourceFiles() {
100+
return this.resourceFiles;
101+
}
102+
103+
/**
104+
* Return a new instance of a compiled class of the given type. There must
105+
* be only a single instance and it must have a default constructor.
106+
* @param <T> the required type
107+
* @param type the required type
108+
* @return an instance of type created from the compiled classes
109+
* @throws IllegalStateException if no instance can be found or instantiated
110+
*/
111+
public <T> T getInstance(Class<T> type) {
112+
List<Class<?>> matching = getAllCompiledClasses().stream().filter(type::isAssignableFrom).toList();
113+
Assert.state(!matching.isEmpty(), () -> "No instance found of type " + type.getName());
114+
Assert.state(matching.size() == 1, () -> "Multiple instances found of type " + type.getName());
115+
return newInstance(matching.get(0));
116+
}
117+
118+
/**
119+
* Return an instance of a compiled class identified by its class name. The
120+
* class must have a default constructor.
121+
* @param <T> the type to return
122+
* @param type the type to return
123+
* @param className the class name to load
124+
* @return an instance of the class
125+
* @throws IllegalStateException if no instance can be found or instantiated
126+
*/
127+
public <T> T getInstance(Class<T> type, String className) {
128+
Class<?> loaded = loadClass(className);
129+
return newInstance(loaded);
130+
}
131+
132+
/**
133+
* Return all compiled classes.
134+
* @return a list of all compiled classes
135+
*/
136+
public List<Class<?>> getAllCompiledClasses() {
137+
List<Class<?>> compiledClasses = this.compiledClasses;
138+
if (compiledClasses == null) {
139+
compiledClasses = new ArrayList<>();
140+
this.sourceFiles.stream().map(this::loadClass).forEach(compiledClasses::add);
141+
this.compiledClasses = Collections.unmodifiableList(compiledClasses);
142+
}
143+
return compiledClasses;
144+
}
145+
146+
@SuppressWarnings("unchecked")
147+
private <T> T newInstance(Class<?> loaded) {
148+
try {
149+
Constructor<?> constructor = loaded.getDeclaredConstructor();
150+
return (T) constructor.newInstance();
151+
}
152+
catch (Exception ex) {
153+
throw new IllegalStateException(ex);
154+
}
155+
}
156+
157+
private Class<?> loadClass(SourceFile sourceFile) {
158+
return loadClass(sourceFile.getClassName());
159+
}
160+
161+
private Class<?> loadClass(String className) {
162+
try {
163+
return this.classLoader.loadClass(className);
164+
}
165+
catch (ClassNotFoundException ex) {
166+
throw new IllegalStateException(ex);
167+
}
168+
}
169+
170+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
/*
2+
* Copyright 2002-2022 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.aot.test.generator.compile;
18+
19+
import java.io.ByteArrayOutputStream;
20+
import java.io.OutputStream;
21+
import java.net.URI;
22+
23+
import javax.tools.JavaFileObject;
24+
import javax.tools.SimpleJavaFileObject;
25+
26+
/**
27+
* In-memory {@link JavaFileObject} used to hold class bytecode.
28+
*
29+
* @author Phillip Webb
30+
* @since 6.0
31+
*/
32+
class DynamicClassFileObject extends SimpleJavaFileObject {
33+
34+
private volatile byte[] bytes = new byte[0];
35+
36+
37+
DynamicClassFileObject(String className) {
38+
super(URI.create("class:///" + className.replace('.', '/') + ".class"),
39+
Kind.CLASS);
40+
}
41+
42+
43+
@Override
44+
public OutputStream openOutputStream() {
45+
return new JavaClassOutputStream();
46+
}
47+
48+
byte[] getBytes() {
49+
return this.bytes;
50+
}
51+
52+
53+
class JavaClassOutputStream extends ByteArrayOutputStream {
54+
55+
@Override
56+
public void close() {
57+
DynamicClassFileObject.this.bytes = toByteArray();
58+
}
59+
60+
}
61+
62+
}

0 commit comments

Comments
 (0)