From 8df43a8a791c5fe87e478f1cae5fecbb60ba0095 Mon Sep 17 00:00:00 2001 From: Kris De Volder Date: Fri, 9 Jan 2015 10:37:51 -0800 Subject: [PATCH 1/2] 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 --- .../pom.xml | 6 +- ...figurationMetadataAnnotationProcessor.java | 92 ++++++++- .../configurationprocessor/BuildResult.java | 47 +++++ ...ationMetadataAnnotationProcessorTests.java | 150 ++++++++------ .../configurationprocessor/TestCompiler.java | 25 ++- ...figurationMetadataAnnotationProcessor.java | 95 +++++++++ .../configurationprocessor/TestProject.java | 187 ++++++++++++++++++ .../incremental/BarProperties.java | 56 ++++++ .../incremental/FooProperties.java | 57 ++++++ .../incremental/RenamedBarProperties.java | 56 ++++++ 10 files changed, 710 insertions(+), 61 deletions(-) create mode 100644 spring-boot-tools/spring-boot-configuration-processor/src/test/java/org/springframework/boot/configurationprocessor/BuildResult.java create mode 100644 spring-boot-tools/spring-boot-configuration-processor/src/test/java/org/springframework/boot/configurationprocessor/TestConfigurationMetadataAnnotationProcessor.java create mode 100644 spring-boot-tools/spring-boot-configuration-processor/src/test/java/org/springframework/boot/configurationprocessor/TestProject.java create mode 100644 spring-boot-tools/spring-boot-configuration-processor/src/test/java/org/springframework/boot/configurationsample/incremental/BarProperties.java create mode 100644 spring-boot-tools/spring-boot-configuration-processor/src/test/java/org/springframework/boot/configurationsample/incremental/FooProperties.java create mode 100644 spring-boot-tools/spring-boot-configuration-processor/src/test/java/org/springframework/boot/configurationsample/incremental/RenamedBarProperties.java diff --git a/spring-boot-tools/spring-boot-configuration-processor/pom.xml b/spring-boot-tools/spring-boot-configuration-processor/pom.xml index c3649bff5c..190cd25e23 100644 --- a/spring-boot-tools/spring-boot-configuration-processor/pom.xml +++ b/spring-boot-tools/spring-boot-configuration-processor/pom.xml @@ -29,6 +29,10 @@ lombok test + + org.springframework + spring-core + @@ -36,7 +40,7 @@ org.apache.maven.plugins maven-compiler-plugin - + none diff --git a/spring-boot-tools/spring-boot-configuration-processor/src/main/java/org/springframework/boot/configurationprocessor/ConfigurationMetadataAnnotationProcessor.java b/spring-boot-tools/spring-boot-configuration-processor/src/main/java/org/springframework/boot/configurationprocessor/ConfigurationMetadataAnnotationProcessor.java index cd8812c02b..ee810139b6 100644 --- a/spring-boot-tools/spring-boot-configuration-processor/src/main/java/org/springframework/boot/configurationprocessor/ConfigurationMetadataAnnotationProcessor.java +++ b/spring-boot-tools/spring-boot-configuration-processor/src/main/java/org/springframework/boot/configurationprocessor/ConfigurationMetadataAnnotationProcessor.java @@ -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; * * @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 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 processedSourceTypes; + private TypeUtils typeUtils; private FieldValuesParser fieldValuesParser; @@ -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(); + } try { this.fieldValuesParser = new JavaCompilerFieldValuesParser(env); } @@ -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 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 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 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 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) { diff --git a/spring-boot-tools/spring-boot-configuration-processor/src/test/java/org/springframework/boot/configurationprocessor/BuildResult.java b/spring-boot-tools/spring-boot-configuration-processor/src/test/java/org/springframework/boot/configurationprocessor/BuildResult.java new file mode 100644 index 0000000000..3fc1e870c7 --- /dev/null +++ b/spring-boot-tools/spring-boot-configuration-processor/src/test/java/org/springframework/boot/configurationprocessor/BuildResult.java @@ -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 processedTypes; + + public final boolean isIncremental; + + public BuildResult(boolean isIncremental, ConfigurationMetadata metadata, + Set processedTypes) { + this.isIncremental = isIncremental; + this.metadata = metadata; + this.processedTypes = processedTypes; + } + + public BuildResult(TestConfigurationMetadataAnnotationProcessor processor) { + this(processor.isIncremental(), processor.getMetadata(), + processor.processedSourceTypes); + } + +} diff --git a/spring-boot-tools/spring-boot-configuration-processor/src/test/java/org/springframework/boot/configurationprocessor/ConfigurationMetadataAnnotationProcessorTests.java b/spring-boot-tools/spring-boot-configuration-processor/src/test/java/org/springframework/boot/configurationprocessor/ConfigurationMetadataAnnotationProcessorTests.java index e898ef8ad4..207a397c6a 100644 --- a/spring-boot-tools/spring-boot-configuration-processor/src/test/java/org/springframework/boot/configurationprocessor/ConfigurationMetadataAnnotationProcessorTests.java +++ b/spring-boot-tools/spring-boot-configuration-processor/src/test/java/org/springframework/boot/configurationprocessor/ConfigurationMetadataAnnotationProcessorTests.java @@ -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; 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; 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 * @author Stephane Nicoll * @author Phillip Webb * @author Andy Wilkinson + * @author Kris De Volder */ 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 { } 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 { } diff --git a/spring-boot-tools/spring-boot-configuration-processor/src/test/java/org/springframework/boot/configurationprocessor/TestCompiler.java b/spring-boot-tools/spring-boot-configuration-processor/src/test/java/org/springframework/boot/configurationprocessor/TestCompiler.java index 871fd88b87..a66b7b7186 100644 --- a/spring-boot-tools/spring-boot-configuration-processor/src/test/java/org/springframework/boot/configurationprocessor/TestCompiler.java +++ b/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; 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; */ 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 { this.fileManager.setLocation(StandardLocation.SOURCE_OUTPUT, temp); } + public TestCompilationTask getTask(Collection sourceFiles) { + Iterable javaFileObjects = this.fileManager + .getJavaFileObjectsFromFiles(sourceFiles); + return getTask(javaFileObjects); + } + public TestCompilationTask getTask(Class... types) { Iterable javaFileObjects = getJavaFileObjects(types); + return getTask(javaFileObjects); + } + + private TestCompilationTask getTask(Iterable javaFileObjects) { return new TestCompilationTask(this.compiler.getTask(null, this.fileManager, null, null, null, javaFileObjects)); } @@ -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; } /** diff --git a/spring-boot-tools/spring-boot-configuration-processor/src/test/java/org/springframework/boot/configurationprocessor/TestConfigurationMetadataAnnotationProcessor.java b/spring-boot-tools/spring-boot-configuration-processor/src/test/java/org/springframework/boot/configurationprocessor/TestConfigurationMetadataAnnotationProcessor.java new file mode 100644 index 0000000000..4964d7506b --- /dev/null +++ b/spring-boot-tools/spring-boot-configuration-processor/src/test/java/org/springframework/boot/configurationprocessor/TestConfigurationMetadataAnnotationProcessor.java @@ -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 getProcessedTypes() { + return this.processedSourceTypes; + } + + @Override + public boolean isIncremental() { + return super.isIncremental(); + } +} \ No newline at end of file diff --git a/spring-boot-tools/spring-boot-configuration-processor/src/test/java/org/springframework/boot/configurationprocessor/TestProject.java b/spring-boot-tools/spring-boot-configuration-processor/src/test/java/org/springframework/boot/configurationprocessor/TestProject.java new file mode 100644 index 0000000000..d17a9b1a87 --- /dev/null +++ b/spring-boot-tools/spring-boot-configuration-processor/src/test/java/org/springframework/boot/configurationprocessor/TestProject.java @@ -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. + *

+ * 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 sourceFiles = new LinkedHashSet(); + + 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> contents = new HashSet>(Arrays.asList(classes)); + contents.addAll(Arrays.asList(ALWAYS_INCLUDE)); + copySources(contents); + } + + private void copySources(Set> 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 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)); + } +} diff --git a/spring-boot-tools/spring-boot-configuration-processor/src/test/java/org/springframework/boot/configurationsample/incremental/BarProperties.java b/spring-boot-tools/spring-boot-configuration-processor/src/test/java/org/springframework/boot/configurationsample/incremental/BarProperties.java new file mode 100644 index 0000000000..f0f143a162 --- /dev/null +++ b/spring-boot-tools/spring-boot-configuration-processor/src/test/java/org/springframework/boot/configurationsample/incremental/BarProperties.java @@ -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; + } +} diff --git a/spring-boot-tools/spring-boot-configuration-processor/src/test/java/org/springframework/boot/configurationsample/incremental/FooProperties.java b/spring-boot-tools/spring-boot-configuration-processor/src/test/java/org/springframework/boot/configurationsample/incremental/FooProperties.java new file mode 100644 index 0000000000..2a046b3e12 --- /dev/null +++ b/spring-boot-tools/spring-boot-configuration-processor/src/test/java/org/springframework/boot/configurationsample/incremental/FooProperties.java @@ -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; + } + +} diff --git a/spring-boot-tools/spring-boot-configuration-processor/src/test/java/org/springframework/boot/configurationsample/incremental/RenamedBarProperties.java b/spring-boot-tools/spring-boot-configuration-processor/src/test/java/org/springframework/boot/configurationsample/incremental/RenamedBarProperties.java new file mode 100644 index 0000000000..8dc73f04e5 --- /dev/null +++ b/spring-boot-tools/spring-boot-configuration-processor/src/test/java/org/springframework/boot/configurationsample/incremental/RenamedBarProperties.java @@ -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; + } +} From 23c175f674e610546753cb9161309144c54573ad Mon Sep 17 00:00:00 2001 From: Andy Wilkinson Date: Wed, 28 Jan 2015 11:10:07 +0000 Subject: [PATCH 2/2] =?UTF-8?q?Polish=20metadata=20annotation=20processor?= =?UTF-8?q?=E2=80=99s=20incremental=20build=20support?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 --- .../configurationprocessor/BuildHandler.java} | 31 ++- ...figurationMetadataAnnotationProcessor.java | 186 ++++-------------- .../IncrementalBuildHandler.java | 109 ++++++++++ .../configurationprocessor/MetadataStore.java | 122 ++++++++++++ .../StandardBuildHandler.java | 58 ++++++ ...ationMetadataAnnotationProcessorTests.java | 161 +++++++-------- ...figurationMetadataAnnotationProcessor.java | 15 +- .../configurationprocessor/TestProject.java | 20 +- .../incremental/BarProperties.snippet | 9 + 9 files changed, 434 insertions(+), 277 deletions(-) rename spring-boot-tools/spring-boot-configuration-processor/src/{test/java/org/springframework/boot/configurationprocessor/BuildResult.java => main/java/org/springframework/boot/configurationprocessor/BuildHandler.java} (54%) create mode 100644 spring-boot-tools/spring-boot-configuration-processor/src/main/java/org/springframework/boot/configurationprocessor/IncrementalBuildHandler.java create mode 100644 spring-boot-tools/spring-boot-configuration-processor/src/main/java/org/springframework/boot/configurationprocessor/MetadataStore.java create mode 100644 spring-boot-tools/spring-boot-configuration-processor/src/main/java/org/springframework/boot/configurationprocessor/StandardBuildHandler.java create mode 100644 spring-boot-tools/spring-boot-configuration-processor/src/test/resources/org/springframework/boot/configurationsample/incremental/BarProperties.snippet diff --git a/spring-boot-tools/spring-boot-configuration-processor/src/test/java/org/springframework/boot/configurationprocessor/BuildResult.java b/spring-boot-tools/spring-boot-configuration-processor/src/main/java/org/springframework/boot/configurationprocessor/BuildHandler.java similarity index 54% rename from spring-boot-tools/spring-boot-configuration-processor/src/test/java/org/springframework/boot/configurationprocessor/BuildResult.java rename to spring-boot-tools/spring-boot-configuration-processor/src/main/java/org/springframework/boot/configurationprocessor/BuildHandler.java index 3fc1e870c7..cae8bdfbde 100644 --- a/spring-boot-tools/spring-boot-configuration-processor/src/test/java/org/springframework/boot/configurationprocessor/BuildResult.java +++ b/spring-boot-tools/spring-boot-configuration-processor/src/main/java/org/springframework/boot/configurationprocessor/BuildHandler.java @@ -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 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 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(); } diff --git a/spring-boot-tools/spring-boot-configuration-processor/src/main/java/org/springframework/boot/configurationprocessor/ConfigurationMetadataAnnotationProcessor.java b/spring-boot-tools/spring-boot-configuration-processor/src/main/java/org/springframework/boot/configurationprocessor/ConfigurationMetadataAnnotationProcessor.java index ee810139b6..32830eae99 100644 --- a/spring-boot-tools/spring-boot-configuration-processor/src/main/java/org/springframework/boot/configurationprocessor/ConfigurationMetadataAnnotationProcessor.java +++ b/spring-boot-tools/spring-boot-configuration-processor/src/main/java/org/springframework/boot/configurationprocessor/ConfigurationMetadataAnnotationProcessor.java @@ -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 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(); - } + 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 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 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); } diff --git a/spring-boot-tools/spring-boot-configuration-processor/src/main/java/org/springframework/boot/configurationprocessor/IncrementalBuildHandler.java b/spring-boot-tools/spring-boot-configuration-processor/src/main/java/org/springframework/boot/configurationprocessor/IncrementalBuildHandler.java new file mode 100644 index 0000000000..e480a667bc --- /dev/null +++ b/spring-boot-tools/spring-boot-configuration-processor/src/main/java/org/springframework/boot/configurationprocessor/IncrementalBuildHandler.java @@ -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 processedSourceTypes = new HashSet(); + + 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 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); + } + +} diff --git a/spring-boot-tools/spring-boot-configuration-processor/src/main/java/org/springframework/boot/configurationprocessor/MetadataStore.java b/spring-boot-tools/spring-boot-configuration-processor/src/main/java/org/springframework/boot/configurationprocessor/MetadataStore.java new file mode 100644 index 0000000000..154b26ffb9 --- /dev/null +++ b/spring-boot-tools/spring-boot-configuration-processor/src/main/java/org/springframework/boot/configurationprocessor/MetadataStore.java @@ -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()); + } + +} diff --git a/spring-boot-tools/spring-boot-configuration-processor/src/main/java/org/springframework/boot/configurationprocessor/StandardBuildHandler.java b/spring-boot-tools/spring-boot-configuration-processor/src/main/java/org/springframework/boot/configurationprocessor/StandardBuildHandler.java new file mode 100644 index 0000000000..7e60315b76 --- /dev/null +++ b/spring-boot-tools/spring-boot-configuration-processor/src/main/java/org/springframework/boot/configurationprocessor/StandardBuildHandler.java @@ -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; + } + +} diff --git a/spring-boot-tools/spring-boot-configuration-processor/src/test/java/org/springframework/boot/configurationprocessor/ConfigurationMetadataAnnotationProcessorTests.java b/spring-boot-tools/spring-boot-configuration-processor/src/test/java/org/springframework/boot/configurationprocessor/ConfigurationMetadataAnnotationProcessorTests.java index 207a397c6a..f843670be1 100644 --- a/spring-boot-tools/spring-boot-configuration-processor/src/test/java/org/springframework/boot/configurationprocessor/ConfigurationMetadataAnnotationProcessorTests.java +++ b/spring-boot-tools/spring-boot-configuration-processor/src/test/java/org/springframework/boot/configurationprocessor/ConfigurationMetadataAnnotationProcessorTests.java @@ -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, diff --git a/spring-boot-tools/spring-boot-configuration-processor/src/test/java/org/springframework/boot/configurationprocessor/TestConfigurationMetadataAnnotationProcessor.java b/spring-boot-tools/spring-boot-configuration-processor/src/test/java/org/springframework/boot/configurationprocessor/TestConfigurationMetadataAnnotationProcessor.java index 4964d7506b..6ed037db89 100644 --- a/spring-boot-tools/spring-boot-configuration-processor/src/test/java/org/springframework/boot/configurationprocessor/TestConfigurationMetadataAnnotationProcessor.java +++ b/spring-boot-tools/spring-boot-configuration-processor/src/test/java/org/springframework/boot/configurationprocessor/TestConfigurationMetadataAnnotationProcessor.java @@ -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 getProcessedTypes() { - return this.processedSourceTypes; - } - - @Override - public boolean isIncremental() { - return super.isIncremental(); - } } \ No newline at end of file diff --git a/spring-boot-tools/spring-boot-configuration-processor/src/test/java/org/springframework/boot/configurationprocessor/TestProject.java b/spring-boot-tools/spring-boot-configuration-processor/src/test/java/org/springframework/boot/configurationprocessor/TestProject.java index d17a9b1a87..23b6eeb578 100644 --- a/spring-boot-tools/spring-boot-configuration-processor/src/test/java/org/springframework/boot/configurationprocessor/TestProject.java +++ b/spring-boot-tools/spring-boot-configuration-processor/src/test/java/org/springframework/boot/configurationprocessor/TestProject.java @@ -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 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); } diff --git a/spring-boot-tools/spring-boot-configuration-processor/src/test/resources/org/springframework/boot/configurationsample/incremental/BarProperties.snippet b/spring-boot-tools/spring-boot-configuration-processor/src/test/resources/org/springframework/boot/configurationsample/incremental/BarProperties.snippet new file mode 100644 index 0000000000..a9f7727aa6 --- /dev/null +++ b/spring-boot-tools/spring-boot-configuration-processor/src/test/resources/org/springframework/boot/configurationsample/incremental/BarProperties.snippet @@ -0,0 +1,9 @@ + private String extra; + + public String getExtra() { + return extra; + } + + public void setExtra(String extra) { + this.extra = extra; + } \ No newline at end of file