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
This commit is contained in:
@@ -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));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,56 @@
|
||||
/*
|
||||
* 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;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
/*
|
||||
* 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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,130 @@
|
||||
/*
|
||||
* 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;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,80 @@
|
||||
/*
|
||||
* 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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,83 @@
|
||||
/*
|
||||
* 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;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
/*
|
||||
* 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;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
/*
|
||||
* 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));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
/*
|
||||
* 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,
|
||||
|
||||
@@ -0,0 +1,124 @@
|
||||
/*
|
||||
* 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;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
/*
|
||||
* 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"));
|
||||
}
|
||||
|
||||
|
||||
Binary file not shown.
@@ -0,0 +1,78 @@
|
||||
/*
|
||||
* 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();
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user