diff --git a/spring-boot-tools/spring-boot-configuration-processor/src/main/java/org/springframework/boot/configurationprocessor/ConfigurationMetadataAnnotationProcessor.java b/spring-boot-tools/spring-boot-configuration-processor/src/main/java/org/springframework/boot/configurationprocessor/ConfigurationMetadataAnnotationProcessor.java index eaee1f5e5e..f2a08639d4 100644 --- a/spring-boot-tools/spring-boot-configuration-processor/src/main/java/org/springframework/boot/configurationprocessor/ConfigurationMetadataAnnotationProcessor.java +++ b/spring-boot-tools/spring-boot-configuration-processor/src/main/java/org/springframework/boot/configurationprocessor/ConfigurationMetadataAnnotationProcessor.java @@ -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 fieldValues = getFieldValues(element); + processSimpleTypes(prefix, element, members, fieldValues); processNestedTypes(prefix, element, members); } + private Map 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 fieldValues) { for (Map.Entry 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)); } } } diff --git a/spring-boot-tools/spring-boot-configuration-processor/src/main/java/org/springframework/boot/configurationprocessor/fieldvalues/FieldValuesParser.java b/spring-boot-tools/spring-boot-configuration-processor/src/main/java/org/springframework/boot/configurationprocessor/fieldvalues/FieldValuesParser.java new file mode 100644 index 0000000000..e2e73d3a7f --- /dev/null +++ b/spring-boot-tools/spring-boot-configuration-processor/src/main/java/org/springframework/boot/configurationprocessor/fieldvalues/FieldValuesParser.java @@ -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 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 getFieldValues(TypeElement element) throws Exception; + +} diff --git a/spring-boot-tools/spring-boot-configuration-processor/src/main/java/org/springframework/boot/configurationprocessor/fieldvalues/javac/ExpressionTree.java b/spring-boot-tools/spring-boot-configuration-processor/src/main/java/org/springframework/boot/configurationprocessor/fieldvalues/javac/ExpressionTree.java new file mode 100644 index 0000000000..6ad9cc77f2 --- /dev/null +++ b/spring-boot-tools/spring-boot-configuration-processor/src/main/java/org/springframework/boot/configurationprocessor/fieldvalues/javac/ExpressionTree.java @@ -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; + } + +} diff --git a/spring-boot-tools/spring-boot-configuration-processor/src/main/java/org/springframework/boot/configurationprocessor/fieldvalues/javac/JavaCompilerFieldValuesParser.java b/spring-boot-tools/spring-boot-configuration-processor/src/main/java/org/springframework/boot/configurationprocessor/fieldvalues/javac/JavaCompilerFieldValuesParser.java new file mode 100644 index 0000000000..8f7e28b1df --- /dev/null +++ b/spring-boot-tools/spring-boot-configuration-processor/src/main/java/org/springframework/boot/configurationprocessor/fieldvalues/javac/JavaCompilerFieldValuesParser.java @@ -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 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> WRAPPER_TYPES; + static { + Map> 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, Object> DEFAULT_TYPE_VALUES; + static { + Map, Object> values = new HashMap, 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 WELL_KNOWN_STATIC_FINALS; + static { + Map values = new HashMap(); + values.put("Boolean.TRUE", true); + values.put("Boolean.FALSE", false); + WELL_KNOWN_STATIC_FINALS = Collections.unmodifiableMap(values); + } + + private final Map fieldValues = new HashMap(); + + private final Map staticFinals = new HashMap(); + + @Override + public void visitVariable(VariableTree variable) throws Exception { + Set 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 getFieldValues() { + return this.fieldValues; + } + + } + +} diff --git a/spring-boot-tools/spring-boot-configuration-processor/src/main/java/org/springframework/boot/configurationprocessor/fieldvalues/javac/ReflectionWrapper.java b/spring-boot-tools/spring-boot-configuration-processor/src/main/java/org/springframework/boot/configurationprocessor/fieldvalues/javac/ReflectionWrapper.java new file mode 100644 index 0000000000..f4f3b19101 --- /dev/null +++ b/spring-boot-tools/spring-boot-configuration-processor/src/main/java/org/springframework/boot/configurationprocessor/fieldvalues/javac/ReflectionWrapper.java @@ -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); + } + } + +} diff --git a/spring-boot-tools/spring-boot-configuration-processor/src/main/java/org/springframework/boot/configurationprocessor/fieldvalues/javac/Tree.java b/spring-boot-tools/spring-boot-configuration-processor/src/main/java/org/springframework/boot/configurationprocessor/fieldvalues/javac/Tree.java new file mode 100644 index 0000000000..86c197f762 --- /dev/null +++ b/spring-boot-tools/spring-boot-configuration-processor/src/main/java/org/springframework/boot/configurationprocessor/fieldvalues/javac/Tree.java @@ -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; + } + + } + +} diff --git a/spring-boot-tools/spring-boot-configuration-processor/src/main/java/org/springframework/boot/configurationprocessor/fieldvalues/javac/TreeVisitor.java b/spring-boot-tools/spring-boot-configuration-processor/src/main/java/org/springframework/boot/configurationprocessor/fieldvalues/javac/TreeVisitor.java new file mode 100644 index 0000000000..b73cbc28d5 --- /dev/null +++ b/spring-boot-tools/spring-boot-configuration-processor/src/main/java/org/springframework/boot/configurationprocessor/fieldvalues/javac/TreeVisitor.java @@ -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; + +} diff --git a/spring-boot-tools/spring-boot-configuration-processor/src/main/java/org/springframework/boot/configurationprocessor/fieldvalues/javac/Trees.java b/spring-boot-tools/spring-boot-configuration-processor/src/main/java/org/springframework/boot/configurationprocessor/fieldvalues/javac/Trees.java new file mode 100644 index 0000000000..639ecfc184 --- /dev/null +++ b/spring-boot-tools/spring-boot-configuration-processor/src/main/java/org/springframework/boot/configurationprocessor/fieldvalues/javac/Trees.java @@ -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)); + } + +} diff --git a/spring-boot-tools/spring-boot-configuration-processor/src/main/java/org/springframework/boot/configurationprocessor/fieldvalues/javac/VariableTree.java b/spring-boot-tools/spring-boot-configuration-processor/src/main/java/org/springframework/boot/configurationprocessor/fieldvalues/javac/VariableTree.java new file mode 100644 index 0000000000..4817a4a78e --- /dev/null +++ b/spring-boot-tools/spring-boot-configuration-processor/src/main/java/org/springframework/boot/configurationprocessor/fieldvalues/javac/VariableTree.java @@ -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 getModifierFlags() throws Exception { + Object modifiers = findMethod("getModifiers").invoke(getInstance()); + if (modifiers == null) { + return Collections.emptySet(); + } + return (Set) findMethod(findClass("com.sun.source.tree.ModifiersTree"), + "getFlags").invoke(modifiers); + } + +} diff --git a/spring-boot-tools/spring-boot-configuration-processor/src/main/java/org/springframework/boot/configurationprocessor/metadata/ItemMetadata.java b/spring-boot-tools/spring-boot-configuration-processor/src/main/java/org/springframework/boot/configurationprocessor/metadata/ItemMetadata.java index e0b4fd309f..f261f4a9b5 100644 --- a/spring-boot-tools/spring-boot-configuration-processor/src/main/java/org/springframework/boot/configurationprocessor/metadata/ItemMetadata.java +++ b/spring-boot-tools/spring-boot-configuration-processor/src/main/java/org/springframework/boot/configurationprocessor/metadata/ItemMetadata.java @@ -38,8 +38,11 @@ public class ItemMetadata implements Comparable { 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 { 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 { 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 { 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); } /** diff --git a/spring-boot-tools/spring-boot-configuration-processor/src/main/java/org/springframework/boot/configurationprocessor/metadata/JsonMarshaller.java b/spring-boot-tools/spring-boot-configuration-processor/src/main/java/org/springframework/boot/configurationprocessor/metadata/JsonMarshaller.java index 7d6f345996..c05f7b6156 100644 --- a/spring-boot-tools/spring-boot-configuration-processor/src/main/java/org/springframework/boot/configurationprocessor/metadata/JsonMarshaller.java +++ b/spring-boot-tools/spring-boot-configuration-processor/src/main/java/org/springframework/boot/configurationprocessor/metadata/JsonMarshaller.java @@ -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 { diff --git a/spring-boot-tools/spring-boot-configuration-processor/src/test/java/org/springframework/boot/configurationprocessor/ConfigurationMetadataAnnotationProcessorTests.java b/spring-boot-tools/spring-boot-configuration-processor/src/test/java/org/springframework/boot/configurationprocessor/ConfigurationMetadataAnnotationProcessorTests.java index f90af42742..023692e7e1 100644 --- a/spring-boot-tools/spring-boot-configuration-processor/src/test/java/org/springframework/boot/configurationprocessor/ConfigurationMetadataAnnotationProcessorTests.java +++ b/spring-boot-tools/spring-boot-configuration-processor/src/test/java/org/springframework/boot/configurationprocessor/ConfigurationMetadataAnnotationProcessorTests.java @@ -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( diff --git a/spring-boot-tools/spring-boot-configuration-processor/src/test/java/org/springframework/boot/configurationprocessor/ConfigurationMetadataMatchers.java b/spring-boot-tools/spring-boot-configuration-processor/src/test/java/org/springframework/boot/configurationprocessor/ConfigurationMetadataMatchers.java index c796172c13..0ffe042db4 100644 --- a/spring-boot-tools/spring-boot-configuration-processor/src/test/java/org/springframework/boot/configurationprocessor/ConfigurationMetadataMatchers.java +++ b/spring-boot-tools/spring-boot-configuration-processor/src/test/java/org/springframework/boot/configurationprocessor/ConfigurationMetadataMatchers.java @@ -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 withDefaultValue(Object defaultValue) { + return new ContainsItemMatcher(this.itemType, this.name, this.type, + this.sourceType, this.description, defaultValue); } private ItemMetadata getFirstPropertyWithName(ConfigurationMetadata metadata, diff --git a/spring-boot-tools/spring-boot-configuration-processor/src/test/java/org/springframework/boot/configurationprocessor/fieldvalues/AbstractFieldValuesProcessorTests.java b/spring-boot-tools/spring-boot-configuration-processor/src/test/java/org/springframework/boot/configurationprocessor/fieldvalues/AbstractFieldValuesProcessorTests.java new file mode 100644 index 0000000000..7cd2fa2e1e --- /dev/null +++ b/spring-boot-tools/spring-boot-configuration-processor/src/test/java/org/springframework/boot/configurationprocessor/fieldvalues/AbstractFieldValuesProcessorTests.java @@ -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 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 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 values = new HashMap(); + + @Override + public synchronized void init(ProcessingEnvironment env) { + this.processor = createProcessor(env); + } + + @Override + public boolean process(Set 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 getValues() { + return this.values; + } + + } + +} diff --git a/spring-boot-tools/spring-boot-configuration-processor/src/test/java/org/springframework/boot/configurationprocessor/fieldvalues/javac/JavaCompilerFieldValuesProcessorTests.java b/spring-boot-tools/spring-boot-configuration-processor/src/test/java/org/springframework/boot/configurationprocessor/fieldvalues/javac/JavaCompilerFieldValuesProcessorTests.java new file mode 100644 index 0000000000..bc1424ac74 --- /dev/null +++ b/spring-boot-tools/spring-boot-configuration-processor/src/test/java/org/springframework/boot/configurationprocessor/fieldvalues/javac/JavaCompilerFieldValuesProcessorTests.java @@ -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(); + } + } + +} diff --git a/spring-boot-tools/spring-boot-configuration-processor/src/test/java/org/springframework/boot/configurationprocessor/metadata/JsonMarshallerTests.java b/spring-boot-tools/spring-boot-configuration-processor/src/test/java/org/springframework/boot/configurationprocessor/metadata/JsonMarshallerTests.java index d7cf0efa2c..76a04631ff 100644 --- a/spring-boot-tools/spring-boot-configuration-processor/src/test/java/org/springframework/boot/configurationprocessor/metadata/JsonMarshallerTests.java +++ b/spring-boot-tools/spring-boot-configuration-processor/src/test/java/org/springframework/boot/configurationprocessor/metadata/JsonMarshallerTests.java @@ -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")); } diff --git a/spring-boot-tools/spring-boot-configuration-processor/src/test/java/org/springframework/boot/configurationsample/fieldvalues/FieldValues.class b/spring-boot-tools/spring-boot-configuration-processor/src/test/java/org/springframework/boot/configurationsample/fieldvalues/FieldValues.class new file mode 100644 index 0000000000..4b15ca5a34 Binary files /dev/null and b/spring-boot-tools/spring-boot-configuration-processor/src/test/java/org/springframework/boot/configurationsample/fieldvalues/FieldValues.class differ diff --git a/spring-boot-tools/spring-boot-configuration-processor/src/test/java/org/springframework/boot/configurationsample/fieldvalues/FieldValues.java b/spring-boot-tools/spring-boot-configuration-processor/src/test/java/org/springframework/boot/configurationsample/fieldvalues/FieldValues.java new file mode 100644 index 0000000000..f3ed74f9c6 --- /dev/null +++ b/spring-boot-tools/spring-boot-configuration-processor/src/test/java/org/springframework/boot/configurationsample/fieldvalues/FieldValues.java @@ -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(); + +}