Relocate projects to spring-boot-project

Move projects to better reflect the way that Spring Boot is released.

The following projects are under `spring-boot-project`:

  - `spring-boot`
  - `spring-boot-autoconfigure`
  - `spring-boot-tools`
  - `spring-boot-starters`
  - `spring-boot-actuator`
  - `spring-boot-actuator-autoconfigure`
  - `spring-boot-test`
  - `spring-boot-test-autoconfigure`
  - `spring-boot-devtools`
  - `spring-boot-cli`
  - `spring-boot-docs`

See gh-9316
This commit is contained in:
Phillip Webb
2017-09-19 14:29:46 -07:00
parent 0419d42b7c
commit 0ba4830b4f
4023 changed files with 0 additions and 0 deletions

View File

@@ -0,0 +1,579 @@
/*
* Copyright 2012-2017 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.FileNotFoundException;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.util.Arrays;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;
import javax.annotation.processing.AbstractProcessor;
import javax.annotation.processing.ProcessingEnvironment;
import javax.annotation.processing.Processor;
import javax.annotation.processing.RoundEnvironment;
import javax.annotation.processing.SupportedAnnotationTypes;
import javax.lang.model.SourceVersion;
import javax.lang.model.element.AnnotationMirror;
import javax.lang.model.element.AnnotationValue;
import javax.lang.model.element.Element;
import javax.lang.model.element.ElementKind;
import javax.lang.model.element.ExecutableElement;
import javax.lang.model.element.Modifier;
import javax.lang.model.element.TypeElement;
import javax.lang.model.element.VariableElement;
import javax.lang.model.type.TypeKind;
import javax.lang.model.type.TypeMirror;
import javax.lang.model.util.Elements;
import javax.tools.Diagnostic.Kind;
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.InvalidConfigurationMetadataException;
import org.springframework.boot.configurationprocessor.metadata.ItemDeprecation;
import org.springframework.boot.configurationprocessor.metadata.ItemMetadata;
/**
* Annotation {@link Processor} that writes meta-data file for
* {@code @ConfigurationProperties}.
*
* @author Stephane Nicoll
* @author Phillip Webb
* @author Kris De Volder
* @since 1.2.0
*/
@SupportedAnnotationTypes({ "*" })
public class ConfigurationMetadataAnnotationProcessor extends AbstractProcessor {
static final String CONFIGURATION_PROPERTIES_ANNOTATION = "org.springframework.boot."
+ "context.properties.ConfigurationProperties";
static final String NESTED_CONFIGURATION_PROPERTY_ANNOTATION = "org.springframework.boot."
+ "context.properties.NestedConfigurationProperty";
static final String DEPRECATED_CONFIGURATION_PROPERTY_ANNOTATION = "org.springframework.boot."
+ "context.properties.DeprecatedConfigurationProperty";
static final String ENDPOINT_ANNOTATION = "org.springframework.boot.actuate."
+ "endpoint.annotation.Endpoint";
static final String LOMBOK_DATA_ANNOTATION = "lombok.Data";
static final String LOMBOK_GETTER_ANNOTATION = "lombok.Getter";
static final String LOMBOK_SETTER_ANNOTATION = "lombok.Setter";
private MetadataStore metadataStore;
private MetadataCollector metadataCollector;
private TypeUtils typeUtils;
private FieldValuesParser fieldValuesParser;
private TypeExcludeFilter typeExcludeFilter = new TypeExcludeFilter();
protected String configurationPropertiesAnnotation() {
return CONFIGURATION_PROPERTIES_ANNOTATION;
}
protected String nestedConfigurationPropertyAnnotation() {
return NESTED_CONFIGURATION_PROPERTY_ANNOTATION;
}
protected String deprecatedConfigurationPropertyAnnotation() {
return DEPRECATED_CONFIGURATION_PROPERTY_ANNOTATION;
}
protected String endpointAnnotation() {
return ENDPOINT_ANNOTATION;
}
@Override
public SourceVersion getSupportedSourceVersion() {
return SourceVersion.latestSupported();
}
@Override
public synchronized void init(ProcessingEnvironment env) {
super.init(env);
this.typeUtils = new TypeUtils(env);
this.metadataStore = new MetadataStore(env);
this.metadataCollector = new MetadataCollector(env,
this.metadataStore.readMetadata());
try {
this.fieldValuesParser = new JavaCompilerFieldValuesParser(env);
}
catch (Throwable ex) {
this.fieldValuesParser = FieldValuesParser.NONE;
logWarning("Field value processing of @ConfigurationProperty meta-data is "
+ "not supported");
}
}
@Override
public boolean process(Set<? extends TypeElement> annotations,
RoundEnvironment roundEnv) {
this.metadataCollector.processing(roundEnv);
Elements elementUtils = this.processingEnv.getElementUtils();
TypeElement annotationType = elementUtils
.getTypeElement(configurationPropertiesAnnotation());
if (annotationType != null) { // Is @ConfigurationProperties available
for (Element element : roundEnv.getElementsAnnotatedWith(annotationType)) {
processElement(element);
}
}
TypeElement endpointType = elementUtils.getTypeElement(endpointAnnotation());
if (endpointType != null) { // Is @Endpoint available
for (Element element : roundEnv.getElementsAnnotatedWith(endpointType)) {
processEndpoint(element);
}
}
if (roundEnv.processingOver()) {
try {
writeMetaData();
}
catch (Exception ex) {
throw new IllegalStateException("Failed to write metadata", ex);
}
}
return false;
}
private void processElement(Element element) {
try {
AnnotationMirror annotation = getAnnotation(element,
configurationPropertiesAnnotation());
if (annotation != null) {
String prefix = getPrefix(annotation);
if (element instanceof TypeElement) {
processAnnotatedTypeElement(prefix, (TypeElement) element);
}
else if (element instanceof ExecutableElement) {
processExecutableElement(prefix, (ExecutableElement) element);
}
}
}
catch (Exception ex) {
throw new IllegalStateException(
"Error processing configuration meta-data on " + element, ex);
}
}
private void processAnnotatedTypeElement(String prefix, TypeElement element) {
String type = this.typeUtils.getQualifiedName(element);
this.metadataCollector.add(ItemMetadata.newGroup(prefix, type, type, null));
processTypeElement(prefix, element, null);
}
private void processExecutableElement(String prefix, ExecutableElement element) {
if (element.getModifiers().contains(Modifier.PUBLIC)
&& (TypeKind.VOID != element.getReturnType().getKind())) {
Element returns = this.processingEnv.getTypeUtils()
.asElement(element.getReturnType());
if (returns instanceof TypeElement) {
ItemMetadata group = ItemMetadata.newGroup(prefix,
this.typeUtils.getQualifiedName(returns),
this.typeUtils.getQualifiedName(element.getEnclosingElement()),
element.toString());
if (this.metadataCollector.hasSimilarGroup(group)) {
this.processingEnv.getMessager().printMessage(Kind.ERROR,
"Duplicate `@ConfigurationProperties` definition for prefix '"
+ prefix + "'",
element);
}
else {
this.metadataCollector.add(group);
processTypeElement(prefix, (TypeElement) returns, element);
}
}
}
}
private void processTypeElement(String prefix, TypeElement element,
ExecutableElement source) {
TypeElementMembers members = new TypeElementMembers(this.processingEnv,
this.fieldValuesParser, element);
Map<String, Object> fieldValues = members.getFieldValues();
processSimpleTypes(prefix, element, source, members, fieldValues);
processSimpleLombokTypes(prefix, element, source, members, fieldValues);
processNestedTypes(prefix, element, source, members);
processNestedLombokTypes(prefix, element, source, members);
}
private void processSimpleTypes(String prefix, TypeElement element,
ExecutableElement source, TypeElementMembers members,
Map<String, Object> fieldValues) {
for (Map.Entry<String, ExecutableElement> entry : members.getPublicGetters()
.entrySet()) {
String name = entry.getKey();
ExecutableElement getter = entry.getValue();
TypeMirror returnType = getter.getReturnType();
ExecutableElement setter = members.getPublicSetter(name, returnType);
VariableElement field = members.getFields().get(name);
Element returnTypeElement = this.processingEnv.getTypeUtils()
.asElement(returnType);
boolean isExcluded = this.typeExcludeFilter.isExcluded(returnType);
boolean isNested = isNested(returnTypeElement, field, element);
boolean isCollection = this.typeUtils.isCollectionOrMap(returnType);
if (!isExcluded && !isNested && (setter != null || isCollection)) {
String dataType = this.typeUtils.getType(returnType);
String sourceType = this.typeUtils.getQualifiedName(element);
String description = this.typeUtils.getJavaDoc(field);
Object defaultValue = fieldValues.get(name);
boolean deprecated = isDeprecated(getter) || isDeprecated(setter)
|| isDeprecated(source);
this.metadataCollector.add(ItemMetadata.newProperty(prefix, name,
dataType, sourceType, null, description, defaultValue,
(deprecated ? getItemDeprecation(getter) : null)));
}
}
}
private ItemDeprecation getItemDeprecation(ExecutableElement getter) {
AnnotationMirror annotation = getAnnotation(getter,
deprecatedConfigurationPropertyAnnotation());
String reason = null;
String replacement = null;
if (annotation != null) {
Map<String, Object> elementValues = getAnnotationElementValues(annotation);
reason = (String) elementValues.get("reason");
replacement = (String) elementValues.get("replacement");
}
return new ItemDeprecation(("".equals(reason) ? null : reason),
("".equals(replacement) ? null : replacement));
}
private void processSimpleLombokTypes(String prefix, TypeElement element,
ExecutableElement source, TypeElementMembers members,
Map<String, Object> fieldValues) {
for (Map.Entry<String, VariableElement> entry : members.getFields().entrySet()) {
String name = entry.getKey();
VariableElement field = entry.getValue();
if (!isLombokField(field, element)) {
continue;
}
TypeMirror returnType = field.asType();
Element returnTypeElement = this.processingEnv.getTypeUtils()
.asElement(returnType);
boolean isExcluded = this.typeExcludeFilter.isExcluded(returnType);
boolean isNested = isNested(returnTypeElement, field, element);
boolean isCollection = this.typeUtils.isCollectionOrMap(returnType);
boolean hasSetter = hasLombokSetter(field, element);
if (!isExcluded && !isNested && (hasSetter || isCollection)) {
String dataType = this.typeUtils.getType(returnType);
String sourceType = this.typeUtils.getQualifiedName(element);
String description = this.typeUtils.getJavaDoc(field);
Object defaultValue = fieldValues.get(name);
boolean deprecated = isDeprecated(field) || isDeprecated(source);
this.metadataCollector.add(ItemMetadata.newProperty(prefix, name,
dataType, sourceType, null, description, defaultValue,
(deprecated ? new ItemDeprecation() : null)));
}
}
}
private void processNestedTypes(String prefix, TypeElement element,
ExecutableElement source, TypeElementMembers members) {
for (Map.Entry<String, ExecutableElement> entry : members.getPublicGetters()
.entrySet()) {
String name = entry.getKey();
ExecutableElement getter = entry.getValue();
VariableElement field = members.getFields().get(name);
processNestedType(prefix, element, source, name, getter, field,
getter.getReturnType());
}
}
private void processNestedLombokTypes(String prefix, TypeElement element,
ExecutableElement source, TypeElementMembers members) {
for (Map.Entry<String, VariableElement> entry : members.getFields().entrySet()) {
String name = entry.getKey();
VariableElement field = entry.getValue();
if (isLombokField(field, element)) {
ExecutableElement getter = members.getPublicGetter(name, field.asType());
processNestedType(prefix, element, source, name, getter, field,
field.asType());
}
}
}
private boolean isLombokField(VariableElement field, TypeElement element) {
return hasAnnotation(field, LOMBOK_GETTER_ANNOTATION)
|| hasAnnotation(element, LOMBOK_GETTER_ANNOTATION)
|| hasAnnotation(element, LOMBOK_DATA_ANNOTATION);
}
private boolean hasLombokSetter(VariableElement field, TypeElement element) {
return !field.getModifiers().contains(Modifier.FINAL)
&& (hasAnnotation(field, LOMBOK_SETTER_ANNOTATION)
|| hasAnnotation(element, LOMBOK_SETTER_ANNOTATION)
|| hasAnnotation(element, LOMBOK_DATA_ANNOTATION));
}
private void processNestedType(String prefix, TypeElement element,
ExecutableElement source, String name, ExecutableElement getter,
VariableElement field, TypeMirror returnType) {
Element returnElement = this.processingEnv.getTypeUtils().asElement(returnType);
boolean isNested = isNested(returnElement, field, element);
AnnotationMirror annotation = getAnnotation(getter,
configurationPropertiesAnnotation());
if (returnElement instanceof TypeElement && annotation == null && isNested) {
String nestedPrefix = ConfigurationMetadata.nestedPrefix(prefix, name);
this.metadataCollector.add(ItemMetadata.newGroup(nestedPrefix,
this.typeUtils.getQualifiedName(returnElement),
this.typeUtils.getQualifiedName(element),
(getter == null ? null : getter.toString())));
processTypeElement(nestedPrefix, (TypeElement) returnElement, source);
}
}
private void processEndpoint(Element element) {
try {
AnnotationMirror annotation = getAnnotation(element, endpointAnnotation());
if (element instanceof TypeElement) {
processEndpoint(annotation, (TypeElement) element);
}
}
catch (Exception ex) {
throw new IllegalStateException(
"Error processing configuration meta-data on " + element, ex);
}
}
private void processEndpoint(AnnotationMirror annotation, TypeElement element) {
Map<String, Object> elementValues = getAnnotationElementValues(annotation);
String endpointId = (String) elementValues.get("id");
if (endpointId == null || "".equals(endpointId)) {
return; // Can't process that endpoint
}
Boolean enabledByDefault = determineEnabledByDefault(
elementValues.get("defaultEnablement"));
String type = this.typeUtils.getQualifiedName(element);
this.metadataCollector
.add(ItemMetadata.newGroup(endpointKey(endpointId), type, type, null));
this.metadataCollector.add(ItemMetadata.newProperty(endpointKey(endpointId),
"enabled", Boolean.class.getName(), type, null,
String.format("Enable the %s endpoint.", endpointId), enabledByDefault,
null));
this.metadataCollector.add(ItemMetadata.newProperty(endpointKey(endpointId),
"cache.time-to-live", Long.class.getName(), type, null,
"Maximum time in milliseconds that a response can be cached.", 0, null));
EndpointExposure endpointTypes = EndpointExposure
.parse(elementValues.get("exposure"));
if (endpointTypes.hasJmx()) {
this.metadataCollector.add(ItemMetadata.newProperty(
endpointKey(endpointId + ".jmx"), "enabled", Boolean.class.getName(),
type, null,
String.format("Expose the %s endpoint as a JMX MBean.", endpointId),
enabledByDefault, null));
}
if (endpointTypes.hasWeb()) {
this.metadataCollector.add(ItemMetadata.newProperty(
endpointKey(endpointId + ".web"), "enabled", Boolean.class.getName(),
type, null, String.format("Expose the %s endpoint as a Web endpoint.",
endpointId),
enabledByDefault, null));
}
}
private Boolean determineEnabledByDefault(Object defaultEnablement) {
if (defaultEnablement != null) {
String value = String.valueOf(defaultEnablement);
if ("ENABLED".equals(value)) {
return true;
}
if ("DISABLED".equals(value)) {
return false;
}
}
return null;
}
private String endpointKey(String suffix) {
return "endpoints." + suffix;
}
private boolean isNested(Element returnType, VariableElement field,
TypeElement element) {
if (hasAnnotation(field, nestedConfigurationPropertyAnnotation())) {
return true;
}
return (isParentTheSame(returnType, element))
&& returnType.getKind() != ElementKind.ENUM;
}
private boolean isParentTheSame(Element returnType, TypeElement element) {
if (returnType == null || element == null) {
return false;
}
return getTopLevelType(returnType).equals(getTopLevelType(element));
}
private Element getTopLevelType(Element element) {
if (!(element.getEnclosingElement() instanceof TypeElement)) {
return element;
}
return getTopLevelType(element.getEnclosingElement());
}
private boolean isDeprecated(Element element) {
if (isElementDeprecated(element)) {
return true;
}
if (element instanceof VariableElement || element instanceof ExecutableElement) {
return isElementDeprecated(element.getEnclosingElement());
}
return false;
}
private boolean isElementDeprecated(Element element) {
return hasAnnotation(element, "java.lang.Deprecated")
|| hasAnnotation(element, deprecatedConfigurationPropertyAnnotation());
}
private boolean hasAnnotation(Element element, String type) {
return getAnnotation(element, type) != null;
}
private AnnotationMirror getAnnotation(Element element, String type) {
if (element != null) {
for (AnnotationMirror annotation : element.getAnnotationMirrors()) {
if (type.equals(annotation.getAnnotationType().toString())) {
return annotation;
}
}
}
return null;
}
private String getPrefix(AnnotationMirror annotation) {
Map<String, Object> elementValues = getAnnotationElementValues(annotation);
Object prefix = elementValues.get("prefix");
if (prefix != null && !"".equals(prefix)) {
return (String) prefix;
}
Object value = elementValues.get("value");
if (value != null && !"".equals(value)) {
return (String) value;
}
return null;
}
private Map<String, Object> getAnnotationElementValues(AnnotationMirror annotation) {
Map<String, Object> values = new LinkedHashMap<>();
for (Map.Entry<? extends ExecutableElement, ? extends AnnotationValue> entry : annotation
.getElementValues().entrySet()) {
values.put(entry.getKey().getSimpleName().toString(),
entry.getValue().getValue());
}
return values;
}
protected ConfigurationMetadata writeMetaData() throws Exception {
ConfigurationMetadata metadata = this.metadataCollector.getMetadata();
metadata = mergeAdditionalMetadata(metadata);
if (!metadata.getItems().isEmpty()) {
this.metadataStore.writeMetadata(metadata);
return metadata;
}
return null;
}
private ConfigurationMetadata mergeAdditionalMetadata(
ConfigurationMetadata metadata) {
try {
ConfigurationMetadata merged = new ConfigurationMetadata(metadata);
merged.merge(this.metadataStore.readAdditionalMetadata());
return merged;
}
catch (FileNotFoundException ex) {
// No additional metadata
}
catch (InvalidConfigurationMetadataException ex) {
log(ex.getKind(), ex.getMessage());
}
catch (Exception ex) {
logWarning("Unable to merge additional metadata");
logWarning(getStackTrace(ex));
}
return metadata;
}
private String getStackTrace(Exception ex) {
StringWriter writer = new StringWriter();
ex.printStackTrace(new PrintWriter(writer, true));
return writer.toString();
}
private void logWarning(String msg) {
log(Kind.WARNING, msg);
}
private void log(Kind kind, String msg) {
this.processingEnv.getMessager().printMessage(kind, msg);
}
private static class EndpointExposure {
private static final List<String> ALL = Arrays.asList("JMX", "WEB");
private final List<String> types;
EndpointExposure(List<String> types) {
this.types = types;
}
static EndpointExposure parse(Object exposureAttribute) {
List<AnnotationValue> values = asAnnotationValues(exposureAttribute);
if (values.isEmpty()) {
return new EndpointExposure(ALL);
}
return new EndpointExposure(
values.stream().map(EndpointExposure::getValueAttribute)
.collect(Collectors.toList()));
}
@SuppressWarnings("unchecked")
private static List<AnnotationValue> asAnnotationValues(Object typesAttribute) {
if (!(typesAttribute instanceof List)) {
return Collections.emptyList();
}
return (List<AnnotationValue>) typesAttribute;
}
private static String getValueAttribute(AnnotationValue value) {
return ((VariableElement) value.getValue()).getSimpleName().toString();
}
public boolean hasJmx() {
return this.types.contains("JMX");
}
public boolean hasWeb() {
return this.types.contains("WEB");
}
}
}

View File

@@ -0,0 +1,125 @@
/*
* Copyright 2012-2017 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.LinkedHashSet;
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;
/**
* Used by {@link ConfigurationMetadataAnnotationProcessor} to collect
* {@link ConfigurationMetadata}.
*
* @author Andy Wilkinson
* @author Kris De Volder
* @since 1.2.2
*/
public class MetadataCollector {
private final Set<ItemMetadata> metadataItems = new LinkedHashSet<>();
private final ProcessingEnvironment processingEnvironment;
private final ConfigurationMetadata previousMetadata;
private final TypeUtils typeUtils;
private final Set<String> processedSourceTypes = new HashSet<>();
/**
* Creates a new {@code MetadataProcessor} instance.
* @param processingEnvironment The processing environment of the build
* @param previousMetadata Any previous metadata or {@code null}
*/
public MetadataCollector(ProcessingEnvironment processingEnvironment,
ConfigurationMetadata previousMetadata) {
this.processingEnvironment = processingEnvironment;
this.previousMetadata = previousMetadata;
this.typeUtils = new TypeUtils(processingEnvironment);
}
public void processing(RoundEnvironment roundEnv) {
for (Element element : roundEnv.getRootElements()) {
markAsProcessed(element);
}
}
private void markAsProcessed(Element element) {
if (element instanceof TypeElement) {
this.processedSourceTypes.add(this.typeUtils.getQualifiedName(element));
}
}
public void add(ItemMetadata metadata) {
this.metadataItems.add(metadata);
}
public boolean hasSimilarGroup(ItemMetadata metadata) {
if (!metadata.isOfItemType(ItemMetadata.ItemType.GROUP)) {
throw new IllegalStateException("item " + metadata + " must be a group");
}
for (ItemMetadata existing : this.metadataItems) {
if (existing.isOfItemType(ItemMetadata.ItemType.GROUP)
&& existing.getName().equals(metadata.getName())
&& existing.getType().equals(metadata.getType())) {
return true;
}
}
return false;
}
public ConfigurationMetadata getMetadata() {
ConfigurationMetadata metadata = new ConfigurationMetadata();
for (ItemMetadata item : this.metadataItems) {
metadata.add(item);
}
if (this.previousMetadata != null) {
List<ItemMetadata> items = this.previousMetadata.getItems();
for (ItemMetadata item : items) {
if (shouldBeMerged(item)) {
metadata.add(item);
}
}
}
return metadata;
}
private boolean shouldBeMerged(ItemMetadata itemMetadata) {
String sourceType = itemMetadata.getSourceType();
return (sourceType != null && !deletedInCurrentBuild(sourceType)
&& !processedInCurrentBuild(sourceType));
}
private boolean deletedInCurrentBuild(String sourceType) {
return this.processingEnvironment.getElementUtils()
.getTypeElement(sourceType) == null;
}
private boolean processedInCurrentBuild(String sourceType) {
return this.processedSourceTypes.contains(sourceType);
}
}

View File

@@ -0,0 +1,140 @@
/*
* Copyright 2012-2017 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.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import javax.annotation.processing.ProcessingEnvironment;
import javax.tools.Diagnostic;
import javax.tools.FileObject;
import javax.tools.StandardLocation;
import org.springframework.boot.configurationprocessor.metadata.ConfigurationMetadata;
import org.springframework.boot.configurationprocessor.metadata.InvalidConfigurationMetadataException;
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()) {
try (OutputStream outputStream = createMetadataResource()
.openOutputStream()) {
new JsonMarshaller().write(metadata, outputStream);
}
}
}
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;
}
catch (Exception ex) {
throw new InvalidConfigurationMetadataException(
"Invalid additional meta-data in '" + METADATA_PATH + "': "
+ ex.getMessage(),
Diagnostic.Kind.ERROR);
}
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 = locateAdditionalMetadataFile(new File(fileObject.toUri()));
return (file.exists() ? new FileInputStream(file)
: fileObject.toUri().toURL().openStream());
}
File locateAdditionalMetadataFile(File standardLocation) throws IOException {
if (standardLocation.exists()) {
return standardLocation;
}
return new File(locateGradleResourcesFolder(standardLocation),
ADDITIONAL_METADATA_PATH);
}
private File locateGradleResourcesFolder(File standardAdditionalMetadataLocation)
throws FileNotFoundException {
String path = standardAdditionalMetadataLocation.getPath();
int index = path.lastIndexOf(CLASSES_FOLDER);
if (index < 0) {
throw new FileNotFoundException();
}
String buildFolderPath = path.substring(0, index);
File classOutputLocation = standardAdditionalMetadataLocation.getParentFile()
.getParentFile();
return new File(buildFolderPath,
RESOURCES_FOLDER + '/' + classOutputLocation.getName());
}
}

View File

@@ -0,0 +1,209 @@
/*
* Copyright 2012-2017 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.ArrayList;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import javax.annotation.processing.ProcessingEnvironment;
import javax.lang.model.element.Element;
import javax.lang.model.element.ExecutableElement;
import javax.lang.model.element.Modifier;
import javax.lang.model.element.TypeElement;
import javax.lang.model.element.VariableElement;
import javax.lang.model.type.TypeKind;
import javax.lang.model.type.TypeMirror;
import javax.lang.model.util.ElementFilter;
import org.springframework.boot.configurationprocessor.fieldvalues.FieldValuesParser;
/**
* Provides access to relevant {@link TypeElement} members.
*
* @author Stephane Nicoll
* @author Phillip Webb
* @since 1.2.0
*/
class TypeElementMembers {
private static final String OBJECT_CLASS_NAME = Object.class.getName();
private final ProcessingEnvironment env;
private final TypeUtils typeUtils;
private final Map<String, VariableElement> fields = new LinkedHashMap<>();
private final Map<String, ExecutableElement> publicGetters = new LinkedHashMap<>();
private final Map<String, List<ExecutableElement>> publicSetters = new LinkedHashMap<>();
private final Map<String, Object> fieldValues = new LinkedHashMap<>();
private final FieldValuesParser fieldValuesParser;
TypeElementMembers(ProcessingEnvironment env, FieldValuesParser fieldValuesParser,
TypeElement element) {
this.env = env;
this.typeUtils = new TypeUtils(this.env);
this.fieldValuesParser = fieldValuesParser;
process(element);
}
private void process(TypeElement element) {
for (ExecutableElement method : ElementFilter
.methodsIn(element.getEnclosedElements())) {
processMethod(method);
}
for (VariableElement field : ElementFilter
.fieldsIn(element.getEnclosedElements())) {
processField(field);
}
try {
Map<String, Object> fieldValues = this.fieldValuesParser
.getFieldValues(element);
for (Map.Entry<String, Object> entry : fieldValues.entrySet()) {
if (!this.fieldValues.containsKey(entry.getKey())) {
this.fieldValues.put(entry.getKey(), entry.getValue());
}
}
}
catch (Exception ex) {
// continue
}
Element superType = this.env.getTypeUtils().asElement(element.getSuperclass());
if (superType != null && superType instanceof TypeElement
&& !OBJECT_CLASS_NAME.equals(superType.toString())) {
process((TypeElement) superType);
}
}
private void processMethod(ExecutableElement method) {
if (method.getModifiers().contains(Modifier.PUBLIC)) {
String name = method.getSimpleName().toString();
if (isGetter(method) && !this.publicGetters.containsKey(name)) {
this.publicGetters.put(getAccessorName(name), method);
}
else if (isSetter(method)) {
String propertyName = getAccessorName(name);
List<ExecutableElement> matchingSetters = this.publicSetters
.get(propertyName);
if (matchingSetters == null) {
matchingSetters = new ArrayList<>();
this.publicSetters.put(propertyName, matchingSetters);
}
TypeMirror paramType = method.getParameters().get(0).asType();
if (getMatchingSetter(matchingSetters, paramType) == null) {
matchingSetters.add(method);
}
}
}
}
private ExecutableElement getMatchingSetter(List<ExecutableElement> candidates,
TypeMirror type) {
for (ExecutableElement candidate : candidates) {
TypeMirror paramType = candidate.getParameters().get(0).asType();
if (this.env.getTypeUtils().isSameType(paramType, type)) {
return candidate;
}
}
return null;
}
private boolean isGetter(ExecutableElement method) {
String name = method.getSimpleName().toString();
return ((name.startsWith("get") && name.length() > 3)
|| (name.startsWith("is") && name.length() > 2))
&& method.getParameters().isEmpty()
&& (TypeKind.VOID != method.getReturnType().getKind());
}
private boolean isSetter(ExecutableElement method) {
final String name = method.getSimpleName().toString();
return (name.startsWith("set") && name.length() > 3
&& method.getParameters().size() == 1 && isSetterReturnType(method));
}
private boolean isSetterReturnType(ExecutableElement method) {
TypeMirror returnType = method.getReturnType();
return (TypeKind.VOID == returnType.getKind() || this.env.getTypeUtils()
.isSameType(method.getEnclosingElement().asType(), returnType));
}
private String getAccessorName(String methodName) {
String name = methodName.startsWith("is") ? methodName.substring(2)
: methodName.substring(3);
name = Character.toLowerCase(name.charAt(0)) + name.substring(1);
return name;
}
private void processField(VariableElement field) {
String name = field.getSimpleName().toString();
if (!this.fields.containsKey(name)) {
this.fields.put(name, field);
}
}
public Map<String, VariableElement> getFields() {
return Collections.unmodifiableMap(this.fields);
}
public Map<String, ExecutableElement> getPublicGetters() {
return Collections.unmodifiableMap(this.publicGetters);
}
public ExecutableElement getPublicGetter(String name, TypeMirror type) {
ExecutableElement candidate = this.publicGetters.get(name);
if (candidate != null) {
TypeMirror returnType = candidate.getReturnType();
if (this.env.getTypeUtils().isSameType(returnType, type)) {
return candidate;
}
TypeMirror alternative = this.typeUtils.getWrapperOrPrimitiveFor(type);
if (alternative != null
&& this.env.getTypeUtils().isSameType(returnType, alternative)) {
return candidate;
}
}
return null;
}
public ExecutableElement getPublicSetter(String name, TypeMirror type) {
List<ExecutableElement> candidates = this.publicSetters.get(name);
if (candidates != null) {
ExecutableElement matching = getMatchingSetter(candidates, type);
if (matching != null) {
return matching;
}
TypeMirror alternative = this.typeUtils.getWrapperOrPrimitiveFor(type);
if (alternative != null) {
return getMatchingSetter(candidates, alternative);
}
}
return null;
}
public Map<String, Object> getFieldValues() {
return Collections.unmodifiableMap(this.fieldValues);
}
}

View File

@@ -0,0 +1,67 @@
/*
* Copyright 2012-2017 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.Set;
import javax.lang.model.type.TypeMirror;
/**
* Filter to exclude elements that don't make sense to process.
*
* @author Stephane Nicoll
* @author Andy Wilkinson
* @since 1.2.0
*/
class TypeExcludeFilter {
private final Set<String> excludes = new HashSet<>();
TypeExcludeFilter() {
add("com.zaxxer.hikari.IConnectionCustomizer");
add("groovy.text.markup.MarkupTemplateEngine");
add("java.io.Writer");
add("java.io.PrintWriter");
add("java.lang.ClassLoader");
add("java.util.concurrent.ThreadFactory");
add("javax.jms.XAConnectionFactory");
add("javax.sql.DataSource");
add("javax.sql.XADataSource");
add("org.apache.tomcat.jdbc.pool.PoolConfiguration");
add("org.apache.tomcat.jdbc.pool.Validator");
add("org.flywaydb.core.api.callback.FlywayCallback");
add("org.flywaydb.core.api.resolver.MigrationResolver");
add("org.springframework.http.MediaType");
}
private void add(String className) {
this.excludes.add(className);
}
public boolean isExcluded(TypeMirror type) {
if (type == null) {
return false;
}
String typeName = type.toString();
if (typeName.endsWith("[]")) {
typeName = typeName.substring(0, typeName.length() - 2);
}
return this.excludes.contains(typeName);
}
}

View File

@@ -0,0 +1,197 @@
/*
* Copyright 2012-2017 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.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import javax.annotation.processing.ProcessingEnvironment;
import javax.lang.model.element.Element;
import javax.lang.model.element.TypeElement;
import javax.lang.model.type.DeclaredType;
import javax.lang.model.type.TypeKind;
import javax.lang.model.type.TypeMirror;
import javax.lang.model.util.Types;
/**
* Type Utilities.
*
* @author Stephane Nicoll
* @author Phillip Webb
* @since 1.2.0
*/
class TypeUtils {
private static final Map<TypeKind, Class<?>> PRIMITIVE_WRAPPERS;
static {
Map<TypeKind, Class<?>> wrappers = new HashMap<>();
wrappers.put(TypeKind.BOOLEAN, Boolean.class);
wrappers.put(TypeKind.BYTE, Byte.class);
wrappers.put(TypeKind.CHAR, Character.class);
wrappers.put(TypeKind.DOUBLE, Double.class);
wrappers.put(TypeKind.FLOAT, Float.class);
wrappers.put(TypeKind.INT, Integer.class);
wrappers.put(TypeKind.LONG, Long.class);
wrappers.put(TypeKind.SHORT, Short.class);
PRIMITIVE_WRAPPERS = Collections.unmodifiableMap(wrappers);
}
private static final Map<String, TypeKind> WRAPPER_TO_PRIMITIVE;
static {
Map<String, TypeKind> primitives = new HashMap<>();
for (Map.Entry<TypeKind, Class<?>> entry : PRIMITIVE_WRAPPERS.entrySet()) {
primitives.put(entry.getValue().getName(), entry.getKey());
}
WRAPPER_TO_PRIMITIVE = primitives;
}
private final ProcessingEnvironment env;
private final TypeMirror collectionType;
private final TypeMirror mapType;
TypeUtils(ProcessingEnvironment env) {
this.env = env;
Types types = env.getTypeUtils();
this.collectionType = getDeclaredType(types, Collection.class, 1);
this.mapType = getDeclaredType(types, Map.class, 2);
}
private TypeMirror getDeclaredType(Types types, Class<?> typeClass,
int numberOfTypeArgs) {
TypeMirror[] typeArgs = new TypeMirror[numberOfTypeArgs];
for (int i = 0; i < typeArgs.length; i++) {
typeArgs[i] = types.getWildcardType(null, null);
}
TypeElement typeElement = this.env.getElementUtils()
.getTypeElement(typeClass.getName());
try {
return types.getDeclaredType(typeElement, typeArgs);
}
catch (IllegalArgumentException ex) {
// Try again without generics for older Java versions
return types.getDeclaredType(typeElement);
}
}
/**
* Return the qualified name of the specified element.
* @param element the element to handle
* @return the fully qualified name of the element, suitable for a call to
* {@link Class#forName(String)}
*/
public String getQualifiedName(Element element) {
if (element == null) {
return null;
}
TypeElement enclosingElement = getEnclosingTypeElement(element.asType());
if (enclosingElement != null) {
return getQualifiedName(enclosingElement) + "$"
+ ((DeclaredType) element.asType()).asElement().getSimpleName()
.toString();
}
if (element instanceof TypeElement) {
return ((TypeElement) element).getQualifiedName().toString();
}
throw new IllegalStateException(
"Could not extract qualified name from " + element);
}
/**
* Return the type of the specified {@link TypeMirror} including all its generic
* information.
* @param type the type to handle
* @return a representation of the type including all its generic information
*/
public String getType(TypeMirror type) {
if (type == null) {
return null;
}
Class<?> wrapper = getWrapperFor(type);
if (wrapper != null) {
return wrapper.getName();
}
TypeElement enclosingElement = getEnclosingTypeElement(type);
if (enclosingElement != null) {
return getQualifiedName(enclosingElement) + "$"
+ ((DeclaredType) type).asElement().getSimpleName().toString();
}
return type.toString();
}
private TypeElement getEnclosingTypeElement(TypeMirror type) {
if (type instanceof DeclaredType) {
DeclaredType declaredType = (DeclaredType) type;
Element enclosingElement = declaredType.asElement().getEnclosingElement();
if (enclosingElement != null && enclosingElement instanceof TypeElement) {
return (TypeElement) enclosingElement;
}
}
return null;
}
public boolean isCollectionOrMap(TypeMirror type) {
return this.env.getTypeUtils().isAssignable(type, this.collectionType)
|| this.env.getTypeUtils().isAssignable(type, this.mapType);
}
public boolean isEnclosedIn(Element candidate, TypeElement element) {
if (candidate == null || element == null) {
return false;
}
if (candidate.equals(element)) {
return true;
}
return isEnclosedIn(candidate.getEnclosingElement(), element);
}
public String getJavaDoc(Element element) {
String javadoc = (element == null ? null
: this.env.getElementUtils().getDocComment(element));
if (javadoc != null) {
javadoc = javadoc.trim();
}
return ("".equals(javadoc) ? null : javadoc);
}
public TypeMirror getWrapperOrPrimitiveFor(TypeMirror typeMirror) {
Class<?> candidate = getWrapperFor(typeMirror);
if (candidate != null) {
return this.env.getElementUtils().getTypeElement(candidate.getName())
.asType();
}
TypeKind primitiveKind = getPrimitiveFor(typeMirror);
if (primitiveKind != null) {
return this.env.getTypeUtils().getPrimitiveType(primitiveKind);
}
return null;
}
private Class<?> getWrapperFor(TypeMirror type) {
return PRIMITIVE_WRAPPERS.get(type.getKind());
}
private TypeKind getPrimitiveFor(TypeMirror type) {
return WRAPPER_TO_PRIMITIVE.get(type.toString());
}
}

View File

@@ -0,0 +1,49 @@
/*
* Copyright 2012-2017 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.fieldvalues;
import java.util.Collections;
import java.util.Map;
import javax.lang.model.element.TypeElement;
import org.springframework.boot.configurationprocessor.fieldvalues.javac.JavaCompilerFieldValuesParser;
/**
* Parser which can be used to obtain the field values from an {@link TypeElement}.
*
* @author Phillip Webb
* @since 1.1.2
* @see JavaCompilerFieldValuesParser
*/
@FunctionalInterface
public interface FieldValuesParser {
/**
* Implementation of {@link FieldValuesParser} that always returns an empty result.
*/
FieldValuesParser NONE = (element) -> Collections.emptyMap();
/**
* Return the field values for the given element.
* @param element the element to inspect
* @return a map of field names to values.
* @throws Exception if the values cannot be extracted
*/
Map<String, Object> getFieldValues(TypeElement element) throws Exception;
}

View File

@@ -0,0 +1,90 @@
/*
* Copyright 2012-2017 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.fieldvalues.javac;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.List;
/**
* Reflection based access to {@code com.sun.source.tree.ExpressionTree}.
*
* @author Phillip Webb
* @author Stephane Nicoll
* @since 1.2.0
*/
class ExpressionTree extends ReflectionWrapper {
private final Class<?> literalTreeType = findClass("com.sun.source.tree.LiteralTree");
private final Method literalValueMethod = findMethod(this.literalTreeType,
"getValue");
private final Class<?> methodInvocationTreeType = findClass(
"com.sun.source.tree.MethodInvocationTree");
private final Method methodInvocationArgumentsMethod = findMethod(
this.methodInvocationTreeType, "getArguments");
private final Class<?> newArrayTreeType = findClass(
"com.sun.source.tree.NewArrayTree");
private final Method arrayValueMethod = findMethod(this.newArrayTreeType,
"getInitializers");
ExpressionTree(Object instance) {
super("com.sun.source.tree.ExpressionTree", instance);
}
public String getKind() throws Exception {
return findMethod("getKind").invoke(getInstance()).toString();
}
public Object getLiteralValue() throws Exception {
if (this.literalTreeType.isAssignableFrom(getInstance().getClass())) {
return this.literalValueMethod.invoke(getInstance());
}
return null;
}
public Object getFactoryValue() throws Exception {
if (this.methodInvocationTreeType.isAssignableFrom(getInstance().getClass())) {
List<?> arguments = (List<?>) this.methodInvocationArgumentsMethod
.invoke(getInstance());
if (arguments.size() == 1) {
return new ExpressionTree(arguments.get(0)).getLiteralValue();
}
}
return null;
}
public List<? extends ExpressionTree> getArrayExpression() throws Exception {
if (this.newArrayTreeType.isAssignableFrom(getInstance().getClass())) {
List<?> elements = (List<?>) this.arrayValueMethod.invoke(getInstance());
List<ExpressionTree> result = new ArrayList<>();
if (elements == null) {
return result;
}
for (Object element : elements) {
result.add(new ExpressionTree(element));
}
return result;
}
return null;
}
}

View File

@@ -0,0 +1,162 @@
/*
* Copyright 2012-2017 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.fieldvalues.javac;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import javax.annotation.processing.ProcessingEnvironment;
import javax.lang.model.element.Modifier;
import javax.lang.model.element.TypeElement;
import org.springframework.boot.configurationprocessor.fieldvalues.FieldValuesParser;
/**
* {@link FieldValuesParser} implementation for the standard Java compiler.
*
* @author Phillip Webb
* @author Stephane Nicoll
* @since 1.2.0
*/
public class JavaCompilerFieldValuesParser implements FieldValuesParser {
private final Trees trees;
public JavaCompilerFieldValuesParser(ProcessingEnvironment env) throws Exception {
this.trees = Trees.instance(env);
}
@Override
public Map<String, Object> getFieldValues(TypeElement element) throws Exception {
Tree tree = this.trees.getTree(element);
if (tree != null) {
FieldCollector fieldCollector = new FieldCollector();
tree.accept(fieldCollector);
return fieldCollector.getFieldValues();
}
return Collections.emptyMap();
}
/**
* {@link TreeVisitor} to collect fields.
*/
private static class FieldCollector implements TreeVisitor {
private static final Map<String, Class<?>> WRAPPER_TYPES;
static {
Map<String, Class<?>> types = new HashMap<>();
types.put("boolean", Boolean.class);
types.put(Boolean.class.getName(), Boolean.class);
types.put("byte", Byte.class);
types.put(Byte.class.getName(), Byte.class);
types.put("short", Short.class);
types.put(Short.class.getName(), Short.class);
types.put("int", Integer.class);
types.put(Integer.class.getName(), Integer.class);
types.put("long", Long.class);
types.put(Long.class.getName(), Long.class);
WRAPPER_TYPES = Collections.unmodifiableMap(types);
}
private static final Map<Class<?>, Object> defaultTypeValues;
static {
Map<Class<?>, Object> values = new HashMap<>();
values.put(Boolean.class, false);
values.put(Byte.class, (byte) 0);
values.put(Short.class, (short) 0);
values.put(Integer.class, 0);
values.put(Long.class, (long) 0);
defaultTypeValues = Collections.unmodifiableMap(values);
}
private static final Map<String, Object> wellKnownStaticFinals;
static {
Map<String, Object> values = new HashMap<>();
values.put("Boolean.TRUE", true);
values.put("Boolean.FALSE", false);
wellKnownStaticFinals = Collections.unmodifiableMap(values);
}
private final Map<String, Object> fieldValues = new HashMap<>();
private final Map<String, Object> staticFinals = new HashMap<>();
@Override
public void visitVariable(VariableTree variable) throws Exception {
Set<Modifier> flags = variable.getModifierFlags();
if (flags.contains(Modifier.STATIC) && flags.contains(Modifier.FINAL)) {
this.staticFinals.put(variable.getName(), getValue(variable));
}
if (!flags.contains(Modifier.FINAL)) {
this.fieldValues.put(variable.getName(), getValue(variable));
}
}
private Object getValue(VariableTree variable) throws Exception {
ExpressionTree initializer = variable.getInitializer();
Class<?> wrapperType = WRAPPER_TYPES.get(variable.getType());
Object defaultValue = defaultTypeValues.get(wrapperType);
if (initializer != null) {
return getValue(initializer, defaultValue);
}
return defaultValue;
}
private Object getValue(ExpressionTree expression, Object defaultValue)
throws Exception {
Object literalValue = expression.getLiteralValue();
if (literalValue != null) {
return literalValue;
}
Object factoryValue = expression.getFactoryValue();
if (factoryValue != null) {
return factoryValue;
}
List<? extends ExpressionTree> arrayValues = expression.getArrayExpression();
if (arrayValues != null) {
Object[] result = new Object[arrayValues.size()];
for (int i = 0; i < arrayValues.size(); i++) {
Object value = getValue(arrayValues.get(i), null);
if (value == null) { // One of the elements could not be resolved
return defaultValue;
}
result[i] = value;
}
return result;
}
if (expression.getKind().equals("IDENTIFIER")) {
return this.staticFinals.get(expression.toString());
}
if (expression.getKind().equals("MEMBER_SELECT")) {
return wellKnownStaticFinals.get(expression.toString());
}
return defaultValue;
}
public Map<String, Object> getFieldValues() {
return this.fieldValues;
}
}
}

View File

@@ -0,0 +1,75 @@
/*
* Copyright 2012-2016 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.fieldvalues.javac;
import java.lang.reflect.Method;
/**
* Base class for reflection based wrappers. Used to access internal Java classes without
* needing tools.jar on the classpath.
*
* @author Phillip Webb
* @since 1.2.0
*/
class ReflectionWrapper {
private final Class<?> type;
private final Object instance;
ReflectionWrapper(String type, Object instance) {
this.type = findClass(instance.getClass().getClassLoader(), type);
this.instance = this.type.cast(instance);
}
protected final Object getInstance() {
return this.instance;
}
@Override
public String toString() {
return this.instance.toString();
}
protected Class<?> findClass(String name) {
return findClass(getInstance().getClass().getClassLoader(), name);
}
protected Method findMethod(String name, Class<?>... parameterTypes) {
return findMethod(this.type, name, parameterTypes);
}
protected static Class<?> findClass(ClassLoader classLoader, String name) {
try {
return classLoader.loadClass(name);
}
catch (ClassNotFoundException ex) {
throw new IllegalStateException(ex);
}
}
protected static Method findMethod(Class<?> type, String name,
Class<?>... parameterTypes) {
try {
return type.getMethod(name, parameterTypes);
}
catch (Exception ex) {
throw new IllegalStateException(ex);
}
}
}

View File

@@ -0,0 +1,86 @@
/*
* 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.fieldvalues.javac;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
/**
* Reflection based access to {@code com.sun.source.tree.Tree}.
*
* @author Phillip Webb
* @since 1.2.0
*/
class Tree extends ReflectionWrapper {
private final Class<?> treeVisitorType = findClass("com.sun.source.tree.TreeVisitor");
private final Method acceptMethod = findMethod("accept", this.treeVisitorType,
Object.class);
private final Method GET_CLASS_TREE_MEMBERS = findMethod(
findClass("com.sun.source.tree.ClassTree"), "getMembers");
Tree(Object instance) {
super("com.sun.source.tree.Tree", instance);
}
public void accept(TreeVisitor visitor) throws Exception {
this.acceptMethod.invoke(getInstance(),
Proxy.newProxyInstance(getInstance().getClass().getClassLoader(),
new Class<?>[] { this.treeVisitorType },
new TreeVisitorInvocationHandler(visitor)),
0);
}
/**
* {@link InvocationHandler} to call the {@link TreeVisitor}.
*/
private class TreeVisitorInvocationHandler implements InvocationHandler {
private TreeVisitor treeVisitor;
TreeVisitorInvocationHandler(TreeVisitor treeVisitor) {
this.treeVisitor = treeVisitor;
}
@Override
@SuppressWarnings("rawtypes")
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable {
if (method.getName().equals("visitClass")) {
if ((Integer) args[1] == 0) {
Iterable members = (Iterable) Tree.this.GET_CLASS_TREE_MEMBERS
.invoke(args[0]);
for (Object member : members) {
if (member != null) {
Tree.this.acceptMethod.invoke(member, proxy,
((Integer) args[1]) + 1);
}
}
}
}
if (method.getName().equals("visitVariable")) {
this.treeVisitor.visitVariable(new VariableTree(args[0]));
}
return null;
}
}
}

View File

@@ -0,0 +1,29 @@
/*
* Copyright 2012-2017 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.fieldvalues.javac;
/**
* Reflection base alternative for {@code com.sun.source.tree.TreeVisitor}.
*
* @author Phillip Webb
* @since 1.2.0
*/
interface TreeVisitor {
void visitVariable(VariableTree variable) throws Exception;
}

View File

@@ -0,0 +1,48 @@
/*
* Copyright 2012-2016 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.fieldvalues.javac;
import java.lang.reflect.Method;
import javax.annotation.processing.ProcessingEnvironment;
import javax.lang.model.element.Element;
/**
* Reflection based access to {@code com.sun.source.util.Trees}.
*
* @author Phillip Webb
* @since 1.2.0
*/
final class Trees extends ReflectionWrapper {
private Trees(Object instance) {
super("com.sun.source.util.Trees", instance);
}
public Tree getTree(Element element) throws Exception {
Object tree = findMethod("getTree", Element.class).invoke(getInstance(), element);
return (tree == null ? null : new Tree(tree));
}
public static Trees instance(ProcessingEnvironment env) throws Exception {
ClassLoader classLoader = env.getClass().getClassLoader();
Class<?> type = findClass(classLoader, "com.sun.source.util.Trees");
Method method = findMethod(type, "instance", ProcessingEnvironment.class);
return new Trees(method.invoke(null, env));
}
}

View File

@@ -0,0 +1,59 @@
/*
* Copyright 2012-2016 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.fieldvalues.javac;
import java.util.Collections;
import java.util.Set;
import javax.lang.model.element.Modifier;
/**
* Reflection based access to {@code com.sun.source.tree.VariableTree}.
*
* @author Phillip Webb
* @since 1.2.0
*/
class VariableTree extends ReflectionWrapper {
VariableTree(Object instance) {
super("com.sun.source.tree.VariableTree", instance);
}
public String getName() throws Exception {
return findMethod("getName").invoke(getInstance()).toString();
}
public String getType() throws Exception {
return findMethod("getType").invoke(getInstance()).toString();
}
public ExpressionTree getInitializer() throws Exception {
Object instance = findMethod("getInitializer").invoke(getInstance());
return (instance == null ? null : new ExpressionTree(instance));
}
@SuppressWarnings("unchecked")
public Set<Modifier> getModifierFlags() throws Exception {
Object modifiers = findMethod("getModifiers").invoke(getInstance());
if (modifiers == null) {
return Collections.emptySet();
}
return (Set<Modifier>) findMethod(findClass("com.sun.source.tree.ModifiersTree"),
"getFlags").invoke(modifiers);
}
}

View File

@@ -0,0 +1,215 @@
/*
* Copyright 2012-2017 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.metadata;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
/**
* Configuration meta-data.
*
* @author Stephane Nicoll
* @author Phillip Webb
* @since 1.2.0
* @see ItemMetadata
*/
public class ConfigurationMetadata {
private static final Set<Character> SEPARATORS;
static {
List<Character> chars = Arrays.asList('-', '_');
SEPARATORS = Collections.unmodifiableSet(new HashSet<>(chars));
}
private final Map<String, List<ItemMetadata>> items;
private final Map<String, List<ItemHint>> hints;
public ConfigurationMetadata() {
this.items = new LinkedHashMap<>();
this.hints = new LinkedHashMap<>();
}
public ConfigurationMetadata(ConfigurationMetadata metadata) {
this.items = new LinkedHashMap<>(metadata.items);
this.hints = new LinkedHashMap<>(metadata.hints);
}
/**
* Add item meta-data.
* @param itemMetadata the meta-data to add
*/
public void add(ItemMetadata itemMetadata) {
add(this.items, itemMetadata.getName(), itemMetadata);
}
/**
* Add item hint.
* @param itemHint the item hint to add
*/
public void add(ItemHint itemHint) {
add(this.hints, itemHint.getName(), itemHint);
}
/**
* Merge the content from another {@link ConfigurationMetadata}.
* @param metadata the {@link ConfigurationMetadata} instance to merge
*/
public void merge(ConfigurationMetadata metadata) {
for (ItemMetadata additionalItem : metadata.getItems()) {
mergeItemMetadata(additionalItem);
}
for (ItemHint itemHint : metadata.getHints()) {
add(itemHint);
}
}
/**
* Return item meta-data.
* @return the items
*/
public List<ItemMetadata> getItems() {
return flattenValues(this.items);
}
/**
* Return hint meta-data.
* @return the hints
*/
public List<ItemHint> getHints() {
return flattenValues(this.hints);
}
protected void mergeItemMetadata(ItemMetadata metadata) {
ItemMetadata matching = findMatchingItemMetadata(metadata);
if (matching != null) {
if (metadata.getDescription() != null) {
matching.setDescription(metadata.getDescription());
}
if (metadata.getDefaultValue() != null) {
matching.setDefaultValue(metadata.getDefaultValue());
}
ItemDeprecation deprecation = metadata.getDeprecation();
ItemDeprecation matchingDeprecation = matching.getDeprecation();
if (deprecation != null) {
if (matchingDeprecation == null) {
matching.setDeprecation(deprecation);
}
else {
if (deprecation.getReason() != null) {
matchingDeprecation.setReason(deprecation.getReason());
}
if (deprecation.getReplacement() != null) {
matchingDeprecation.setReplacement(deprecation.getReplacement());
}
if (deprecation.getLevel() != null) {
matchingDeprecation.setLevel(deprecation.getLevel());
}
}
}
}
else {
add(this.items, metadata.getName(), metadata);
}
}
private <K, V> void add(Map<K, List<V>> map, K key, V value) {
List<V> values = map.get(key);
if (values == null) {
values = new ArrayList<>();
map.put(key, values);
}
values.add(value);
}
private ItemMetadata findMatchingItemMetadata(ItemMetadata metadata) {
List<ItemMetadata> candidates = this.items.get(metadata.getName());
if (candidates == null || candidates.isEmpty()) {
return null;
}
candidates.removeIf((itemMetadata) -> !itemMetadata.hasSameType(metadata));
if (candidates.size() == 1) {
return candidates.get(0);
}
for (ItemMetadata candidate : candidates) {
if (nullSafeEquals(candidate.getSourceType(), metadata.getSourceType())) {
return candidate;
}
}
return null;
}
private boolean nullSafeEquals(Object o1, Object o2) {
if (o1 == o2) {
return true;
}
return o1 != null && o2 != null && o1.equals(o2);
}
public static String nestedPrefix(String prefix, String name) {
String nestedPrefix = (prefix == null ? "" : prefix);
String dashedName = toDashedCase(name);
nestedPrefix += ("".equals(nestedPrefix) ? dashedName : "." + dashedName);
return nestedPrefix;
}
static String toDashedCase(String name) {
StringBuilder dashed = new StringBuilder();
Character previous = null;
for (char current : name.toCharArray()) {
if (SEPARATORS.contains(current)) {
dashed.append("-");
}
else if (Character.isUpperCase(current) && previous != null
&& !SEPARATORS.contains(previous)) {
dashed.append("-").append(current);
}
else {
dashed.append(current);
}
previous = current;
}
return dashed.toString().toLowerCase();
}
private static <T extends Comparable<T>> List<T> flattenValues(Map<?, List<T>> map) {
List<T> content = new ArrayList<>();
for (List<T> values : map.values()) {
content.addAll(values);
}
Collections.sort(content);
return content;
}
@Override
public String toString() {
StringBuilder result = new StringBuilder();
result.append(String.format("items: %n"));
this.items.values().forEach((itemMetadata) -> result.append("\t")
.append(String.format("%s%n", itemMetadata)));
return result.toString();
}
}

View File

@@ -0,0 +1,42 @@
/*
* 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.metadata;
import javax.tools.Diagnostic;
/**
* Thrown to indicate that some meta-data is invalid. Define the severity to determine
* whether it has to fail the build.
*
* @author Stephane Nicoll
* @since 1.3.0
*/
@SuppressWarnings("serial")
public class InvalidConfigurationMetadataException extends RuntimeException {
private final Diagnostic.Kind kind;
public InvalidConfigurationMetadataException(String message, Diagnostic.Kind kind) {
super(message);
this.kind = kind;
}
public Diagnostic.Kind getKind() {
return this.kind;
}
}

View File

@@ -0,0 +1,114 @@
/*
* Copyright 2012-2017 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.metadata;
/**
* Describe an item deprecation.
*
* @author Stephane Nicoll
* @since 1.3.0
*/
public class ItemDeprecation {
private String reason;
private String replacement;
private String level;
public ItemDeprecation() {
this(null, null);
}
public ItemDeprecation(String reason, String replacement) {
this(reason, replacement, null);
}
public ItemDeprecation(String reason, String replacement, String level) {
this.reason = reason;
this.replacement = replacement;
this.level = level;
}
public String getReason() {
return this.reason;
}
public void setReason(String reason) {
this.reason = reason;
}
public String getReplacement() {
return this.replacement;
}
public void setReplacement(String replacement) {
this.replacement = replacement;
}
public String getLevel() {
return this.level;
}
public void setLevel(String level) {
this.level = level;
}
@Override
public String toString() {
return "ItemDeprecation{" + "reason='" + this.reason + '\'' + ", "
+ "replacement='" + this.replacement + '\'' + ", " + "level='"
+ this.level + '\'' + '}';
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
ItemDeprecation other = (ItemDeprecation) o;
return nullSafeEquals(this.reason, other.reason)
&& nullSafeEquals(this.replacement, other.replacement)
&& nullSafeEquals(this.level, other.level);
}
@Override
public int hashCode() {
int result = nullSafeHashCode(this.reason);
result = 31 * result + nullSafeHashCode(this.replacement);
result = 31 * result + nullSafeHashCode(this.level);
return result;
}
private boolean nullSafeEquals(Object o1, Object o2) {
if (o1 == o2) {
return true;
}
if (o1 == null || o2 == null) {
return false;
}
return o1.equals(o2);
}
private int nullSafeHashCode(Object o) {
return (o == null ? 0 : o.hashCode());
}
}

View File

@@ -0,0 +1,150 @@
/*
* Copyright 2012-2017 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.metadata;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Map;
/**
* Provide hints on an {@link ItemMetadata}. Defines the list of possible values for a
* particular item as {@link ItemHint.ValueHint} instances.
* <p>
* The {@code name} of the hint is the name of the related property with one major
* exception for map types as both the keys and values of the map can have hints. In such
* a case, the hint should be suffixed by ".keys" or ".values" respectively. Creating a
* hint for a map using its property name is therefore invalid.
*
* @author Stephane Nicoll
* @since 1.3.0
*/
public class ItemHint implements Comparable<ItemHint> {
private final String name;
private final List<ValueHint> values;
private final List<ValueProvider> providers;
public ItemHint(String name, List<ValueHint> values, List<ValueProvider> providers) {
this.name = toCanonicalName(name);
this.values = (values != null ? new ArrayList<>(values) : new ArrayList<>());
this.providers = (providers != null ? new ArrayList<>(providers)
: new ArrayList<>());
}
private String toCanonicalName(String name) {
int dot = name.lastIndexOf('.');
if (dot != -1) {
String prefix = name.substring(0, dot);
String originalName = name.substring(dot);
return prefix + ConfigurationMetadata.toDashedCase(originalName);
}
return ConfigurationMetadata.toDashedCase(name);
}
public String getName() {
return this.name;
}
public List<ValueHint> getValues() {
return Collections.unmodifiableList(this.values);
}
public List<ValueProvider> getProviders() {
return Collections.unmodifiableList(this.providers);
}
@Override
public int compareTo(ItemHint other) {
return getName().compareTo(other.getName());
}
public static ItemHint newHint(String name, ValueHint... values) {
return new ItemHint(name, Arrays.asList(values),
Collections.<ValueProvider>emptyList());
}
@Override
public String toString() {
return "ItemHint{" + "name='" + this.name + "', values=" + this.values
+ ", providers=" + this.providers + '}';
}
/**
* A hint for a value.
*/
public static class ValueHint {
private final Object value;
private final String description;
public ValueHint(Object value, String description) {
this.value = value;
this.description = description;
}
public Object getValue() {
return this.value;
}
public String getDescription() {
return this.description;
}
@Override
public String toString() {
return "ValueHint{" + "value=" + this.value + ", description='"
+ this.description + '\'' + '}';
}
}
/**
* A value provider.
*/
public static class ValueProvider {
private final String name;
private final Map<String, Object> parameters;
public ValueProvider(String name, Map<String, Object> parameters) {
this.name = name;
this.parameters = parameters;
}
public String getName() {
return this.name;
}
public Map<String, Object> getParameters() {
return this.parameters;
}
@Override
public String toString() {
return "ValueProvider{" + "name='" + this.name + "', parameters="
+ this.parameters + '}';
}
}
}

View File

@@ -0,0 +1,236 @@
/*
* Copyright 2012-2017 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.metadata;
/**
* A group or property meta-data item from some {@link ConfigurationMetadata}.
*
* @author Stephane Nicoll
* @author Phillip Webb
* @since 1.2.0
* @see ConfigurationMetadata
*/
public final class ItemMetadata implements Comparable<ItemMetadata> {
private ItemType itemType;
private String name;
private String type;
private String description;
private String sourceType;
private String sourceMethod;
private Object defaultValue;
private ItemDeprecation deprecation;
ItemMetadata(ItemType itemType, String prefix, String name, String type,
String sourceType, String sourceMethod, String description,
Object defaultValue, ItemDeprecation deprecation) {
super();
this.itemType = itemType;
this.name = buildName(prefix, name);
this.type = type;
this.sourceType = sourceType;
this.sourceMethod = sourceMethod;
this.description = description;
this.defaultValue = defaultValue;
this.deprecation = deprecation;
}
private String buildName(String prefix, String name) {
while (prefix != null && prefix.endsWith(".")) {
prefix = prefix.substring(0, prefix.length() - 1);
}
StringBuilder fullName = new StringBuilder(prefix == null ? "" : prefix);
if (fullName.length() > 0 && name != null) {
fullName.append(".");
}
fullName.append(name == null ? "" : ConfigurationMetadata.toDashedCase(name));
return fullName.toString();
}
public boolean isOfItemType(ItemType itemType) {
return this.itemType == itemType;
}
public boolean hasSameType(ItemMetadata metadata) {
return this.itemType == metadata.itemType;
}
public String getName() {
return this.name;
}
public void setName(String name) {
this.name = name;
}
public String getType() {
return this.type;
}
public void setType(String type) {
this.type = type;
}
public String getDescription() {
return this.description;
}
public void setDescription(String description) {
this.description = description;
}
public String getSourceType() {
return this.sourceType;
}
public void setSourceType(String sourceType) {
this.sourceType = sourceType;
}
public String getSourceMethod() {
return this.sourceMethod;
}
public void setSourceMethod(String sourceMethod) {
this.sourceMethod = sourceMethod;
}
public Object getDefaultValue() {
return this.defaultValue;
}
public void setDefaultValue(Object defaultValue) {
this.defaultValue = defaultValue;
}
public ItemDeprecation getDeprecation() {
return this.deprecation;
}
public void setDeprecation(ItemDeprecation deprecation) {
this.deprecation = deprecation;
}
@Override
public String toString() {
StringBuilder string = new StringBuilder(this.name);
buildToStringProperty(string, "type", this.type);
buildToStringProperty(string, "sourceType", this.sourceType);
buildToStringProperty(string, "description", this.description);
buildToStringProperty(string, "defaultValue", this.defaultValue);
buildToStringProperty(string, "deprecation", this.deprecation);
return string.toString();
}
protected void buildToStringProperty(StringBuilder string, String property,
Object value) {
if (value != null) {
string.append(" ").append(property).append(":").append(value);
}
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
ItemMetadata other = (ItemMetadata) o;
boolean result = true;
result = result && nullSafeEquals(this.itemType, other.itemType);
result = result && nullSafeEquals(this.name, other.name);
result = result && nullSafeEquals(this.type, other.type);
result = result && nullSafeEquals(this.description, other.description);
result = result && nullSafeEquals(this.sourceType, other.sourceType);
result = result && nullSafeEquals(this.sourceMethod, other.sourceMethod);
result = result && nullSafeEquals(this.defaultValue, other.defaultValue);
result = result && nullSafeEquals(this.deprecation, other.deprecation);
return result;
}
@Override
public int hashCode() {
int result = nullSafeHashCode(this.itemType);
result = 31 * result + nullSafeHashCode(this.name);
result = 31 * result + nullSafeHashCode(this.type);
result = 31 * result + nullSafeHashCode(this.description);
result = 31 * result + nullSafeHashCode(this.sourceType);
result = 31 * result + nullSafeHashCode(this.sourceMethod);
result = 31 * result + nullSafeHashCode(this.defaultValue);
result = 31 * result + nullSafeHashCode(this.deprecation);
return result;
}
private boolean nullSafeEquals(Object o1, Object o2) {
if (o1 == o2) {
return true;
}
if (o1 == null || o2 == null) {
return false;
}
return o1.equals(o2);
}
private int nullSafeHashCode(Object o) {
return (o == null ? 0 : o.hashCode());
}
@Override
public int compareTo(ItemMetadata o) {
return getName().compareTo(o.getName());
}
public static ItemMetadata newGroup(String name, String type, String sourceType,
String sourceMethod) {
return new ItemMetadata(ItemType.GROUP, name, null, type, sourceType,
sourceMethod, null, null, null);
}
public static ItemMetadata newProperty(String prefix, String name, String type,
String sourceType, String sourceMethod, String description,
Object defaultValue, ItemDeprecation deprecation) {
return new ItemMetadata(ItemType.PROPERTY, prefix, name, type, sourceType,
sourceMethod, description, defaultValue, deprecation);
}
/**
* The item type.
*/
public enum ItemType {
/**
* Group item type.
*/
GROUP,
/**
* Property item type.
*/
PROPERTY
}
}

View File

@@ -0,0 +1,48 @@
/*
* Copyright 2012-2017 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.metadata;
import java.util.Iterator;
import java.util.LinkedHashSet;
import java.util.Set;
import org.json.JSONException;
import org.json.JSONObject;
/**
* Extension to {@link JSONObject} that remembers the order of inserts.
*
* @author Stephane Nicoll
* @author Phillip Webb
*/
@SuppressWarnings("rawtypes")
class JSONOrderedObject extends JSONObject {
private Set<String> keys = new LinkedHashSet<>();
@Override
public JSONObject put(String key, Object value) throws JSONException {
this.keys.add(key);
return super.put(key, value);
}
@Override
public Iterator keys() {
return this.keys.iterator();
}
}

View File

@@ -0,0 +1,164 @@
/*
* Copyright 2012-2017 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.metadata;
import java.lang.reflect.Array;
import java.util.Collection;
import java.util.Map;
import org.json.JSONArray;
import org.json.JSONObject;
import org.springframework.boot.configurationprocessor.metadata.ItemMetadata.ItemType;
/**
* Converter to change meta-data objects into JSON objects.
*
* @author Stephane Nicoll
* @author Phillip Webb
*/
class JsonConverter {
public JSONArray toJsonArray(ConfigurationMetadata metadata, ItemType itemType)
throws Exception {
JSONArray jsonArray = new JSONArray();
for (ItemMetadata item : metadata.getItems()) {
if (item.isOfItemType(itemType)) {
jsonArray.put(toJsonObject(item));
}
}
return jsonArray;
}
public JSONArray toJsonArray(Collection<ItemHint> hints) throws Exception {
JSONArray jsonArray = new JSONArray();
for (ItemHint hint : hints) {
jsonArray.put(toJsonObject(hint));
}
return jsonArray;
}
public JSONObject toJsonObject(ItemMetadata item) throws Exception {
JSONObject jsonObject = new JSONOrderedObject();
jsonObject.put("name", item.getName());
putIfPresent(jsonObject, "type", item.getType());
putIfPresent(jsonObject, "description", item.getDescription());
putIfPresent(jsonObject, "sourceType", item.getSourceType());
putIfPresent(jsonObject, "sourceMethod", item.getSourceMethod());
Object defaultValue = item.getDefaultValue();
if (defaultValue != null) {
putDefaultValue(jsonObject, defaultValue);
}
ItemDeprecation deprecation = item.getDeprecation();
if (deprecation != null) {
jsonObject.put("deprecated", true); // backward compatibility
JSONObject deprecationJsonObject = new JSONObject();
if (deprecation.getLevel() != null) {
deprecationJsonObject.put("level", deprecation.getLevel());
}
if (deprecation.getReason() != null) {
deprecationJsonObject.put("reason", deprecation.getReason());
}
if (deprecation.getReplacement() != null) {
deprecationJsonObject.put("replacement", deprecation.getReplacement());
}
jsonObject.put("deprecation", deprecationJsonObject);
}
return jsonObject;
}
private JSONObject toJsonObject(ItemHint hint) throws Exception {
JSONObject jsonObject = new JSONOrderedObject();
jsonObject.put("name", hint.getName());
if (!hint.getValues().isEmpty()) {
jsonObject.put("values", getItemHintValues(hint));
}
if (!hint.getProviders().isEmpty()) {
jsonObject.put("providers", getItemHintProviders(hint));
}
return jsonObject;
}
private JSONArray getItemHintValues(ItemHint hint) throws Exception {
JSONArray values = new JSONArray();
for (ItemHint.ValueHint value : hint.getValues()) {
values.put(getItemHintValue(value));
}
return values;
}
private JSONObject getItemHintValue(ItemHint.ValueHint value) throws Exception {
JSONObject result = new JSONOrderedObject();
putHintValue(result, value.getValue());
putIfPresent(result, "description", value.getDescription());
return result;
}
private JSONArray getItemHintProviders(ItemHint hint) throws Exception {
JSONArray providers = new JSONArray();
for (ItemHint.ValueProvider provider : hint.getProviders()) {
providers.put(getItemHintProvider(provider));
}
return providers;
}
private JSONObject getItemHintProvider(ItemHint.ValueProvider provider)
throws Exception {
JSONObject result = new JSONOrderedObject();
result.put("name", provider.getName());
if (provider.getParameters() != null && !provider.getParameters().isEmpty()) {
JSONObject parameters = new JSONOrderedObject();
for (Map.Entry<String, Object> entry : provider.getParameters().entrySet()) {
parameters.put(entry.getKey(), extractItemValue(entry.getValue()));
}
result.put("parameters", parameters);
}
return result;
}
private void putIfPresent(JSONObject jsonObject, String name, Object value)
throws Exception {
if (value != null) {
jsonObject.put(name, value);
}
}
private void putHintValue(JSONObject jsonObject, Object value) throws Exception {
Object hintValue = extractItemValue(value);
jsonObject.put("value", hintValue);
}
private void putDefaultValue(JSONObject jsonObject, Object value) throws Exception {
Object defaultValue = extractItemValue(value);
jsonObject.put("defaultValue", defaultValue);
}
private Object extractItemValue(Object value) {
Object defaultValue = value;
if (value.getClass().isArray()) {
JSONArray array = new JSONArray();
int length = Array.getLength(value);
for (int i = 0; i < length; i++) {
array.put(Array.get(value, i));
}
defaultValue = array;
}
return defaultValue;
}
}

View File

@@ -0,0 +1,182 @@
/*
* Copyright 2012-2017 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.metadata;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import org.json.JSONArray;
import org.json.JSONObject;
import org.springframework.boot.configurationprocessor.metadata.ItemMetadata.ItemType;
/**
* Marshaller to write {@link ConfigurationMetadata} as JSON.
*
* @author Stephane Nicoll
* @author Phillip Webb
* @since 1.2.0
*/
public class JsonMarshaller {
private static final Charset UTF_8 = Charset.forName("UTF-8");
private static final int BUFFER_SIZE = 4098;
public void write(ConfigurationMetadata metadata, OutputStream outputStream)
throws IOException {
try {
JSONObject object = new JSONOrderedObject();
JsonConverter converter = new JsonConverter();
object.put("groups", converter.toJsonArray(metadata, ItemType.GROUP));
object.put("properties", converter.toJsonArray(metadata, ItemType.PROPERTY));
object.put("hints", converter.toJsonArray(metadata.getHints()));
outputStream.write(object.toString(2).getBytes(UTF_8));
}
catch (Exception ex) {
if (ex instanceof IOException) {
throw (IOException) ex;
}
if (ex instanceof RuntimeException) {
throw (RuntimeException) ex;
}
throw new IllegalStateException(ex);
}
}
public ConfigurationMetadata read(InputStream inputStream) throws Exception {
ConfigurationMetadata metadata = new ConfigurationMetadata();
JSONObject object = new JSONObject(toString(inputStream));
JSONArray groups = object.optJSONArray("groups");
if (groups != null) {
for (int i = 0; i < groups.length(); i++) {
metadata.add(toItemMetadata((JSONObject) groups.get(i), ItemType.GROUP));
}
}
JSONArray properties = object.optJSONArray("properties");
if (properties != null) {
for (int i = 0; i < properties.length(); i++) {
metadata.add(toItemMetadata((JSONObject) properties.get(i),
ItemType.PROPERTY));
}
}
JSONArray hints = object.optJSONArray("hints");
if (hints != null) {
for (int i = 0; i < hints.length(); i++) {
metadata.add(toItemHint((JSONObject) hints.get(i)));
}
}
return metadata;
}
private ItemMetadata toItemMetadata(JSONObject object, ItemType itemType)
throws Exception {
String name = object.getString("name");
String type = object.optString("type", null);
String description = object.optString("description", null);
String sourceType = object.optString("sourceType", null);
String sourceMethod = object.optString("sourceMethod", null);
Object defaultValue = readItemValue(object.opt("defaultValue"));
ItemDeprecation deprecation = toItemDeprecation(object);
return new ItemMetadata(itemType, name, null, type, sourceType, sourceMethod,
description, defaultValue, deprecation);
}
private ItemDeprecation toItemDeprecation(JSONObject object) throws Exception {
if (object.has("deprecation")) {
JSONObject deprecationJsonObject = object.getJSONObject("deprecation");
ItemDeprecation deprecation = new ItemDeprecation();
deprecation.setLevel(deprecationJsonObject.optString("level", null));
deprecation.setReason(deprecationJsonObject.optString("reason", null));
deprecation
.setReplacement(deprecationJsonObject.optString("replacement", null));
return deprecation;
}
return (object.optBoolean("deprecated") ? new ItemDeprecation() : null);
}
private ItemHint toItemHint(JSONObject object) throws Exception {
String name = object.getString("name");
List<ItemHint.ValueHint> values = new ArrayList<>();
if (object.has("values")) {
JSONArray valuesArray = object.getJSONArray("values");
for (int i = 0; i < valuesArray.length(); i++) {
values.add(toValueHint((JSONObject) valuesArray.get(i)));
}
}
List<ItemHint.ValueProvider> providers = new ArrayList<>();
if (object.has("providers")) {
JSONArray providersObject = object.getJSONArray("providers");
for (int i = 0; i < providersObject.length(); i++) {
providers.add(toValueProvider((JSONObject) providersObject.get(i)));
}
}
return new ItemHint(name, values, providers);
}
private ItemHint.ValueHint toValueHint(JSONObject object) throws Exception {
Object value = readItemValue(object.get("value"));
String description = object.optString("description", null);
return new ItemHint.ValueHint(value, description);
}
private ItemHint.ValueProvider toValueProvider(JSONObject object) throws Exception {
String name = object.getString("name");
Map<String, Object> parameters = new HashMap<>();
if (object.has("parameters")) {
JSONObject parametersObject = object.getJSONObject("parameters");
for (Iterator<?> iterator = parametersObject.keys(); iterator.hasNext();) {
String key = (String) iterator.next();
Object value = readItemValue(parametersObject.get(key));
parameters.put(key, value);
}
}
return new ItemHint.ValueProvider(name, parameters);
}
private Object readItemValue(Object value) throws Exception {
if (value instanceof JSONArray) {
JSONArray array = (JSONArray) value;
Object[] content = new Object[array.length()];
for (int i = 0; i < array.length(); i++) {
content[i] = array.get(i);
}
return content;
}
return value;
}
private String toString(InputStream inputStream) throws IOException {
StringBuilder out = new StringBuilder();
InputStreamReader reader = new InputStreamReader(inputStream, UTF_8);
char[] buffer = new char[BUFFER_SIZE];
int bytesRead;
while ((bytesRead = reader.read(buffer)) != -1) {
out.append(buffer, 0, bytesRead);
}
return out.toString();
}
}

View File

@@ -0,0 +1 @@
org.springframework.boot.configurationprocessor.ConfigurationMetadataAnnotationProcessor