Polish metadata annotation processor’s incremental build support

The main change in this commit is to introduce a new BuildHandler
abstraction. A BuildHandler is responsible for producing the metadata
for a build. Two implementations are provided; one for standard builds
and one for incremental builds. This change means that the annotation
processor is no longer concerned with the two different build types
and can use the same logic in each case.

The code for reading and writing metadata files has also been moved
out into a separate class, MetadataStore, to allow it to be easily
utilised from multiple places.

Closes gh-2313
pull/2400/merge
Andy Wilkinson 10 years ago
parent 8df43a8a79
commit 23c175f674

@ -13,35 +13,30 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.boot.configurationprocessor;
import java.util.Set;
import javax.annotation.processing.RoundEnvironment;
import org.springframework.boot.configurationprocessor.metadata.ConfigurationMetadata;
/**
* Data object containing information about a finished build.
* A {@code BuildTracker} tracks a build in which configuration processing has been
* performed and is responsible for managing the associated state including the resulting
* metadata.
*
* @author Kris De Volder
* @author Andy Wilkinson
*/
public class BuildResult {
public final ConfigurationMetadata metadata;
public interface BuildHandler {
public final Set<String> processedTypes;
void addGroup(String name, String type, String sourceType, String sourceMethod);
public final boolean isIncremental;
void addProperty(String prefix, String name, String type, String sourceType,
String sourceMethod, String description, Object defaultValue,
boolean deprecated);
public BuildResult(boolean isIncremental, ConfigurationMetadata metadata,
Set<String> processedTypes) {
this.isIncremental = isIncremental;
this.metadata = metadata;
this.processedTypes = processedTypes;
}
void processing(RoundEnvironment environment);
public BuildResult(TestConfigurationMetadataAnnotationProcessor processor) {
this(processor.isIncremental(), processor.getMetadata(),
processor.processedSourceTypes);
}
ConfigurationMetadata produceMetadata();
}

@ -16,18 +16,12 @@
package org.springframework.boot.configurationprocessor;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
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;
@ -49,14 +43,10 @@ import javax.lang.model.type.TypeKind;
import javax.lang.model.type.TypeMirror;
import javax.lang.model.util.Elements;
import javax.tools.Diagnostic.Kind;
import javax.tools.FileObject;
import javax.tools.StandardLocation;
import org.springframework.boot.configurationprocessor.fieldvalues.FieldValuesParser;
import org.springframework.boot.configurationprocessor.fieldvalues.javac.JavaCompilerFieldValuesParser;
import org.springframework.boot.configurationprocessor.metadata.ConfigurationMetadata;
import org.springframework.boot.configurationprocessor.metadata.ItemMetadata;
import org.springframework.boot.configurationprocessor.metadata.JsonMarshaller;
/**
* Annotation {@link Processor} that writes meta-data file for
@ -70,8 +60,6 @@ import org.springframework.boot.configurationprocessor.metadata.JsonMarshaller;
@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";
@ -84,22 +72,9 @@ public class ConfigurationMetadataAnnotationProcessor extends AbstractProcessor
static final String LOMBOK_SETTER_ANNOTATION = "lombok.Setter";
private static final String RESOURCES_FOLDER = "resources";
private static final String CLASSES_FOLDER = "classes";
private ConfigurationMetadata metadata;
private MetadataStore metadataStore;
/**
* 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 BuildHandler buildHandler;
private TypeUtils typeUtils;
@ -123,12 +98,9 @@ public class ConfigurationMetadataAnnotationProcessor extends AbstractProcessor
@Override
public synchronized void init(ProcessingEnvironment env) {
super.init(env);
this.metadata = new ConfigurationMetadata();
this.typeUtils = new TypeUtils(env);
this.oldmetadata = readMetadata();
if (isIncremental()) {
this.processedSourceTypes = new HashSet<String>();
}
this.metadataStore = new MetadataStore(env);
this.buildHandler = createBuildHandler(env, this.metadataStore);
try {
this.fieldValuesParser = new JavaCompilerFieldValuesParser(env);
}
@ -139,43 +111,28 @@ 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));
private BuildHandler createBuildHandler(ProcessingEnvironment env,
MetadataStore metadataStore) {
ConfigurationMetadata existingMetadata = metadataStore.readMetadata();
if (existingMetadata != null) {
return new IncrementalBuildHandler(env, existingMetadata);
}
else {
return new StandardBuildHandler();
}
}
@Override
public boolean process(Set<? extends TypeElement> annotations,
RoundEnvironment roundEnv) {
this.buildHandler.processing(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);
}
if (roundEnv.processingOver()) {
writeMetaData(this.metadata);
writeMetaData();
}
return false;
}
@ -196,7 +153,7 @@ public class ConfigurationMetadataAnnotationProcessor extends AbstractProcessor
private void processAnnotatedTypeElement(String prefix, TypeElement element) {
String type = this.typeUtils.getType(element);
this.metadata.add(ItemMetadata.newGroup(prefix, type, type, null));
this.buildHandler.addGroup(prefix, type, type, null);
processTypeElement(prefix, element);
}
@ -206,10 +163,9 @@ public class ConfigurationMetadataAnnotationProcessor extends AbstractProcessor
Element returns = this.processingEnv.getTypeUtils().asElement(
element.getReturnType());
if (returns instanceof TypeElement) {
this.metadata.add(ItemMetadata.newGroup(prefix,
this.typeUtils.getType(returns),
this.buildHandler.addGroup(prefix, this.typeUtils.getType(returns),
this.typeUtils.getType(element.getEnclosingElement()),
element.toString()));
element.toString());
processTypeElement(prefix, (TypeElement) returns);
}
}
@ -254,8 +210,8 @@ public class ConfigurationMetadataAnnotationProcessor extends AbstractProcessor
boolean deprecated = hasDeprecateAnnotation(getter)
|| hasDeprecateAnnotation(setter)
|| hasDeprecateAnnotation(element);
this.metadata.add(ItemMetadata.newProperty(prefix, name, dataType,
sourceType, null, description, defaultValue, deprecated));
this.buildHandler.addProperty(prefix, name, dataType, sourceType, null,
description, defaultValue, deprecated);
}
}
}
@ -282,8 +238,8 @@ public class ConfigurationMetadataAnnotationProcessor extends AbstractProcessor
Object defaultValue = fieldValues.get(name);
boolean deprecated = hasDeprecateAnnotation(field)
|| hasDeprecateAnnotation(element);
this.metadata.add(ItemMetadata.newProperty(prefix, name, dataType,
sourceType, null, description, defaultValue, deprecated));
this.buildHandler.addProperty(prefix, name, dataType, sourceType, null,
description, defaultValue, deprecated);
}
}
}
@ -316,9 +272,9 @@ public class ConfigurationMetadataAnnotationProcessor extends AbstractProcessor
if (returnType != null && returnType instanceof TypeElement
&& annotation == null && isNested) {
String nestedPrefix = ConfigurationMetadata.nestedPrefix(prefix, name);
this.metadata.add(ItemMetadata.newGroup(nestedPrefix,
this.buildHandler.addGroup(nestedPrefix,
this.typeUtils.getType(returnType),
this.typeUtils.getType(element), getter.toString()));
this.typeUtils.getType(element), getter.toString());
processTypeElement(nestedPrefix, (TypeElement) returnType);
}
}
@ -375,89 +331,33 @@ public class ConfigurationMetadataAnnotationProcessor extends AbstractProcessor
return values;
}
protected ConfigurationMetadata writeMetaData(ConfigurationMetadata metadata) {
protected ConfigurationMetadata writeMetaData() {
ConfigurationMetadata metadata = this.buildHandler.produceMetadata();
metadata = mergeAdditionalMetadata(metadata);
if (isIncremental()) {
mergeOldMetadata(metadata);
}
if (!metadata.getItems().isEmpty()) {
try {
FileObject resource = this.processingEnv.getFiler().createResource(
StandardLocation.CLASS_OUTPUT, "", METADATA_PATH);
OutputStream outputStream = resource.openOutputStream();
try {
new JsonMarshaller().write(metadata, outputStream);
return metadata;
}
finally {
outputStream.close();
}
}
catch (Exception ex) {
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;
}
this.metadataStore.writeMetadata(metadata);
}
finally {
in.close();
catch (IOException ex) {
throw new IllegalStateException("Failed to write metadata", ex);
}
}
catch (IOException e) {
// no 'old' metadata
return 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) {
try {
InputStream inputStream = getAdditionalMetadata();
try {
ConfigurationMetadata merged = new ConfigurationMetadata(metadata);
try {
merged.addAll(new JsonMarshaller().read(inputStream));
}
catch (Exception ex) {
throw new IllegalStateException(ex);
}
return merged;
}
finally {
inputStream.close();
}
ConfigurationMetadata merged = new ConfigurationMetadata(metadata);
merged.addAll(this.metadataStore.readAdditionalMetadata());
return merged;
}
catch (FileNotFoundException ex) {
// No additional metadata
return metadata;
}
catch (Exception ex) {
logWarning("Unable to merge additional-spring-configuration-metadata.json");
logWarning("Unable to merge additional metadata");
logWarning(getStackTrace(ex));
return metadata;
}
@ -469,26 +369,6 @@ public class ConfigurationMetadataAnnotationProcessor extends AbstractProcessor
return writer.toString();
}
private InputStream getAdditionalMetadata() throws IOException {
// Most build systems will have copied the file to the class output location
FileObject fileObject = this.processingEnv.getFiler().createResource(
StandardLocation.CLASS_OUTPUT, "",
"META-INF/additional-spring-configuration-metadata.json");
File file = new File(fileObject.toUri());
if (!file.exists()) {
// Gradle keeps things separate
String path = file.getPath();
int index = path.lastIndexOf(CLASSES_FOLDER);
if (index >= 0) {
path = path.substring(0, index) + RESOURCES_FOLDER
+ path.substring(index + CLASSES_FOLDER.length());
file = new File(path);
}
}
return (file.exists() ? new FileInputStream(file) : fileObject.toUri().toURL()
.openStream());
}
private void logWarning(String msg) {
this.processingEnv.getMessager().printMessage(Kind.WARNING, msg);
}

@ -0,0 +1,109 @@
/*
* Copyright 2012-2014 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.HashSet;
import java.util.List;
import java.util.Set;
import javax.annotation.processing.ProcessingEnvironment;
import javax.annotation.processing.RoundEnvironment;
import javax.lang.model.element.Element;
import javax.lang.model.element.TypeElement;
import org.springframework.boot.configurationprocessor.metadata.ConfigurationMetadata;
import org.springframework.boot.configurationprocessor.metadata.ItemMetadata;
/**
* {@code BuildHandler} that provides incremental build support by merging the metadata
* from the current incremental build with any existing metadata.
*
* @author Andy Wilkinson
* @author Kris De Volder
* @since 1.2.2
*/
public class IncrementalBuildHandler extends StandardBuildHandler {
private final Set<String> processedSourceTypes = new HashSet<String>();
private final ProcessingEnvironment processingEnvironment;
private final ConfigurationMetadata existingMetadata;
private final TypeUtils typeUtils;
/**
* Creates a new {@code IncrementalBuildTracker} that will merge the metadata produced
* by an incremental build with the given {@code existingMetadata}.
*
* @param processingEnvironment The processing environment of the build
* @param existingMetadata The existing metadata
*/
public IncrementalBuildHandler(ProcessingEnvironment processingEnvironment,
ConfigurationMetadata existingMetadata) {
this.existingMetadata = existingMetadata;
this.processingEnvironment = processingEnvironment;
this.typeUtils = new TypeUtils(processingEnvironment);
}
@Override
public void processing(RoundEnvironment environment) {
for (Element element : environment.getRootElements()) {
markAsProcessed(element);
}
}
@Override
public ConfigurationMetadata produceMetadata() {
ConfigurationMetadata metadata = super.produceMetadata();
mergeExistingMetadata(metadata);
return metadata;
}
private void markAsProcessed(Element element) {
if (element instanceof TypeElement) {
this.processedSourceTypes.add(this.typeUtils.getType(element));
}
}
private void mergeExistingMetadata(ConfigurationMetadata metadata) {
List<ItemMetadata> items = this.existingMetadata.getItems();
for (ItemMetadata oldItem : items) {
if (shouldBeMerged(oldItem)) {
metadata.add(oldItem);
}
}
}
private boolean shouldBeMerged(ItemMetadata itemMetadata) {
String sourceType = itemMetadata.getSourceType();
if (sourceType == null || deletedInCurrentBuild(sourceType)
|| processedInCurrentBuild(sourceType)) {
return false;
}
return true;
}
private boolean deletedInCurrentBuild(String sourceType) {
return this.processingEnvironment.getElementUtils().getTypeElement(sourceType) == null;
}
private boolean processedInCurrentBuild(String sourceType) {
return this.processedSourceTypes.contains(sourceType);
}
}

@ -0,0 +1,122 @@
/*
* 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.io.InputStream;
import java.io.OutputStream;
import javax.annotation.processing.ProcessingEnvironment;
import javax.tools.FileObject;
import javax.tools.StandardLocation;
import org.springframework.boot.configurationprocessor.metadata.ConfigurationMetadata;
import org.springframework.boot.configurationprocessor.metadata.JsonMarshaller;
/**
* A {@code MetadataStore} is responsible for the storage of metadata on the filesystem
*
* @author Andy Wilkinson
* @since 1.2.2
*/
public class MetadataStore {
static final String METADATA_PATH = "META-INF/spring-configuration-metadata.json";
private static final String ADDITIONAL_METADATA_PATH = "META-INF/additional-spring-configuration-metadata.json";
private static final String RESOURCES_FOLDER = "resources";
private static final String CLASSES_FOLDER = "classes";
private final ProcessingEnvironment environment;
public MetadataStore(ProcessingEnvironment environment) {
this.environment = environment;
}
public ConfigurationMetadata readMetadata() {
try {
return readMetadata(getMetadataResource().openInputStream());
}
catch (IOException ex) {
return null;
}
}
public void writeMetadata(ConfigurationMetadata metadata) throws IOException {
if (!metadata.getItems().isEmpty()) {
OutputStream outputStream = createMetadataResource().openOutputStream();
try {
new JsonMarshaller().write(metadata, outputStream);
}
finally {
outputStream.close();
}
}
}
public ConfigurationMetadata readAdditionalMetadata() throws IOException {
return readMetadata(getAdditionalMetadataStream());
}
private ConfigurationMetadata readMetadata(InputStream in) throws IOException {
try {
return new JsonMarshaller().read(in);
}
catch (IOException ex) {
return null;
}
finally {
in.close();
}
}
private FileObject getMetadataResource() throws IOException {
FileObject resource = this.environment.getFiler().getResource(
StandardLocation.CLASS_OUTPUT, "", METADATA_PATH);
return resource;
}
private FileObject createMetadataResource() throws IOException {
FileObject resource = this.environment.getFiler().createResource(
StandardLocation.CLASS_OUTPUT, "", METADATA_PATH);
return resource;
}
private InputStream getAdditionalMetadataStream() throws IOException {
// Most build systems will have copied the file to the class output location
FileObject fileObject = this.environment.getFiler().getResource(
StandardLocation.CLASS_OUTPUT, "", ADDITIONAL_METADATA_PATH);
File file = new File(fileObject.toUri());
if (!file.exists()) {
// Gradle keeps things separate
String path = file.getPath();
int index = path.lastIndexOf(CLASSES_FOLDER);
if (index >= 0) {
path = path.substring(0, index) + RESOURCES_FOLDER
+ path.substring(index + CLASSES_FOLDER.length());
file = new File(path);
}
}
return (file.exists() ? new FileInputStream(file) : fileObject.toUri().toURL()
.openStream());
}
}

@ -0,0 +1,58 @@
/*
* Copyright 2012-2014 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 javax.annotation.processing.RoundEnvironment;
import org.springframework.boot.configurationprocessor.metadata.ConfigurationMetadata;
import org.springframework.boot.configurationprocessor.metadata.ItemMetadata;
/**
* Standard implementation of {@code BuildHandler} that handles the state of a single
* build.
*
* @author Andy Wilkinson
* @since 1.2.2
*/
public class StandardBuildHandler implements BuildHandler {
private final ConfigurationMetadata metadata = new ConfigurationMetadata();
@Override
public void addGroup(String name, String type, String sourceType, String sourceMethod) {
this.metadata.add(ItemMetadata.newGroup(name, type, sourceType, sourceMethod));
}
@Override
public void addProperty(String prefix, String name, String type, String sourceType,
String sourceMethod, String description, Object defaultValue,
boolean deprecated) {
this.metadata.add(ItemMetadata.newProperty(prefix, name, type, sourceType,
sourceMethod, description, defaultValue, deprecated));
}
@Override
public void processing(RoundEnvironment environment) {
}
@Override
public ConfigurationMetadata produceMetadata() {
return this.metadata;
}
}

@ -57,9 +57,9 @@ 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;
import static org.springframework.boot.configurationprocessor.MetadataStore.METADATA_PATH;
/**
* Tests for {@link ConfigurationMetadataAnnotationProcessor}.
@ -81,92 +81,6 @@ 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);
@ -436,7 +350,80 @@ public class ConfigurationMetadataAnnotationProcessorTests {
assertThat(metadata,
containsProperty("foo", String.class)
.fromSource(AdditionalMetadata.class));
}
@Test
public void incrementalBuild() throws Exception {
TestProject project = new TestProject(this.temporaryFolder, FooProperties.class,
BarProperties.class);
assertFalse(project.getOutputFile(METADATA_PATH).exists());
ConfigurationMetadata metadata = project.fullBuild();
assertTrue(project.getOutputFile(METADATA_PATH).exists());
assertThat(metadata,
containsProperty("foo.counter").fromSource(FooProperties.class));
assertThat(metadata,
containsProperty("bar.counter").fromSource(BarProperties.class));
metadata = project.incrementalBuild(BarProperties.class);
assertThat(metadata,
containsProperty("foo.counter").fromSource(FooProperties.class));
assertThat(metadata,
containsProperty("bar.counter").fromSource(BarProperties.class));
project.addSourceCode(BarProperties.class,
BarProperties.class.getResourceAsStream("BarProperties.snippet"));
metadata = project.incrementalBuild(BarProperties.class);
assertThat(metadata, containsProperty("bar.extra"));
assertThat(metadata, containsProperty("foo.counter"));
assertThat(metadata, containsProperty("bar.counter"));
project.revert(BarProperties.class);
metadata = project.incrementalBuild(BarProperties.class);
assertThat(metadata, not(containsProperty("bar.extra")));
assertThat(metadata, containsProperty("foo.counter"));
assertThat(metadata, containsProperty("bar.counter"));
}
@Test
public void incremenalBuildAnnotationRemoved() throws Exception {
TestProject project = new TestProject(this.temporaryFolder, FooProperties.class,
BarProperties.class);
ConfigurationMetadata metadata = project.fullBuild();
assertThat(metadata, containsProperty("foo.counter"));
assertThat(metadata, containsProperty("bar.counter"));
project.replaceText(BarProperties.class, "@ConfigurationProperties",
"//@ConfigurationProperties");
metadata = project.incrementalBuild(BarProperties.class);
assertThat(metadata, containsProperty("foo.counter"));
assertThat(metadata, not(containsProperty("bar.counter")));
}
@Test
public void incremenalBuildTypeRenamed() throws Exception {
TestProject project = new TestProject(this.temporaryFolder, FooProperties.class,
BarProperties.class);
ConfigurationMetadata metadata = project.fullBuild();
assertThat(metadata,
containsProperty("foo.counter").fromSource(FooProperties.class));
assertThat(metadata,
containsProperty("bar.counter").fromSource(BarProperties.class));
assertThat(metadata,
not(containsProperty("bar.counter")
.fromSource(RenamedBarProperties.class)));
project.delete(BarProperties.class);
project.add(RenamedBarProperties.class);
metadata = project.incrementalBuild(RenamedBarProperties.class);
assertThat(metadata,
containsProperty("foo.counter").fromSource(FooProperties.class));
assertThat(metadata,
not(containsProperty("bar.counter").fromSource(BarProperties.class)));
assertThat(metadata,
containsProperty("bar.counter").fromSource(RenamedBarProperties.class));
}
private void assertSimpleLombokProperties(ConfigurationMetadata metadata,

@ -18,7 +18,6 @@ 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;
@ -61,8 +60,8 @@ public class TestConfigurationMetadataAnnotationProcessor extends
}
@Override
protected ConfigurationMetadata writeMetaData(ConfigurationMetadata metadata) {
super.writeMetaData(metadata);
protected ConfigurationMetadata writeMetaData() {
super.writeMetaData();
try {
File metadataFile = new File(this.outputLocation,
"META-INF/spring-configuration-metadata.json");
@ -71,7 +70,7 @@ public class TestConfigurationMetadataAnnotationProcessor extends
metadataFile));
}
else {
this.metadata = metadata;
this.metadata = new ConfigurationMetadata();
}
return this.metadata;
}
@ -84,12 +83,4 @@ public class TestConfigurationMetadataAnnotationProcessor extends
return this.metadata;
}
public Set<String> getProcessedTypes() {
return this.processedSourceTypes;
}
@Override
public boolean isIncremental() {
return super.isIncremental();
}
}

@ -20,6 +20,8 @@ import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.StringReader;
import java.io.UnsupportedEncodingException;
import java.util.Arrays;
@ -30,9 +32,9 @@ import java.util.Set;
import org.junit.Assert;
import org.junit.rules.TemporaryFolder;
import org.springframework.boot.configurationprocessor.TestCompiler.TestCompilationTask;
import org.springframework.boot.configurationprocessor.metadata.ConfigurationMetadata;
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;
@ -94,21 +96,21 @@ public class TestProject {
return new File(this.sourceFolder, sourcePathFor(klass));
}
public BuildResult fullBuild() {
public ConfigurationMetadata 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);
return processor.getMetadata();
}
public BuildResult incrementalBuild(Class<?>... toRecompile) {
public ConfigurationMetadata incrementalBuild(Class<?>... toRecompile) {
TestConfigurationMetadataAnnotationProcessor processor = new TestConfigurationMetadataAnnotationProcessor(
this.compiler.getOutputLocation());
TestCompilationTask task = this.compiler.getTask(toRecompile);
task.call(processor);
return new BuildResult(processor);
return processor.getMetadata();
}
private void deleteFolderContents(File outputFolder) {
@ -127,11 +129,15 @@ public class TestProject {
/**
* Add source code at the end of file, just before last '}'
*/
public void addSourceCode(Class<BarProperties> target, String text) throws Exception {
public void addSourceCode(Class<?> target, InputStream snippetStream)
throws Exception {
File targetFile = getSourceFile(target);
String contents = getContents(targetFile);
int insertAt = contents.lastIndexOf('}');
contents = contents.substring(0, insertAt) + text + contents.substring(insertAt);
String additionalSource = FileCopyUtils.copyToString(new InputStreamReader(
snippetStream));
contents = contents.substring(0, insertAt) + additionalSource
+ contents.substring(insertAt);
putContents(targetFile, contents);
}

@ -0,0 +1,9 @@
private String extra;
public String getExtra() {
return extra;
}
public void setExtra(String extra) {
this.extra = extra;
}
Loading…
Cancel
Save