From 7d57cb70817343bd0947b8c30f57fbf2f5830631 Mon Sep 17 00:00:00 2001 From: Stephane Nicoll Date: Wed, 3 Dec 2014 15:25:48 +0100 Subject: [PATCH] Add default value for arrays Previously, a property holding an array did not have a proper default value in the meta-data even though the related field was initialized properly. An explicit support for arrays has been added. The "defaultValue" now holds the default value for singular properties or an array of values for array-based properties. If the value is initalized with an empty array, the default value is an empty array as well. Closes gh-1996 --- .../fieldvalues/javac/ExpressionTree.java | 21 +++++++++++ .../javac/JavaCompilerFieldValuesParser.java | 36 +++++++++++++------ .../metadata/JsonMarshaller.java | 35 ++++++++++++++++-- ...ationMetadataAnnotationProcessorTests.java | 3 +- .../ConfigurationMetadataMatchers.java | 8 ++--- .../AbstractFieldValuesProcessorTests.java | 7 ++++ .../metadata/JsonMarshallerTests.java | 16 +++++++-- .../fieldvalues/FieldValues.java | 15 ++++++++ 8 files changed, 121 insertions(+), 20 deletions(-) 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 index 6ad9cc77f2..101cc06492 100644 --- 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 @@ -17,6 +17,8 @@ package org.springframework.boot.configurationprocessor.fieldvalues.javac; import java.lang.reflect.Method; +import java.util.ArrayList; +import java.util.List; /** * Reflection based access to {@code com.sun.source.tree.ExpressionTree}. @@ -30,6 +32,10 @@ class ExpressionTree extends ReflectionWrapper { private final Method literalValueMethod = findMethod(this.literalTreeType, "getValue"); + private final Class newArrayTreeType = findClass("com.sun.source.tree.NewArrayTree"); + + private final Method arrayValueMethod = findMethod(this.newArrayTreeType, "getInitializers"); + public ExpressionTree(Object instance) { super(instance); } @@ -45,4 +51,19 @@ class ExpressionTree extends ReflectionWrapper { return null; } + public List getArrayExpression() throws Exception { + if (this.newArrayTreeType.isAssignableFrom(getInstance().getClass())) { + List elements = (List) this.arrayValueMethod.invoke(getInstance()); + List result = new ArrayList(); + if (elements == null) { + return result; + } + for (Object element : elements) { + result.add(new ExpressionTree(element)); + } + return result; + } + 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 index 8f7e28b1df..ae4b51a1c7 100644 --- 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 @@ -18,6 +18,7 @@ package org.springframework.boot.configurationprocessor.fieldvalues.javac; import java.util.Collections; import java.util.HashMap; +import java.util.List; import java.util.Map; import java.util.Set; @@ -107,18 +108,33 @@ public class JavaCompilerFieldValuesParser implements FieldValuesParser { private Object getValue(VariableTree variable) throws Exception { ExpressionTree initializer = variable.getInitializer(); Class wrapperType = WRAPPER_TYPES.get(variable.getType()); + Object defaultValue = DEFAULT_TYPE_VALUES.get(wrapperType); 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 getValue(initializer, defaultValue); } - return DEFAULT_TYPE_VALUES.get(wrapperType); + return defaultValue; + } + + private Object getValue(ExpressionTree expression, Object defaultValue) throws Exception { + Object literalValue = expression.getLiteralValue(); + if (literalValue != null) { + return literalValue; + } + List arrayValues = expression.getArrayExpression(); + if (arrayValues != null) { + Object[] result = new Object[arrayValues.size()]; + for (int i = 0; i < arrayValues.size(); i++) { + result[i] = getValue(arrayValues.get(i), null); + } + return result; + } + if (expression.getKind().equals("IDENTIFIER")) { + return this.staticFinals.get(expression.toString()); + } + if (expression.getKind().equals("MEMBER_SELECT")) { + return WELL_KNOWN_STATIC_FINALS.get(expression.toString()); + } + return defaultValue; } public Map getFieldValues() { 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 14e38706c0..843f0a7613 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 @@ -20,6 +20,7 @@ import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.io.OutputStream; +import java.lang.reflect.Array; import java.nio.charset.Charset; import java.util.LinkedHashSet; import java.util.Set; @@ -67,7 +68,10 @@ public class JsonMarshaller { putIfPresent(jsonObject, "description", item.getDescription()); putIfPresent(jsonObject, "sourceType", item.getSourceType()); putIfPresent(jsonObject, "sourceMethod", item.getSourceMethod()); - putIfPresent(jsonObject, "defaultValue", item.getDefaultValue()); + Object defaultValue = item.getDefaultValue(); + if (defaultValue != null) { + putDefaultValue(jsonObject, defaultValue); + } if (item.isDeprecated()) { jsonObject.put("deprecated", true); } @@ -80,6 +84,20 @@ public class JsonMarshaller { } } + private void putDefaultValue(JSONObject jsonObject, Object value) { + Object defaultValue = value; + if (value.getClass().isArray()) { + JSONArray array = new JSONArray(); + int length = Array.getLength(value); + for (int i = 0; i < length; i++) { + array.put(Array.get(value, i)); + } + defaultValue = array; + + } + jsonObject.put("defaultValue", defaultValue); + } + public ConfigurationMetadata read(InputStream inputStream) throws IOException { ConfigurationMetadata metadata = new ConfigurationMetadata(); JSONObject object = new JSONObject(toString(inputStream)); @@ -105,12 +123,25 @@ 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"); + Object defaultValue = readDefaultValue(object); boolean deprecated = object.optBoolean("deprecated"); return new ItemMetadata(itemType, name, null, type, sourceType, sourceMethod, description, defaultValue, deprecated); } + private Object readDefaultValue(JSONObject object) { + Object defaultValue = object.opt("defaultValue"); + if (defaultValue instanceof JSONArray) { + JSONArray array = (JSONArray) defaultValue; + Object[] content = new Object[array.length()]; + for (int i = 0; i < array.length(); i++) { + content[i] = array.get(i); + } + return content; + } + return defaultValue; + } + private String toString(InputStream inputStream) throws IOException { StringBuilder out = new StringBuilder(); InputStreamReader reader = new InputStreamReader(inputStream, UTF_8); 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 26497113f7..0e55330513 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 @@ -43,6 +43,7 @@ import org.springframework.boot.configurationsample.specific.InnerClassPropertie import org.springframework.boot.configurationsample.specific.InnerClassRootConfig; import org.springframework.boot.configurationsample.specific.SimplePojo; +import static org.hamcrest.CoreMatchers.is; import static org.hamcrest.Matchers.empty; import static org.hamcrest.Matchers.equalTo; import static org.hamcrest.Matchers.not; @@ -77,7 +78,7 @@ public class ConfigurationMetadataAnnotationProcessorTests { containsProperty("simple.the-name", String.class) .fromSource(SimpleProperties.class) .withDescription("The name of this simple properties.") - .withDefaultValue("boot").withDeprecated()); + .withDefaultValue(is("boot")).withDeprecated()); assertThat( metadata, containsProperty("simple.flag", Boolean.class) 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 353ee1d693..57b04b8408 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,7 +66,7 @@ public class ConfigurationMetadataMatchers { private final String description; - private final Object defaultValue; + private final Matcher defaultValue; private final boolean deprecated; @@ -75,7 +75,7 @@ public class ConfigurationMetadataMatchers { } public ContainsItemMatcher(ItemType itemType, String name, String type, - Class sourceType, String description, Object defaultValue, + Class sourceType, String description, Matcher defaultValue, boolean deprecated) { this.itemType = itemType; this.name = name; @@ -101,7 +101,7 @@ public class ConfigurationMetadataMatchers { return false; } if (this.defaultValue != null - && !this.defaultValue.equals(itemMetadata.getDefaultValue())) { + && !this.defaultValue.matches(itemMetadata.getDefaultValue())) { return false; } if (this.description != null @@ -169,7 +169,7 @@ public class ConfigurationMetadataMatchers { this.sourceType, description, this.defaultValue, this.deprecated); } - public ContainsItemMatcher withDefaultValue(Object defaultValue) { + public ContainsItemMatcher withDefaultValue(Matcher defaultValue) { return new ContainsItemMatcher(this.itemType, this.name, this.type, this.sourceType, this.description, defaultValue, this.deprecated); } 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 index 7cd2fa2e1e..536ad78c12 100644 --- 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 @@ -44,6 +44,7 @@ import static org.junit.Assert.assertThat; * Abstract base class for {@link FieldValuesParser} tests. * * @author Phillip Webb + * @author Stephane Nicoll */ public abstract class AbstractFieldValuesProcessorTests { @@ -77,6 +78,12 @@ public abstract class AbstractFieldValuesProcessorTests { assertThat(values.get("objectNone"), nullValue()); assertThat(values.get("objectConst"), equalToObject("c")); assertThat(values.get("objectInstance"), nullValue()); + assertThat(values.get("stringArray"), equalToObject(new Object[] {"FOO", "BAR"})); + assertThat(values.get("stringArrayNone"), nullValue()); + assertThat(values.get("stringEmptyArray"), equalToObject(new Object[0])); + assertThat(values.get("stringArrayConst"), equalToObject(new Object[]{"OK", "KO"})); + assertThat(values.get("stringArrayConstElements"), equalToObject(new Object[] {"c"})); + assertThat(values.get("integerArray"), equalToObject(new Object[] {42, 24})); } private Matcher equalToObject(Object object) { 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 7b5e838d28..6fa49a20e4 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 @@ -23,6 +23,9 @@ import java.io.InputStream; import org.junit.Test; +import static org.hamcrest.CoreMatchers.equalTo; +import static org.hamcrest.CoreMatchers.is; +import static org.hamcrest.Matchers.array; import static org.junit.Assert.assertThat; import static org.springframework.boot.configurationprocessor.ConfigurationMetadataMatchers.containsGroup; import static org.springframework.boot.configurationprocessor.ConfigurationMetadataMatchers.containsProperty; @@ -31,6 +34,7 @@ import static org.springframework.boot.configurationprocessor.ConfigurationMetad * Tests for {@link JsonMarshaller}. * * @author Phillip Webb + * @author Stephane Nicoll */ public class JsonMarshallerTests { @@ -45,6 +49,10 @@ public class JsonMarshallerTests { false)); metadata.add(ItemMetadata.newProperty("d", null, null, null, null, null, true, false)); + metadata.add(ItemMetadata.newProperty("e", null, null, null, null, null, new String[]{"y", "n"}, + false)); + metadata.add(ItemMetadata.newProperty("f", null, null, null, null, null, new Boolean[]{true, false}, + false)); metadata.add(ItemMetadata.newGroup("d", null, null, null)); ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); JsonMarshaller marshaller = new JsonMarshaller(); @@ -53,10 +61,12 @@ public class JsonMarshallerTests { outputStream.toByteArray())); assertThat(read, containsProperty("a.b", StringBuffer.class).fromSource(InputStream.class) - .withDescription("desc").withDefaultValue("x").withDeprecated()); + .withDescription("desc").withDefaultValue(is("x")).withDeprecated()); assertThat(read, containsProperty("b.c.d")); - assertThat(read, containsProperty("c").withDefaultValue(123)); - assertThat(read, containsProperty("d").withDefaultValue(true)); + assertThat(read, containsProperty("c").withDefaultValue(is(123))); + assertThat(read, containsProperty("d").withDefaultValue(is(true))); + assertThat(read, containsProperty("e").withDefaultValue(is(array(equalTo("y"), equalTo("n"))))); + assertThat(read, containsProperty("f").withDefaultValue(is(array(equalTo(true), equalTo(false))))); assertThat(read, containsGroup("d")); } 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 index f3ed74f9c6..655e352ca8 100644 --- 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 @@ -22,6 +22,7 @@ import org.springframework.boot.configurationsample.ConfigurationProperties; * Sample object containing fields with initial values. * * @author Phillip Webb + * @author Stephane Nicoll */ @SuppressWarnings("unused") @ConfigurationProperties @@ -37,6 +38,8 @@ public class FieldValues { private static final Integer INTEGER_OBJ_CONST = 4; + private static final String[] STRING_ARRAY_CONST = new String[] {"OK", "KO"}; + private String string = "1"; private String stringNone; @@ -75,4 +78,16 @@ public class FieldValues { private Object objectInstance = new StringBuffer(); + private String[] stringArray = new String[] {"FOO", "BAR"}; + + private String[] stringArrayNone; + + private String[] stringEmptyArray = new String[0]; + + private String[] stringArrayConst = STRING_ARRAY_CONST; + + private String[] stringArrayConstElements = new String[] { STRING_CONST }; + + private Integer[] integerArray = new Integer[] {42, 24}; + }