Commit c73adcd1 authored by Phillip Webb's avatar Phillip Webb

Add a defaultValue attribute to config meta-data

Update `ConfigurationMetadataAnnotationProcessor` to include the
'defaultValue' of a property when possible. For example the
'defaultValue' or 'server.port' is '8080'.

Default values are detected by inspecting the field assignments of
@ConfigurationProperties items. In order to detect field values some
internals of the Java compiler are used. To save a dependency on
'tools.jar' internal javac classes are accessed using reflection.

See gh-1001
parent 884c058e
......@@ -19,6 +19,7 @@ package org.springframework.boot.configurationprocessor;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Set;
......@@ -38,9 +39,12 @@ 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.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;
......@@ -64,6 +68,8 @@ public class ConfigurationMetadataAnnotationProcessor extends AbstractProcessor
private TypeUtils typeUtils;
private FieldValuesParser fieldValuesParser;
protected String configurationPropertiesAnnotation() {
return CONFIGURATION_PROPERTIES_ANNOTATION;
}
......@@ -73,6 +79,18 @@ public class ConfigurationMetadataAnnotationProcessor extends AbstractProcessor
super.init(env);
this.metadata = new ConfigurationMetadata();
this.typeUtils = new TypeUtils(env);
try {
this.fieldValuesParser = new JavaCompilerFieldValuesParser(env);
}
catch (Throwable ex) {
this.fieldValuesParser = FieldValuesParser.NONE;
logWarning("Field value processing of @ConfigurationProperty meta-data is "
+ "not supported");
}
}
private void logWarning(String msg) {
this.processingEnv.getMessager().printMessage(Kind.WARNING, msg);
}
@Override
......@@ -126,12 +144,22 @@ public class ConfigurationMetadataAnnotationProcessor extends AbstractProcessor
private void processTypeElement(String prefix, TypeElement element) {
TypeElementMembers members = new TypeElementMembers(this.processingEnv, element);
processSimpleTypes(prefix, element, members);
Map<String, Object> fieldValues = getFieldValues(element);
processSimpleTypes(prefix, element, members, fieldValues);
processNestedTypes(prefix, element, members);
}
private Map<String, Object> getFieldValues(TypeElement element) {
try {
return this.fieldValuesParser.getFieldValues(element);
}
catch (Exception ex) {
return Collections.emptyMap();
}
}
private void processSimpleTypes(String prefix, TypeElement element,
TypeElementMembers members) {
TypeElementMembers members, Map<String, Object> fieldValues) {
for (Map.Entry<String, ExecutableElement> entry : members.getPublicGetters()
.entrySet()) {
String name = entry.getKey();
......@@ -143,8 +171,9 @@ public class ConfigurationMetadataAnnotationProcessor extends AbstractProcessor
String dataType = this.typeUtils.getType(getter.getReturnType());
String sourceType = this.typeUtils.getType(element);
String description = this.typeUtils.getJavaDoc(field);
Object defaultValue = fieldValues.get(name);
this.metadata.add(ItemMetadata.newProperty(prefix, name, dataType,
sourceType, null, description));
sourceType, null, description, defaultValue));
}
}
}
......
/*
* 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;
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
*/
public interface FieldValuesParser {
/**
* Implementation of {@link FieldValuesParser} that always returns an empty
* result.
*/
public static final FieldValuesParser NONE = new FieldValuesParser() {
@Override
public Map<String, Object> getFieldValues(TypeElement element) {
return 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;
}
/*
* 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.Method;
/**
* Reflection based access to {@code com.sun.source.tree.ExpressionTree}.
*
* @author Phillip Webb
* @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");
public ExpressionTree(Object instance) {
super(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;
}
}
/*
* 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.util.Collections;
import java.util.HashMap;
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
* @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();
}
private static class FieldCollector implements TreeVisitor {
private static final Map<String, Class<?>> WRAPPER_TYPES;
static {
Map<String, Class<?>> types = new HashMap<String, Class<?>>();
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> DEFAULT_TYPE_VALUES;
static {
Map<Class<?>, Object> values = new HashMap<Class<?>, Object>();
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);
DEFAULT_TYPE_VALUES = Collections.unmodifiableMap(values);
}
private static final Map<String, Object> WELL_KNOWN_STATIC_FINALS;
static {
Map<String, Object> values = new HashMap<String, Object>();
values.put("Boolean.TRUE", true);
values.put("Boolean.FALSE", false);
WELL_KNOWN_STATIC_FINALS = Collections.unmodifiableMap(values);
}
private final Map<String, Object> fieldValues = new HashMap<String, Object>();
private final Map<String, Object> staticFinals = new HashMap<String, Object>();
@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());
if (initializer != null) {
if (initializer.getLiteralValue() != null) {
return initializer.getLiteralValue();
}
if (initializer.getKind().equals("IDENTIFIER")) {
return this.staticFinals.get(initializer.toString());
}
if (initializer.getKind().equals("MEMBER_SELECT")) {
return WELL_KNOWN_STATIC_FINALS.get(initializer.toString());
}
}
return DEFAULT_TYPE_VALUES.get(wrapperType);
}
public Map<String, Object> getFieldValues() {
return this.fieldValues;
}
}
}
/*
* 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.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;
public ReflectionWrapper(Object instance) {
this.type = instance.getClass();
this.instance = instance;
}
public ReflectionWrapper(String type, Object instance) {
this.type = findClass(instance.getClass().getClassLoader(), type);
this.instance = 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);
}
}
}
/*
* 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");
public 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;
public 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;
}
}
}
/*
* 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;
/**
* 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;
}
/*
* 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.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
*/
class Trees extends ReflectionWrapper {
private Trees(Object instance) {
super(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));
}
}
/*
* 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.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 {
public VariableTree(Object instance) {
super(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);
}
}
......@@ -38,8 +38,11 @@ public class ItemMetadata implements Comparable<ItemMetadata> {
private String sourceMethod;
private Object defaultValue;
ItemMetadata(ItemType itemType, String prefix, String name, String type,
String sourceType, String sourceMethod, String description) {
String sourceType, String sourceMethod, String description,
Object defaultValue) {
super();
this.itemType = itemType;
this.name = buildName(prefix, name);
......@@ -47,6 +50,7 @@ public class ItemMetadata implements Comparable<ItemMetadata> {
this.sourceType = sourceType;
this.sourceMethod = sourceMethod;
this.description = description;
this.defaultValue = defaultValue;
}
private String buildName(String prefix, String name) {
......@@ -85,12 +89,17 @@ public class ItemMetadata implements Comparable<ItemMetadata> {
return this.description;
}
public Object getDefaultValue() {
return this.defaultValue;
}
@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);
return string.toString();
}
......@@ -109,13 +118,14 @@ public class ItemMetadata implements Comparable<ItemMetadata> {
public static ItemMetadata newGroup(String name, String type, String sourceType,
String sourceMethod) {
return new ItemMetadata(ItemType.GROUP, name, null, type, sourceType,
sourceMethod, null);
sourceMethod, null, null);
}
public static ItemMetadata newProperty(String prefix, String name, String type,
String sourceType, String sourceMethod, String description) {
String sourceType, String sourceMethod, String description,
Object defaultValue) {
return new ItemMetadata(ItemType.PROPERTY, prefix, name, type, sourceType,
sourceMethod, description);
sourceMethod, description, defaultValue);
}
/**
......
......@@ -67,6 +67,7 @@ public class JsonMarshaller {
putIfPresent(jsonObject, "description", item.getDescription());
putIfPresent(jsonObject, "sourceType", item.getSourceType());
putIfPresent(jsonObject, "sourceMethod", item.getSourceMethod());
putIfPresent(jsonObject, "defaultValue", item.getDefaultValue());
return jsonObject;
}
......@@ -101,8 +102,9 @@ public class JsonMarshaller {
String description = object.optString("description", null);
String sourceType = object.optString("sourceType", null);
String sourceMethod = object.optString("sourceMethod", null);
Object defaultValue = object.opt("defaultValue");
return new ItemMetadata(itemType, name, null, type, sourceType, sourceMethod,
description);
description, defaultValue);
}
private String toString(InputStream inputStream) throws IOException {
......
......@@ -71,9 +71,10 @@ public class ConfigurationMetadataAnnotationProcessorTests {
assertThat(metadata, containsGroup("simple").fromSource(SimpleProperties.class));
assertThat(
metadata,
containsProperty("simple.the-name", String.class).fromSource(
SimpleProperties.class).withDescription(
"The name of this simple properties."));
containsProperty("simple.the-name", String.class)
.fromSource(SimpleProperties.class)
.withDescription("The name of this simple properties.")
.withDefaultValue("boot"));
assertThat(
metadata,
containsProperty("simple.flag", Boolean.class).fromSource(
......@@ -171,7 +172,8 @@ public class ConfigurationMetadataAnnotationProcessorTests {
@Test
public void simpleMethodConfig() throws Exception {
ConfigurationMetadata metadata = compile(SimpleMethodConfig.class);
assertThat(metadata, containsGroup("foo").fromSource(SimpleMethodConfig.class));
assertThat(metadata, containsGroup("foo")
.fromSource(SimpleMethodConfig.class));
assertThat(
metadata,
containsProperty("foo.name", String.class).fromSource(
......
......@@ -66,17 +66,20 @@ public class ConfigurationMetadataMatchers {
private final String description;
private final Object defaultValue;
public ContainsItemMatcher(ItemType itemType, String name) {
this(itemType, name, null, null, null);
this(itemType, name, null, null, null, null);
}
public ContainsItemMatcher(ItemType itemType, String name, String type,
Class<?> sourceType, String description) {
Class<?> sourceType, String description, Object defaultValue) {
this.itemType = itemType;
this.name = name;
this.type = type;
this.sourceType = sourceType;
this.description = description;
this.defaultValue = defaultValue;
}
@Override
......@@ -93,6 +96,10 @@ public class ConfigurationMetadataMatchers {
&& !this.sourceType.getName().equals(itemMetadata.getSourceType())) {
return false;
}
if (this.defaultValue != null
&& !this.defaultValue.equals(itemMetadata.getDefaultValue())) {
return false;
}
if (this.description != null
&& !this.description.equals(itemMetadata.getDescription())) {
return false;
......@@ -121,6 +128,9 @@ public class ConfigurationMetadataMatchers {
if (this.sourceType != null) {
description.appendText(" sourceType ").appendValue(this.sourceType);
}
if (this.defaultValue != null) {
description.appendText(" defaultValue ").appendValue(this.defaultValue);
}
if (this.description != null) {
description.appendText(" description ").appendValue(this.description);
}
......@@ -128,22 +138,27 @@ public class ConfigurationMetadataMatchers {
public ContainsItemMatcher ofType(Class<?> dataType) {
return new ContainsItemMatcher(this.itemType, this.name, dataType.getName(),
this.sourceType, this.description);
this.sourceType, this.description, this.defaultValue);
}
public ContainsItemMatcher ofDataType(String dataType) {
return new ContainsItemMatcher(this.itemType, this.name, dataType,
this.sourceType, this.description);
this.sourceType, this.description, this.defaultValue);
}
public ContainsItemMatcher fromSource(Class<?> sourceType) {
return new ContainsItemMatcher(this.itemType, this.name, this.type,
sourceType, this.description);
sourceType, this.description, this.defaultValue);
}
public ContainsItemMatcher withDescription(String description) {
return new ContainsItemMatcher(this.itemType, this.name, this.type,
this.sourceType, description);
this.sourceType, description, this.defaultValue);
}
public Matcher<? super ConfigurationMetadata> withDefaultValue(Object defaultValue) {
return new ContainsItemMatcher(this.itemType, this.name, this.type,
this.sourceType, this.description, defaultValue);
}
private ItemMetadata getFirstPropertyWithName(ConfigurationMetadata metadata,
......
/*
* 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;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import javax.annotation.processing.AbstractProcessor;
import javax.annotation.processing.ProcessingEnvironment;
import javax.annotation.processing.RoundEnvironment;
import javax.annotation.processing.SupportedAnnotationTypes;
import javax.annotation.processing.SupportedSourceVersion;
import javax.lang.model.SourceVersion;
import javax.lang.model.element.Element;
import javax.lang.model.element.TypeElement;
import org.hamcrest.Matcher;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.TemporaryFolder;
import org.springframework.boot.configurationprocessor.TestCompiler;
import org.springframework.boot.configurationsample.fieldvalues.FieldValues;
import static org.hamcrest.Matchers.equalTo;
import static org.hamcrest.Matchers.nullValue;
import static org.junit.Assert.assertThat;
/**
* Abstract base class for {@link FieldValuesParser} tests.
*
* @author Phillip Webb
*/
public abstract class AbstractFieldValuesProcessorTests {
@Rule
public TemporaryFolder temporaryFolder = new TemporaryFolder();
protected abstract FieldValuesParser createProcessor(ProcessingEnvironment env);
@Test
public void getFieldValues() throws Exception {
TestProcessor processor = new TestProcessor();
TestCompiler compiler = new TestCompiler(this.temporaryFolder);
compiler.getTask(FieldValues.class).call(processor);
Map<String, Object> values = processor.getValues();
assertThat(values.get("string"), equalToObject("1"));
assertThat(values.get("stringNone"), nullValue());
assertThat(values.get("stringConst"), equalToObject("c"));
assertThat(values.get("bool"), equalToObject(true));
assertThat(values.get("boolNone"), equalToObject(false));
assertThat(values.get("boolConst"), equalToObject(true));
assertThat(values.get("boolObject"), equalToObject(true));
assertThat(values.get("boolObjectNone"), nullValue());
assertThat(values.get("boolObjectConst"), equalToObject(true));
assertThat(values.get("integer"), equalToObject(1));
assertThat(values.get("integerNone"), equalToObject(0));
assertThat(values.get("integerConst"), equalToObject(2));
assertThat(values.get("integerObject"), equalToObject(3));
assertThat(values.get("integerObjectNone"), nullValue());
assertThat(values.get("integerObjectConst"), equalToObject(4));
assertThat(values.get("object"), equalToObject(123));
assertThat(values.get("objectNone"), nullValue());
assertThat(values.get("objectConst"), equalToObject("c"));
assertThat(values.get("objectInstance"), nullValue());
}
private Matcher<Object> equalToObject(Object object) {
return equalTo(object);
}
@SupportedAnnotationTypes({ "org.springframework.boot.configurationsample.ConfigurationProperties" })
@SupportedSourceVersion(SourceVersion.RELEASE_6)
private class TestProcessor extends AbstractProcessor {
private FieldValuesParser processor;
private Map<String, Object> values = new HashMap<String, Object>();
@Override
public synchronized void init(ProcessingEnvironment env) {
this.processor = createProcessor(env);
}
@Override
public boolean process(Set<? extends TypeElement> annotations,
RoundEnvironment roundEnv) {
for (TypeElement annotation : annotations) {
for (Element element : roundEnv.getElementsAnnotatedWith(annotation)) {
if (element instanceof TypeElement) {
try {
this.values.putAll(this.processor
.getFieldValues((TypeElement) element));
}
catch (Exception ex) {
throw new IllegalStateException(ex);
}
}
}
}
return false;
}
public Map<String, Object> getValues() {
return this.values;
}
}
}
/*
* 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 javax.annotation.processing.ProcessingEnvironment;
import org.springframework.boot.configurationprocessor.fieldvalues.AbstractFieldValuesProcessorTests;
import org.springframework.boot.configurationprocessor.fieldvalues.FieldValuesParser;
import static org.junit.Assume.assumeNoException;
/**
* Tests for {@link JavaCompilerFieldValuesParser}.
*
* @author Phillip Webb
*/
public class JavaCompilerFieldValuesProcessorTests extends
AbstractFieldValuesProcessorTests {
@Override
protected FieldValuesParser createProcessor(ProcessingEnvironment env) {
try {
return new JavaCompilerFieldValuesParser(env);
}
catch (Throwable ex) {
assumeNoException(ex);
throw new IllegalStateException();
}
}
}
......@@ -38,10 +38,11 @@ public class JsonMarshallerTests {
public void marshallAndUnmarshal() throws IOException {
ConfigurationMetadata metadata = new ConfigurationMetadata();
metadata.add(ItemMetadata.newProperty("a", "b", StringBuffer.class.getName(),
InputStream.class.getName(), "sourceMethod", "desc"));
metadata.add(ItemMetadata.newProperty("b.c.d", null, null, null, null, null));
metadata.add(ItemMetadata.newProperty("c", null, null, null, null, null));
metadata.add(ItemMetadata.newProperty("d", null, null, null, null, null));
InputStream.class.getName(), "sourceMethod", "desc", "x"));
metadata.add(ItemMetadata
.newProperty("b.c.d", null, null, null, null, null, null));
metadata.add(ItemMetadata.newProperty("c", null, null, null, null, null, 123));
metadata.add(ItemMetadata.newProperty("d", null, null, null, null, null, true));
metadata.add(ItemMetadata.newGroup("d", null, null, null));
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
JsonMarshaller marshaller = new JsonMarshaller();
......@@ -51,10 +52,10 @@ public class JsonMarshallerTests {
outputStream.toByteArray()));
assertThat(read,
containsProperty("a.b", StringBuffer.class).fromSource(InputStream.class)
.withDescription("desc"));
.withDescription("desc").withDefaultValue("x"));
assertThat(read, containsProperty("b.c.d"));
assertThat(read, containsProperty("c"));
assertThat(read, containsProperty("d"));
assertThat(read, containsProperty("c").withDefaultValue(123));
assertThat(read, containsProperty("d").withDefaultValue(true));
assertThat(read, containsGroup("d"));
}
......
/*
* 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.configurationsample.fieldvalues;
import org.springframework.boot.configurationsample.ConfigurationProperties;
/**
* Sample object containing fields with initial values.
*
* @author Phillip Webb
*/
@SuppressWarnings("unused")
@ConfigurationProperties
public class FieldValues {
private static final String STRING_CONST = "c";
private static final boolean BOOLEAN_CONST = true;
private static final Boolean BOOLEAN_OBJ_CONST = true;
private static final int INTEGER_CONST = 2;
private static final Integer INTEGER_OBJ_CONST = 4;
private String string = "1";
private String stringNone;
private String stringConst = STRING_CONST;
private boolean bool = true;
private boolean boolNone;
private boolean boolConst = BOOLEAN_CONST;
private Boolean boolObject = Boolean.TRUE;
private Boolean boolObjectNone;
private Boolean boolObjectConst = BOOLEAN_OBJ_CONST;
private int integer = 1;
private int integerNone;
private int integerConst = INTEGER_CONST;
private Integer integerObject = 3;
private Integer integerObjectNone;
private Integer integerObjectConst = INTEGER_OBJ_CONST;
private Object object = 123;
private Object objectNone;
private Object objectConst = STRING_CONST;
private Object objectInstance = new StringBuffer();
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment