Skip to content

Commit

Permalink
Add module to support testing of generated code
Browse files Browse the repository at this point in the history
  • Loading branch information
snicoll committed Mar 9, 2022
2 parents b5695b9 + 7255a8b commit 30cd14d
Show file tree
Hide file tree
Showing 38 changed files with 3,262 additions and 1 deletion.
1 change: 1 addition & 0 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,7 @@ configure(allprojects) { project ->
dependency "com.google.code.gson:gson:2.8.9"
dependency "com.google.protobuf:protobuf-java-util:3.19.3"
dependency "com.googlecode.protobuf-java-format:protobuf-java-format:1.4"
dependency "com.thoughtworks.qdox:qdox:2.0.1"
dependency("com.thoughtworks.xstream:xstream:1.4.18") {
exclude group: "xpp3", name: "xpp3_min"
exclude group: "xmlpull", name: "xmlpull"
Expand Down
2 changes: 1 addition & 1 deletion framework-bom/framework-bom.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ group = "org.springframework"

dependencies {
constraints {
parent.moduleProjects.sort { "$it.name" }.each {
parent.moduleProjects.findAll{ it.name != 'spring-core-test' }.sort{ "$it.name" }.each {
api it
}
}
Expand Down
1 change: 1 addition & 0 deletions settings.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ include "spring-context"
include "spring-context-indexer"
include "spring-context-support"
include "spring-core"
include "spring-core-test"
include "spring-expression"
include "spring-instrument"
include "spring-jcl"
Expand Down
11 changes: 11 additions & 0 deletions spring-core-test/spring-core-test.gradle
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
description = "Spring Core Test"

dependencies {
api(project(":spring-core"))
api("org.assertj:assertj-core")
api("com.thoughtworks.qdox:qdox")
}

tasks.withType(PublishToMavenRepository).configureEach {
it.enabled = false
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
/*
* 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.test.generator.compile;

/**
* Exception thrown when code cannot compile.
*
* @author Phillip Webb
* @since 6.0
*/
@SuppressWarnings("serial")
public class CompilationException extends RuntimeException {

CompilationException(String message) {
super(message);
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,170 @@
/*
* 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.test.generator.compile;

import java.lang.reflect.Constructor;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

import org.springframework.aot.test.generator.file.ResourceFile;
import org.springframework.aot.test.generator.file.ResourceFiles;
import org.springframework.aot.test.generator.file.SourceFile;
import org.springframework.aot.test.generator.file.SourceFiles;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;

/**
* Fully compiled results provided from a {@link TestCompiler}.
*
* @author Phillip Webb
* @since 6.0
*/
public class Compiled {


private final ClassLoader classLoader;

private final SourceFiles sourceFiles;

private final ResourceFiles resourceFiles;

@Nullable
private List<Class<?>> compiledClasses;


Compiled(ClassLoader classLoader, SourceFiles sourceFiles,
ResourceFiles resourceFiles) {
this.classLoader = classLoader;
this.sourceFiles = sourceFiles;
this.resourceFiles = resourceFiles;
}


/**
* Return the classloader containing the compiled content and access to the
* resources.
* @return the classLoader
*/
public ClassLoader getClassLoader() {
return this.classLoader;
}

/**
* Return the single source file that was compiled.
* @return the single source file
* @throws IllegalStateException if the compiler wasn't passed exactly one
* file
*/
public SourceFile getSourceFile() {
return this.sourceFiles.getSingle();
}

/**
* Return all source files that were compiled.
* @return the source files used by the compiler
*/
public SourceFiles getSourceFiles() {
return this.sourceFiles;
}

/**
* Return the single resource file that was used when compiled.
* @return the single resource file
* @throws IllegalStateException if the compiler wasn't passed exactly one
* file
*/
public ResourceFile getResourceFile() {
return this.resourceFiles.getSingle();
}

/**
* Return all resource files that were compiled.
* @return the resource files used by the compiler
*/
public ResourceFiles getResourceFiles() {
return this.resourceFiles;
}

/**
* Return a new instance of a compiled class of the given type. There must
* be only a single instance and it must have a default constructor.
* @param <T> the required type
* @param type the required type
* @return an instance of type created from the compiled classes
* @throws IllegalStateException if no instance can be found or instantiated
*/
public <T> T getInstance(Class<T> type) {
List<Class<?>> matching = getAllCompiledClasses().stream().filter(type::isAssignableFrom).toList();
Assert.state(!matching.isEmpty(), () -> "No instance found of type " + type.getName());
Assert.state(matching.size() == 1, () -> "Multiple instances found of type " + type.getName());
return newInstance(matching.get(0));
}

/**
* Return an instance of a compiled class identified by its class name. The
* class must have a default constructor.
* @param <T> the type to return
* @param type the type to return
* @param className the class name to load
* @return an instance of the class
* @throws IllegalStateException if no instance can be found or instantiated
*/
public <T> T getInstance(Class<T> type, String className) {
Class<?> loaded = loadClass(className);
return newInstance(loaded);
}

/**
* Return all compiled classes.
* @return a list of all compiled classes
*/
public List<Class<?>> getAllCompiledClasses() {
List<Class<?>> compiledClasses = this.compiledClasses;
if (compiledClasses == null) {
compiledClasses = new ArrayList<>();
this.sourceFiles.stream().map(this::loadClass).forEach(compiledClasses::add);
this.compiledClasses = Collections.unmodifiableList(compiledClasses);
}
return compiledClasses;
}

@SuppressWarnings("unchecked")
private <T> T newInstance(Class<?> loaded) {
try {
Constructor<?> constructor = loaded.getDeclaredConstructor();
return (T) constructor.newInstance();
}
catch (Exception ex) {
throw new IllegalStateException(ex);
}
}

private Class<?> loadClass(SourceFile sourceFile) {
return loadClass(sourceFile.getClassName());
}

private Class<?> loadClass(String className) {
try {
return this.classLoader.loadClass(className);
}
catch (ClassNotFoundException ex) {
throw new IllegalStateException(ex);
}
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
/*
* 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.test.generator.compile;

import java.io.ByteArrayOutputStream;
import java.io.OutputStream;
import java.net.URI;

import javax.tools.JavaFileObject;
import javax.tools.SimpleJavaFileObject;

/**
* In-memory {@link JavaFileObject} used to hold class bytecode.
*
* @author Phillip Webb
* @since 6.0
*/
class DynamicClassFileObject extends SimpleJavaFileObject {

private volatile byte[] bytes = new byte[0];


DynamicClassFileObject(String className) {
super(URI.create("class:///" + className.replace('.', '/') + ".class"),
Kind.CLASS);
}


@Override
public OutputStream openOutputStream() {
return new JavaClassOutputStream();
}

byte[] getBytes() {
return this.bytes;
}


class JavaClassOutputStream extends ByteArrayOutputStream {

@Override
public void close() {
DynamicClassFileObject.this.bytes = toByteArray();
}

}

}
Loading

0 comments on commit 30cd14d

Please sign in to comment.