Browse Source

Update the metadata annotation processor to support incremental builds

This commit udpdates the metadata annotation processor so that change
data from an incremental build is merged with the metadata from the
previous build.

Closes gh-2321
pull/2400/merge
Kris De Volder 11 years ago committed by Andy Wilkinson
parent
commit
8df43a8a79
  1. 6
      spring-boot-tools/spring-boot-configuration-processor/pom.xml
  2. 92
      spring-boot-tools/spring-boot-configuration-processor/src/main/java/org/springframework/boot/configurationprocessor/ConfigurationMetadataAnnotationProcessor.java
  3. 47
      spring-boot-tools/spring-boot-configuration-processor/src/test/java/org/springframework/boot/configurationprocessor/BuildResult.java
  4. 150
      spring-boot-tools/spring-boot-configuration-processor/src/test/java/org/springframework/boot/configurationprocessor/ConfigurationMetadataAnnotationProcessorTests.java
  5. 25
      spring-boot-tools/spring-boot-configuration-processor/src/test/java/org/springframework/boot/configurationprocessor/TestCompiler.java
  6. 95
      spring-boot-tools/spring-boot-configuration-processor/src/test/java/org/springframework/boot/configurationprocessor/TestConfigurationMetadataAnnotationProcessor.java
  7. 187
      spring-boot-tools/spring-boot-configuration-processor/src/test/java/org/springframework/boot/configurationprocessor/TestProject.java
  8. 56
      spring-boot-tools/spring-boot-configuration-processor/src/test/java/org/springframework/boot/configurationsample/incremental/BarProperties.java
  9. 57
      spring-boot-tools/spring-boot-configuration-processor/src/test/java/org/springframework/boot/configurationsample/incremental/FooProperties.java
  10. 56
      spring-boot-tools/spring-boot-configuration-processor/src/test/java/org/springframework/boot/configurationsample/incremental/RenamedBarProperties.java

6
spring-boot-tools/spring-boot-configuration-processor/pom.xml

@ -29,6 +29,10 @@ @@ -29,6 +29,10 @@
<artifactId>lombok</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-core</artifactId>
</dependency>
</dependencies>
<build>
<plugins>
@ -36,7 +40,7 @@ @@ -36,7 +40,7 @@
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<!-- Ensure own annotation processor doens't kick in -->
<!-- Ensure own annotation processor doesn't kick in -->
<proc>none</proc>
</configuration>
</plugin>

92
spring-boot-tools/spring-boot-configuration-processor/src/main/java/org/springframework/boot/configurationprocessor/ConfigurationMetadataAnnotationProcessor.java

@ -25,7 +25,9 @@ import java.io.OutputStream; @@ -25,7 +25,9 @@ import java.io.OutputStream;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.util.Collections;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
@ -62,11 +64,14 @@ import org.springframework.boot.configurationprocessor.metadata.JsonMarshaller; @@ -62,11 +64,14 @@ import org.springframework.boot.configurationprocessor.metadata.JsonMarshaller;
*
* @author Stephane Nicoll
* @author Phillip Webb
* @author Kris De Volder
* @since 1.2.0
*/
@SupportedAnnotationTypes({ "*" })
public class ConfigurationMetadataAnnotationProcessor extends AbstractProcessor {
public static final String METADATA_PATH = "META-INF/spring-configuration-metadata.json";
static final String CONFIGURATION_PROPERTIES_ANNOTATION = "org.springframework.boot."
+ "context.properties.ConfigurationProperties";
@ -85,6 +90,17 @@ public class ConfigurationMetadataAnnotationProcessor extends AbstractProcessor @@ -85,6 +90,17 @@ public class ConfigurationMetadataAnnotationProcessor extends AbstractProcessor
private ConfigurationMetadata metadata;
/**
* On incremental builds, holds the 'old' metadata (created by the previous build).
*/
private ConfigurationMetadata oldmetadata;
/**
* On incremental builds, keeps track of the types that where presented to the
* processor. This includes types that are not annotated.
*/
protected Set<String> processedSourceTypes;
private TypeUtils typeUtils;
private FieldValuesParser fieldValuesParser;
@ -109,6 +125,10 @@ public class ConfigurationMetadataAnnotationProcessor extends AbstractProcessor @@ -109,6 +125,10 @@ public class ConfigurationMetadataAnnotationProcessor extends AbstractProcessor
super.init(env);
this.metadata = new ConfigurationMetadata();
this.typeUtils = new TypeUtils(env);
this.oldmetadata = readMetadata();
if (isIncremental()) {
this.processedSourceTypes = new HashSet<String>();
}
try {
this.fieldValuesParser = new JavaCompilerFieldValuesParser(env);
}
@ -119,10 +139,37 @@ public class ConfigurationMetadataAnnotationProcessor extends AbstractProcessor @@ -119,10 +139,37 @@ public class ConfigurationMetadataAnnotationProcessor extends AbstractProcessor
}
}
protected boolean isIncremental() {
return this.oldmetadata != null;
}
protected boolean isDeleted(String sourceType) {
return this.processingEnv.getElementUtils().getTypeElement(sourceType) == null;
}
protected boolean isProcessed(String sourceType) {
return this.processedSourceTypes.contains(sourceType);
}
/**
* Called during incremental build on all the 'root elements' that are being presented
* to the processor.
*/
protected void markAsProcessed(Element element) {
if (element instanceof TypeElement) {
this.processedSourceTypes.add(this.typeUtils.getType(element));
}
}
@Override
public boolean process(Set<? extends TypeElement> annotations,
RoundEnvironment roundEnv) {
Elements elementUtils = this.processingEnv.getElementUtils();
if (isIncremental()) {
for (Element element : roundEnv.getRootElements()) {
markAsProcessed(element);
}
}
for (Element element : roundEnv.getElementsAnnotatedWith(elementUtils
.getTypeElement(configurationPropertiesAnnotation()))) {
processElement(element);
@ -328,16 +375,19 @@ public class ConfigurationMetadataAnnotationProcessor extends AbstractProcessor @@ -328,16 +375,19 @@ public class ConfigurationMetadataAnnotationProcessor extends AbstractProcessor
return values;
}
protected void writeMetaData(ConfigurationMetadata metadata) {
protected ConfigurationMetadata writeMetaData(ConfigurationMetadata metadata) {
metadata = mergeAdditionalMetadata(metadata);
if (isIncremental()) {
mergeOldMetadata(metadata);
}
if (!metadata.getItems().isEmpty()) {
try {
FileObject resource = this.processingEnv.getFiler().createResource(
StandardLocation.CLASS_OUTPUT, "",
"META-INF/spring-configuration-metadata.json");
StandardLocation.CLASS_OUTPUT, "", METADATA_PATH);
OutputStream outputStream = resource.openOutputStream();
try {
new JsonMarshaller().write(metadata, outputStream);
return metadata;
}
finally {
outputStream.close();
@ -347,6 +397,42 @@ public class ConfigurationMetadataAnnotationProcessor extends AbstractProcessor @@ -347,6 +397,42 @@ public class ConfigurationMetadataAnnotationProcessor extends AbstractProcessor
throw new IllegalStateException(ex);
}
}
return null;
}
protected ConfigurationMetadata readMetadata() {
try {
FileObject resource = this.processingEnv.getFiler().getResource(
StandardLocation.CLASS_OUTPUT, "", METADATA_PATH);
InputStream in = resource.openInputStream();
try {
ConfigurationMetadata data = new ConfigurationMetadata();
data.addAll(new JsonMarshaller().read(in));
if (!data.getItems().isEmpty()) {
return data;
}
}
finally {
in.close();
}
}
catch (IOException e) {
// no 'old' metadata
}
return null;
}
private void mergeOldMetadata(ConfigurationMetadata metadata) {
List<ItemMetadata> items = this.oldmetadata.getItems();
for (ItemMetadata oldItem : items) {
String sourceType = oldItem.getSourceType();
if (sourceType == null || isProcessed(sourceType) || isDeleted(sourceType)) {
// skip
}
else {
metadata.add(oldItem);
}
}
}
private ConfigurationMetadata mergeAdditionalMetadata(ConfigurationMetadata metadata) {

47
spring-boot-tools/spring-boot-configuration-processor/src/test/java/org/springframework/boot/configurationprocessor/BuildResult.java

@ -0,0 +1,47 @@ @@ -0,0 +1,47 @@
/*
* Copyright 2012-2015 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
*
* http://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.boot.configurationprocessor;
import java.util.Set;
import org.springframework.boot.configurationprocessor.metadata.ConfigurationMetadata;
/**
* Data object containing information about a finished build.
*
* @author Kris De Volder
*/
public class BuildResult {
public final ConfigurationMetadata metadata;
public final Set<String> processedTypes;
public final boolean isIncremental;
public BuildResult(boolean isIncremental, ConfigurationMetadata metadata,
Set<String> processedTypes) {
this.isIncremental = isIncremental;
this.metadata = metadata;
this.processedTypes = processedTypes;
}
public BuildResult(TestConfigurationMetadataAnnotationProcessor processor) {
this(processor.isIncremental(), processor.getMetadata(),
processor.processedSourceTypes);
}
}

150
spring-boot-tools/spring-boot-configuration-processor/src/test/java/org/springframework/boot/configurationprocessor/ConfigurationMetadataAnnotationProcessorTests.java

@ -17,14 +17,9 @@ @@ -17,14 +17,9 @@
package org.springframework.boot.configurationprocessor;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileWriter;
import java.io.IOException;
import javax.annotation.processing.SupportedAnnotationTypes;
import javax.annotation.processing.SupportedSourceVersion;
import javax.lang.model.SourceVersion;
import org.json.JSONArray;
import org.json.JSONObject;
import org.junit.Before;
@ -32,7 +27,9 @@ import org.junit.Rule; @@ -32,7 +27,9 @@ import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.TemporaryFolder;
import org.springframework.boot.configurationprocessor.metadata.ConfigurationMetadata;
import org.springframework.boot.configurationprocessor.metadata.JsonMarshaller;
import org.springframework.boot.configurationsample.incremental.BarProperties;
import org.springframework.boot.configurationsample.incremental.FooProperties;
import org.springframework.boot.configurationsample.incremental.RenamedBarProperties;
import org.springframework.boot.configurationsample.lombok.LombokExplicitProperties;
import org.springframework.boot.configurationsample.lombok.LombokSimpleDataProperties;
import org.springframework.boot.configurationsample.lombok.LombokSimpleProperties;
@ -57,7 +54,10 @@ import static org.hamcrest.CoreMatchers.is; @@ -57,7 +54,10 @@ import static org.hamcrest.CoreMatchers.is;
import static org.hamcrest.Matchers.empty;
import static org.hamcrest.Matchers.equalTo;
import static org.hamcrest.Matchers.not;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertThat;
import static org.junit.Assert.assertTrue;
import static org.springframework.boot.configurationprocessor.ConfigurationMetadataAnnotationProcessor.METADATA_PATH;
import static org.springframework.boot.configurationprocessor.ConfigurationMetadataMatchers.containsGroup;
import static org.springframework.boot.configurationprocessor.ConfigurationMetadataMatchers.containsProperty;
@ -67,6 +67,7 @@ import static org.springframework.boot.configurationprocessor.ConfigurationMetad @@ -67,6 +67,7 @@ import static org.springframework.boot.configurationprocessor.ConfigurationMetad
* @author Stephane Nicoll
* @author Phillip Webb
* @author Andy Wilkinson
* @author Kris De Volder
*/
public class ConfigurationMetadataAnnotationProcessorTests {
@ -80,6 +81,92 @@ public class ConfigurationMetadataAnnotationProcessorTests { @@ -80,6 +81,92 @@ public class ConfigurationMetadataAnnotationProcessorTests {
this.compiler = new TestCompiler(this.temporaryFolder);
}
@Test
public void incrementalBuild() throws Exception {
TestProject project = new TestProject(this.temporaryFolder, FooProperties.class,
BarProperties.class);
assertFalse(project.getOutputFile(METADATA_PATH).exists());
BuildResult r = project.fullBuild();
assertFalse(r.isIncremental);
assertTrue(project.getOutputFile(METADATA_PATH).exists());
assertThat(r.metadata,
containsProperty("foo.counter").fromSource(FooProperties.class));
assertThat(r.metadata,
containsProperty("bar.counter").fromSource(BarProperties.class));
r = project.incrementalBuild(BarProperties.class);
assertTrue(r.isIncremental);
assertTrue(r.processedTypes.contains(BarProperties.class.getName()));
assertFalse(r.processedTypes.contains(FooProperties.class.getName()));
assertThat(r.metadata,
containsProperty("foo.counter").fromSource(FooProperties.class));
assertThat(r.metadata,
containsProperty("bar.counter").fromSource(BarProperties.class));
assertTrue(r.processedTypes.contains(BarProperties.class.getName()));
assertFalse(r.processedTypes.contains(FooProperties.class.getName()));
project.addSourceCode(BarProperties.class, " private String extra;\n" + " \n"
+ " public String getExtra() {\n" + " return extra;\n" + " }\n" + "\n"
+ " public void setExtra(String extra) {\n" + " this.extra = extra;\n"
+ " }\n");
r = project.incrementalBuild(BarProperties.class);
assertTrue(r.isIncremental);
assertThat(r.metadata, containsProperty("bar.extra"));
assertThat(r.metadata, containsProperty("foo.counter"));
assertThat(r.metadata, containsProperty("bar.counter"));
project.revert(BarProperties.class);
r = project.incrementalBuild(BarProperties.class);
assertTrue(r.isIncremental);
assertThat(r.metadata, not(containsProperty("bar.extra")));
assertThat(r.metadata, containsProperty("foo.counter"));
assertThat(r.metadata, containsProperty("bar.counter"));
}
@Test
public void incremenalBuildAnnotationRemoved() throws Exception {
TestProject project = new TestProject(this.temporaryFolder, FooProperties.class,
BarProperties.class);
BuildResult r = project.fullBuild();
assertThat(r.metadata, containsProperty("foo.counter"));
assertThat(r.metadata, containsProperty("bar.counter"));
project.replaceText(BarProperties.class, "@ConfigurationProperties",
"//@ConfigurationProperties");
r = project.incrementalBuild(BarProperties.class);
assertThat(r.metadata, containsProperty("foo.counter"));
assertThat(r.metadata, not(containsProperty("bar.counter")));
}
@Test
public void incremenalBuildTypeRenamed() throws Exception {
TestProject project = new TestProject(this.temporaryFolder, FooProperties.class,
BarProperties.class);
BuildResult r = project.fullBuild();
assertThat(r.metadata,
containsProperty("foo.counter").fromSource(FooProperties.class));
assertThat(r.metadata,
containsProperty("bar.counter").fromSource(BarProperties.class));
assertThat(r.metadata,
not(containsProperty("bar.counter")
.fromSource(RenamedBarProperties.class)));
project.delete(BarProperties.class);
project.add(RenamedBarProperties.class);
r = project.incrementalBuild(RenamedBarProperties.class);
assertThat(r.metadata,
containsProperty("foo.counter").fromSource(FooProperties.class));
assertThat(r.metadata,
not(containsProperty("bar.counter").fromSource(BarProperties.class)));
assertThat(r.metadata,
containsProperty("bar.counter").fromSource(RenamedBarProperties.class));
}
@Test
public void notAnnotated() throws Exception {
ConfigurationMetadata metadata = compile(NotAnnotated.class);
@ -368,59 +455,12 @@ public class ConfigurationMetadataAnnotationProcessorTests { @@ -368,59 +455,12 @@ public class ConfigurationMetadataAnnotationProcessorTests {
}
private ConfigurationMetadata compile(Class<?>... types) throws IOException {
TestConfigurationMetadataAnnotationProcessor processor = new TestConfigurationMetadataAnnotationProcessor();
TestConfigurationMetadataAnnotationProcessor processor = new TestConfigurationMetadataAnnotationProcessor(
this.compiler.getOutputLocation());
this.compiler.getTask(types).call(processor);
return processor.getMetadata();
}
@SupportedAnnotationTypes({ "*" })
@SupportedSourceVersion(SourceVersion.RELEASE_6)
private class TestConfigurationMetadataAnnotationProcessor extends
ConfigurationMetadataAnnotationProcessor {
static final String CONFIGURATION_PROPERTIES_ANNOTATION = "org.springframework.boot.configurationsample.ConfigurationProperties";
static final String NESTED_CONFIGURATION_PROPERTY_ANNOTATION = "org.springframework.boot.configurationsample.NestedConfigurationProperty";
private ConfigurationMetadata metadata;
@Override
protected String configurationPropertiesAnnotation() {
return CONFIGURATION_PROPERTIES_ANNOTATION;
}
@Override
protected String nestedConfigurationPropertyAnnotation() {
return NESTED_CONFIGURATION_PROPERTY_ANNOTATION;
}
@Override
protected void writeMetaData(ConfigurationMetadata metadata) {
super.writeMetaData(metadata);
try {
File metadataFile = new File(
ConfigurationMetadataAnnotationProcessorTests.this.compiler
.getOutputLocation(),
"META-INF/spring-configuration-metadata.json");
if (metadataFile.isFile()) {
this.metadata = new JsonMarshaller().read(new FileInputStream(
metadataFile));
}
else {
this.metadata = metadata;
}
}
catch (IOException e) {
throw new RuntimeException("Failed to read metadata from disk", e);
}
}
public ConfigurationMetadata getMetadata() {
return this.metadata;
}
}
private static class AdditionalMetadata {
}

25
spring-boot-tools/spring-boot-configuration-processor/src/test/java/org/springframework/boot/configurationprocessor/TestCompiler.java

@ -19,6 +19,7 @@ package org.springframework.boot.configurationprocessor; @@ -19,6 +19,7 @@ package org.springframework.boot.configurationprocessor;
import java.io.File;
import java.io.IOException;
import java.util.Arrays;
import java.util.Collection;
import javax.annotation.processing.Processor;
import javax.tools.JavaCompiler;
@ -39,6 +40,8 @@ import org.junit.rules.TemporaryFolder; @@ -39,6 +40,8 @@ import org.junit.rules.TemporaryFolder;
*/
public class TestCompiler {
public static final File ORIGINAL_SOURCE_FOLDER = new File("src/test/java");
private final JavaCompiler compiler;
private final StandardJavaFileManager fileManager;
@ -59,8 +62,18 @@ public class TestCompiler { @@ -59,8 +62,18 @@ public class TestCompiler {
this.fileManager.setLocation(StandardLocation.SOURCE_OUTPUT, temp);
}
public TestCompilationTask getTask(Collection<File> sourceFiles) {
Iterable<? extends JavaFileObject> javaFileObjects = this.fileManager
.getJavaFileObjectsFromFiles(sourceFiles);
return getTask(javaFileObjects);
}
public TestCompilationTask getTask(Class<?>... types) {
Iterable<? extends JavaFileObject> javaFileObjects = getJavaFileObjects(types);
return getTask(javaFileObjects);
}
private TestCompilationTask getTask(Iterable<? extends JavaFileObject> javaFileObjects) {
return new TestCompilationTask(this.compiler.getTask(null, this.fileManager,
null, null, null, javaFileObjects));
}
@ -77,8 +90,16 @@ public class TestCompiler { @@ -77,8 +90,16 @@ public class TestCompiler {
return this.fileManager.getJavaFileObjects(files);
}
private File getFile(Class<?> type) {
return new File("src/test/java/" + type.getName().replace(".", "/") + ".java");
protected File getFile(Class<?> type) {
return new File(getSourceFolder(), sourcePathFor(type));
}
public static String sourcePathFor(Class<?> type) {
return type.getName().replace(".", "/") + ".java";
}
protected File getSourceFolder() {
return ORIGINAL_SOURCE_FOLDER;
}
/**

95
spring-boot-tools/spring-boot-configuration-processor/src/test/java/org/springframework/boot/configurationprocessor/TestConfigurationMetadataAnnotationProcessor.java

@ -0,0 +1,95 @@ @@ -0,0 +1,95 @@
/*
* Copyright 2012-2015 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
*
* http://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.boot.configurationprocessor;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.util.Set;
import javax.annotation.processing.SupportedAnnotationTypes;
import javax.annotation.processing.SupportedSourceVersion;
import javax.lang.model.SourceVersion;
import org.springframework.boot.configurationprocessor.metadata.ConfigurationMetadata;
import org.springframework.boot.configurationprocessor.metadata.JsonMarshaller;
/**
* @author Stephane Nicoll
* @author Phillip Webb
* @author Andy Wilkinson
* @author Kris De Volder
*/
@SupportedAnnotationTypes({ "*" })
@SupportedSourceVersion(SourceVersion.RELEASE_6)
public class TestConfigurationMetadataAnnotationProcessor extends
ConfigurationMetadataAnnotationProcessor {
static final String CONFIGURATION_PROPERTIES_ANNOTATION = "org.springframework.boot.configurationsample.ConfigurationProperties";
static final String NESTED_CONFIGURATION_PROPERTY_ANNOTATION = "org.springframework.boot.configurationsample.NestedConfigurationProperty";
private ConfigurationMetadata metadata;
private final File outputLocation;
public TestConfigurationMetadataAnnotationProcessor(File outputLocation) {
this.outputLocation = outputLocation;
}
@Override
protected String configurationPropertiesAnnotation() {
return CONFIGURATION_PROPERTIES_ANNOTATION;
}
@Override
protected String nestedConfigurationPropertyAnnotation() {
return NESTED_CONFIGURATION_PROPERTY_ANNOTATION;
}
@Override
protected ConfigurationMetadata writeMetaData(ConfigurationMetadata metadata) {
super.writeMetaData(metadata);
try {
File metadataFile = new File(this.outputLocation,
"META-INF/spring-configuration-metadata.json");
if (metadataFile.isFile()) {
this.metadata = new JsonMarshaller().read(new FileInputStream(
metadataFile));
}
else {
this.metadata = metadata;
}
return this.metadata;
}
catch (IOException e) {
throw new RuntimeException("Failed to read metadata from disk", e);
}
}
public ConfigurationMetadata getMetadata() {
return this.metadata;
}
public Set<String> getProcessedTypes() {
return this.processedSourceTypes;
}
@Override
public boolean isIncremental() {
return super.isIncremental();
}
}

187
spring-boot-tools/spring-boot-configuration-processor/src/test/java/org/springframework/boot/configurationprocessor/TestProject.java

@ -0,0 +1,187 @@ @@ -0,0 +1,187 @@
/*
* Copyright 2012-2015 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
*
* http://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.boot.configurationprocessor;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.io.StringReader;
import java.io.UnsupportedEncodingException;
import java.util.Arrays;
import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.Set;
import org.junit.Assert;
import org.junit.rules.TemporaryFolder;
import org.springframework.boot.configurationprocessor.TestCompiler.TestCompilationTask;
import org.springframework.boot.configurationsample.ConfigurationProperties;
import org.springframework.boot.configurationsample.NestedConfigurationProperty;
import org.springframework.boot.configurationsample.incremental.BarProperties;
import org.springframework.util.FileCopyUtils;
import org.springframework.util.FileSystemUtils;
import static org.springframework.boot.configurationprocessor.TestCompiler.ORIGINAL_SOURCE_FOLDER;
import static org.springframework.boot.configurationprocessor.TestCompiler.sourcePathFor;
/**
* A TestProject contains a copy of a subset of test sample code.
* <p>
* Why a copy? Because when doing incremental build testing, we need to make modifications
* to the contents of the 'test project'. But we don't want to actually modify the
* original content itself.
*
* @author Kris De Volder
*/
public class TestProject {
private static final Class<?>[] ALWAYS_INCLUDE = { ConfigurationProperties.class,
NestedConfigurationProperty.class };
/**
* Contains copies of the original source so we can modify it safely to test
* incremental builds.
*/
private File sourceFolder;
private TestCompiler compiler;
private Set<File> sourceFiles = new LinkedHashSet<File>();
public TestProject(TemporaryFolder tempFolder, Class<?>... classes)
throws IOException {
this.sourceFolder = tempFolder.newFolder();
this.compiler = new TestCompiler(tempFolder) {
@Override
protected File getSourceFolder() {
return TestProject.this.sourceFolder;
}
};
Set<Class<?>> contents = new HashSet<Class<?>>(Arrays.asList(classes));
contents.addAll(Arrays.asList(ALWAYS_INCLUDE));
copySources(contents);
}
private void copySources(Set<Class<?>> contents) throws IOException {
for (Class<?> klass : contents) {
copySources(klass);
}
}
private void copySources(Class<?> klass) throws IOException {
File original = getOriginalSourceFile(klass);
File target = getSourceFile(klass);
target.getParentFile().mkdirs();
FileCopyUtils.copy(original, target);
this.sourceFiles.add(target);
}
public File getSourceFile(Class<?> klass) {
return new File(this.sourceFolder, sourcePathFor(klass));
}
public BuildResult fullBuild() {
TestConfigurationMetadataAnnotationProcessor processor = new TestConfigurationMetadataAnnotationProcessor(
this.compiler.getOutputLocation());
TestCompilationTask task = this.compiler.getTask(this.sourceFiles);
deleteFolderContents(this.compiler.getOutputLocation());
task.call(processor);
return new BuildResult(processor);
}
public BuildResult incrementalBuild(Class<?>... toRecompile) {
TestConfigurationMetadataAnnotationProcessor processor = new TestConfigurationMetadataAnnotationProcessor(
this.compiler.getOutputLocation());
TestCompilationTask task = this.compiler.getTask(toRecompile);
task.call(processor);
return new BuildResult(processor);
}
private void deleteFolderContents(File outputFolder) {
FileSystemUtils.deleteRecursively(outputFolder);
outputFolder.mkdirs();
}
/**
* Retrieve File relative to project's output folder.
*/
public File getOutputFile(String relativePath) {
Assert.assertFalse(new File(relativePath).isAbsolute());
return new File(this.compiler.getOutputLocation(), relativePath);
}
/**
* Add source code at the end of file, just before last '}'
*/
public void addSourceCode(Class<BarProperties> target, String text) throws Exception {
File targetFile = getSourceFile(target);
String contents = getContents(targetFile);
int insertAt = contents.lastIndexOf('}');
contents = contents.substring(0, insertAt) + text + contents.substring(insertAt);
putContents(targetFile, contents);
}
/**
* Delete source file for given class from project.
*/
public void delete(Class<?> klass) {
File target = getSourceFile(klass);
target.delete();
this.sourceFiles.remove(target);
}
/**
* Restore source code of given class to its original contents.
*/
public void revert(Class<?> klass) throws IOException {
Assert.assertTrue(getSourceFile(klass).exists());
copySources(klass);
}
/**
* Add source code of given class to this project.
*/
public void add(Class<?> klass) throws IOException {
Assert.assertFalse(getSourceFile(klass).exists());
copySources(klass);
}
public void replaceText(Class<?> klass, String find, String replace) throws Exception {
File target = getSourceFile(klass);
String contents = getContents(target);
contents = contents.replace(find, replace);
putContents(target, contents);
}
/**
* Find the 'original' source code for given test class. Clients or subclasses should
* have no need to know about these. They should work only with the copied source
* code.
*/
private File getOriginalSourceFile(Class<?> klass) {
return new File(ORIGINAL_SOURCE_FOLDER, sourcePathFor(klass));
}
private static void putContents(File targetFile, String contents)
throws FileNotFoundException, IOException, UnsupportedEncodingException {
FileCopyUtils.copy(new StringReader(contents), new FileWriter(targetFile));
}
private static String getContents(File file) throws Exception {
return FileCopyUtils.copyToString(new FileReader(file));
}
}

56
spring-boot-tools/spring-boot-configuration-processor/src/test/java/org/springframework/boot/configurationsample/incremental/BarProperties.java

@ -0,0 +1,56 @@ @@ -0,0 +1,56 @@
/*
* Copyright 2012-2015 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
*
* http://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.boot.configurationsample.incremental;
import org.springframework.boot.configurationsample.ConfigurationProperties;
@ConfigurationProperties("bar")
public class BarProperties {
private String name;
private String description;
/**
* A nice counter description.
*/
private Integer counter = 0;
public String getName() {
return this.name;
}
public void setName(String name) {
this.name = name;
}
public String getDescription() {
return this.description;
}
public void setDescription(String description) {
this.description = description;
}
public Integer getCounter() {
return this.counter;
}
public void setCounter(Integer counter) {
this.counter = counter;
}
}

57
spring-boot-tools/spring-boot-configuration-processor/src/test/java/org/springframework/boot/configurationsample/incremental/FooProperties.java

@ -0,0 +1,57 @@ @@ -0,0 +1,57 @@
/*
* Copyright 2012-2015 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
*
* http://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.boot.configurationsample.incremental;
import org.springframework.boot.configurationsample.ConfigurationProperties;
@ConfigurationProperties("foo")
public class FooProperties {
private String name;
private String description;
/**
* A nice counter description.
*/
private Integer counter = 0;
public String getName() {
return this.name;
}
public void setName(String name) {
this.name = name;
}
public String getDescription() {
return this.description;
}
public void setDescription(String description) {
this.description = description;
}
public Integer getCounter() {
return this.counter;
}
public void setCounter(Integer counter) {
this.counter = counter;
}
}

56
spring-boot-tools/spring-boot-configuration-processor/src/test/java/org/springframework/boot/configurationsample/incremental/RenamedBarProperties.java

@ -0,0 +1,56 @@ @@ -0,0 +1,56 @@
/*
* Copyright 2012-2015 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
*
* http://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.boot.configurationsample.incremental;
import org.springframework.boot.configurationsample.ConfigurationProperties;
@ConfigurationProperties("bar")
public class RenamedBarProperties {
private String name;
private String description;
/**
* A nice counter description.
*/
private Integer counter = 0;
public String getName() {
return this.name;
}
public void setName(String name) {
this.name = name;
}
public String getDescription() {
return this.description;
}
public void setDescription(String description) {
this.description = description;
}
public Integer getCounter() {
return this.counter;
}
public void setCounter(Integer counter) {
this.counter = counter;
}
}
Loading…
Cancel
Save