Skip to content

Commit

Permalink
Polish metadata annotation processor’s incremental build support
Browse files Browse the repository at this point in the history
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
  • Loading branch information
wilkinsona committed Jan 28, 2015
1 parent 8df43a8 commit 23c175f
Show file tree
Hide file tree
Showing 9 changed files with 434 additions and 277 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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();

}
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand All @@ -49,14 +43,10 @@
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
Expand All @@ -70,8 +60,6 @@
@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";

Expand All @@ -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;

Expand All @@ -123,12 +98,9 @@ public SourceVersion getSupportedSourceVersion() {
@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);
}
Expand All @@ -139,43 +111,28 @@ public synchronized void init(ProcessingEnvironment env) {
}
}

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;
}
Expand All @@ -196,7 +153,7 @@ else if (element instanceof ExecutableElement) {

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);
}

Expand All @@ -206,10 +163,9 @@ private void processExecutableElement(String prefix, ExecutableElement element)
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);
}
}
Expand Down Expand Up @@ -254,8 +210,8 @@ private void processSimpleTypes(String prefix, TypeElement element,
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);
}
}
}
Expand All @@ -282,8 +238,8 @@ private void processLombokTypes(String prefix, TypeElement element,
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);
}
}
}
Expand Down Expand Up @@ -316,9 +272,9 @@ private void processNestedTypes(String prefix, TypeElement element,
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);
}
}
Expand Down Expand Up @@ -375,89 +331,33 @@ private Map<String, Object> getAnnotationElementValues(AnnotationMirror annotati
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;
}
Expand All @@ -469,26 +369,6 @@ private String getStackTrace(Exception ex) {
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);
}
Expand Down
Loading

0 comments on commit 23c175f

Please sign in to comment.