Commit 7d57cb70 authored by Stephane Nicoll's avatar Stephane Nicoll

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
parent 0b19884f
...@@ -17,6 +17,8 @@ ...@@ -17,6 +17,8 @@
package org.springframework.boot.configurationprocessor.fieldvalues.javac; package org.springframework.boot.configurationprocessor.fieldvalues.javac;
import java.lang.reflect.Method; import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.List;
/** /**
* Reflection based access to {@code com.sun.source.tree.ExpressionTree}. * Reflection based access to {@code com.sun.source.tree.ExpressionTree}.
...@@ -30,6 +32,10 @@ class ExpressionTree extends ReflectionWrapper { ...@@ -30,6 +32,10 @@ class ExpressionTree extends ReflectionWrapper {
private final Method literalValueMethod = findMethod(this.literalTreeType, "getValue"); 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) { public ExpressionTree(Object instance) {
super(instance); super(instance);
} }
...@@ -45,4 +51,19 @@ class ExpressionTree extends ReflectionWrapper { ...@@ -45,4 +51,19 @@ class ExpressionTree extends ReflectionWrapper {
return null; return null;
} }
public List<? extends ExpressionTree> getArrayExpression() throws Exception {
if (this.newArrayTreeType.isAssignableFrom(getInstance().getClass())) {
List<?> elements = (List<?>) this.arrayValueMethod.invoke(getInstance());
List<ExpressionTree> result = new ArrayList<ExpressionTree>();
if (elements == null) {
return result;
}
for (Object element : elements) {
result.add(new ExpressionTree(element));
}
return result;
}
return null;
}
} }
...@@ -18,6 +18,7 @@ package org.springframework.boot.configurationprocessor.fieldvalues.javac; ...@@ -18,6 +18,7 @@ package org.springframework.boot.configurationprocessor.fieldvalues.javac;
import java.util.Collections; import java.util.Collections;
import java.util.HashMap; import java.util.HashMap;
import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Set; import java.util.Set;
...@@ -107,18 +108,33 @@ public class JavaCompilerFieldValuesParser implements FieldValuesParser { ...@@ -107,18 +108,33 @@ public class JavaCompilerFieldValuesParser implements FieldValuesParser {
private Object getValue(VariableTree variable) throws Exception { private Object getValue(VariableTree variable) throws Exception {
ExpressionTree initializer = variable.getInitializer(); ExpressionTree initializer = variable.getInitializer();
Class<?> wrapperType = WRAPPER_TYPES.get(variable.getType()); Class<?> wrapperType = WRAPPER_TYPES.get(variable.getType());
Object defaultValue = DEFAULT_TYPE_VALUES.get(wrapperType);
if (initializer != null) { if (initializer != null) {
if (initializer.getLiteralValue() != null) { return getValue(initializer, defaultValue);
return initializer.getLiteralValue(); }
} return defaultValue;
if (initializer.getKind().equals("IDENTIFIER")) { }
return this.staticFinals.get(initializer.toString());
} private Object getValue(ExpressionTree expression, Object defaultValue) throws Exception {
if (initializer.getKind().equals("MEMBER_SELECT")) { Object literalValue = expression.getLiteralValue();
return WELL_KNOWN_STATIC_FINALS.get(initializer.toString()); if (literalValue != null) {
return literalValue;
}
List<? extends ExpressionTree> 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 DEFAULT_TYPE_VALUES.get(wrapperType); return defaultValue;
} }
public Map<String, Object> getFieldValues() { public Map<String, Object> getFieldValues() {
......
...@@ -20,6 +20,7 @@ import java.io.IOException; ...@@ -20,6 +20,7 @@ import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.io.InputStreamReader; import java.io.InputStreamReader;
import java.io.OutputStream; import java.io.OutputStream;
import java.lang.reflect.Array;
import java.nio.charset.Charset; import java.nio.charset.Charset;
import java.util.LinkedHashSet; import java.util.LinkedHashSet;
import java.util.Set; import java.util.Set;
...@@ -67,7 +68,10 @@ public class JsonMarshaller { ...@@ -67,7 +68,10 @@ public class JsonMarshaller {
putIfPresent(jsonObject, "description", item.getDescription()); putIfPresent(jsonObject, "description", item.getDescription());
putIfPresent(jsonObject, "sourceType", item.getSourceType()); putIfPresent(jsonObject, "sourceType", item.getSourceType());
putIfPresent(jsonObject, "sourceMethod", item.getSourceMethod()); putIfPresent(jsonObject, "sourceMethod", item.getSourceMethod());
putIfPresent(jsonObject, "defaultValue", item.getDefaultValue()); Object defaultValue = item.getDefaultValue();
if (defaultValue != null) {
putDefaultValue(jsonObject, defaultValue);
}
if (item.isDeprecated()) { if (item.isDeprecated()) {
jsonObject.put("deprecated", true); jsonObject.put("deprecated", true);
} }
...@@ -80,6 +84,20 @@ public class JsonMarshaller { ...@@ -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 { public ConfigurationMetadata read(InputStream inputStream) throws IOException {
ConfigurationMetadata metadata = new ConfigurationMetadata(); ConfigurationMetadata metadata = new ConfigurationMetadata();
JSONObject object = new JSONObject(toString(inputStream)); JSONObject object = new JSONObject(toString(inputStream));
...@@ -105,12 +123,25 @@ public class JsonMarshaller { ...@@ -105,12 +123,25 @@ public class JsonMarshaller {
String description = object.optString("description", null); String description = object.optString("description", null);
String sourceType = object.optString("sourceType", null); String sourceType = object.optString("sourceType", null);
String sourceMethod = object.optString("sourceMethod", null); String sourceMethod = object.optString("sourceMethod", null);
Object defaultValue = object.opt("defaultValue"); Object defaultValue = readDefaultValue(object);
boolean deprecated = object.optBoolean("deprecated"); boolean deprecated = object.optBoolean("deprecated");
return new ItemMetadata(itemType, name, null, type, sourceType, sourceMethod, return new ItemMetadata(itemType, name, null, type, sourceType, sourceMethod,
description, defaultValue, deprecated); 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 { private String toString(InputStream inputStream) throws IOException {
StringBuilder out = new StringBuilder(); StringBuilder out = new StringBuilder();
InputStreamReader reader = new InputStreamReader(inputStream, UTF_8); InputStreamReader reader = new InputStreamReader(inputStream, UTF_8);
......
...@@ -43,6 +43,7 @@ import org.springframework.boot.configurationsample.specific.InnerClassPropertie ...@@ -43,6 +43,7 @@ import org.springframework.boot.configurationsample.specific.InnerClassPropertie
import org.springframework.boot.configurationsample.specific.InnerClassRootConfig; import org.springframework.boot.configurationsample.specific.InnerClassRootConfig;
import org.springframework.boot.configurationsample.specific.SimplePojo; import org.springframework.boot.configurationsample.specific.SimplePojo;
import static org.hamcrest.CoreMatchers.is;
import static org.hamcrest.Matchers.empty; import static org.hamcrest.Matchers.empty;
import static org.hamcrest.Matchers.equalTo; import static org.hamcrest.Matchers.equalTo;
import static org.hamcrest.Matchers.not; import static org.hamcrest.Matchers.not;
...@@ -77,7 +78,7 @@ public class ConfigurationMetadataAnnotationProcessorTests { ...@@ -77,7 +78,7 @@ public class ConfigurationMetadataAnnotationProcessorTests {
containsProperty("simple.the-name", String.class) containsProperty("simple.the-name", String.class)
.fromSource(SimpleProperties.class) .fromSource(SimpleProperties.class)
.withDescription("The name of this simple properties.") .withDescription("The name of this simple properties.")
.withDefaultValue("boot").withDeprecated()); .withDefaultValue(is("boot")).withDeprecated());
assertThat( assertThat(
metadata, metadata,
containsProperty("simple.flag", Boolean.class) containsProperty("simple.flag", Boolean.class)
......
...@@ -66,7 +66,7 @@ public class ConfigurationMetadataMatchers { ...@@ -66,7 +66,7 @@ public class ConfigurationMetadataMatchers {
private final String description; private final String description;
private final Object defaultValue; private final Matcher<?> defaultValue;
private final boolean deprecated; private final boolean deprecated;
...@@ -75,7 +75,7 @@ public class ConfigurationMetadataMatchers { ...@@ -75,7 +75,7 @@ public class ConfigurationMetadataMatchers {
} }
public ContainsItemMatcher(ItemType itemType, String name, String type, public ContainsItemMatcher(ItemType itemType, String name, String type,
Class<?> sourceType, String description, Object defaultValue, Class<?> sourceType, String description, Matcher<?> defaultValue,
boolean deprecated) { boolean deprecated) {
this.itemType = itemType; this.itemType = itemType;
this.name = name; this.name = name;
...@@ -101,7 +101,7 @@ public class ConfigurationMetadataMatchers { ...@@ -101,7 +101,7 @@ public class ConfigurationMetadataMatchers {
return false; return false;
} }
if (this.defaultValue != null if (this.defaultValue != null
&& !this.defaultValue.equals(itemMetadata.getDefaultValue())) { && !this.defaultValue.matches(itemMetadata.getDefaultValue())) {
return false; return false;
} }
if (this.description != null if (this.description != null
...@@ -169,7 +169,7 @@ public class ConfigurationMetadataMatchers { ...@@ -169,7 +169,7 @@ public class ConfigurationMetadataMatchers {
this.sourceType, description, this.defaultValue, this.deprecated); 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, return new ContainsItemMatcher(this.itemType, this.name, this.type,
this.sourceType, this.description, defaultValue, this.deprecated); this.sourceType, this.description, defaultValue, this.deprecated);
} }
......
...@@ -44,6 +44,7 @@ import static org.junit.Assert.assertThat; ...@@ -44,6 +44,7 @@ import static org.junit.Assert.assertThat;
* Abstract base class for {@link FieldValuesParser} tests. * Abstract base class for {@link FieldValuesParser} tests.
* *
* @author Phillip Webb * @author Phillip Webb
* @author Stephane Nicoll
*/ */
public abstract class AbstractFieldValuesProcessorTests { public abstract class AbstractFieldValuesProcessorTests {
...@@ -77,6 +78,12 @@ public abstract class AbstractFieldValuesProcessorTests { ...@@ -77,6 +78,12 @@ public abstract class AbstractFieldValuesProcessorTests {
assertThat(values.get("objectNone"), nullValue()); assertThat(values.get("objectNone"), nullValue());
assertThat(values.get("objectConst"), equalToObject("c")); assertThat(values.get("objectConst"), equalToObject("c"));
assertThat(values.get("objectInstance"), nullValue()); 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<Object> equalToObject(Object object) { private Matcher<Object> equalToObject(Object object) {
......
...@@ -23,6 +23,9 @@ import java.io.InputStream; ...@@ -23,6 +23,9 @@ import java.io.InputStream;
import org.junit.Test; 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.junit.Assert.assertThat;
import static org.springframework.boot.configurationprocessor.ConfigurationMetadataMatchers.containsGroup; import static org.springframework.boot.configurationprocessor.ConfigurationMetadataMatchers.containsGroup;
import static org.springframework.boot.configurationprocessor.ConfigurationMetadataMatchers.containsProperty; import static org.springframework.boot.configurationprocessor.ConfigurationMetadataMatchers.containsProperty;
...@@ -31,6 +34,7 @@ import static org.springframework.boot.configurationprocessor.ConfigurationMetad ...@@ -31,6 +34,7 @@ import static org.springframework.boot.configurationprocessor.ConfigurationMetad
* Tests for {@link JsonMarshaller}. * Tests for {@link JsonMarshaller}.
* *
* @author Phillip Webb * @author Phillip Webb
* @author Stephane Nicoll
*/ */
public class JsonMarshallerTests { public class JsonMarshallerTests {
...@@ -45,6 +49,10 @@ public class JsonMarshallerTests { ...@@ -45,6 +49,10 @@ public class JsonMarshallerTests {
false)); false));
metadata.add(ItemMetadata.newProperty("d", null, null, null, null, null, true, metadata.add(ItemMetadata.newProperty("d", null, null, null, null, null, true,
false)); 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)); metadata.add(ItemMetadata.newGroup("d", null, null, null));
ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
JsonMarshaller marshaller = new JsonMarshaller(); JsonMarshaller marshaller = new JsonMarshaller();
...@@ -53,10 +61,12 @@ public class JsonMarshallerTests { ...@@ -53,10 +61,12 @@ public class JsonMarshallerTests {
outputStream.toByteArray())); outputStream.toByteArray()));
assertThat(read, assertThat(read,
containsProperty("a.b", StringBuffer.class).fromSource(InputStream.class) 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("b.c.d"));
assertThat(read, containsProperty("c").withDefaultValue(123)); assertThat(read, containsProperty("c").withDefaultValue(is(123)));
assertThat(read, containsProperty("d").withDefaultValue(true)); 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")); assertThat(read, containsGroup("d"));
} }
......
...@@ -22,6 +22,7 @@ import org.springframework.boot.configurationsample.ConfigurationProperties; ...@@ -22,6 +22,7 @@ import org.springframework.boot.configurationsample.ConfigurationProperties;
* Sample object containing fields with initial values. * Sample object containing fields with initial values.
* *
* @author Phillip Webb * @author Phillip Webb
* @author Stephane Nicoll
*/ */
@SuppressWarnings("unused") @SuppressWarnings("unused")
@ConfigurationProperties @ConfigurationProperties
...@@ -37,6 +38,8 @@ public class FieldValues { ...@@ -37,6 +38,8 @@ public class FieldValues {
private static final Integer INTEGER_OBJ_CONST = 4; 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 string = "1";
private String stringNone; private String stringNone;
...@@ -75,4 +78,16 @@ public class FieldValues { ...@@ -75,4 +78,16 @@ public class FieldValues {
private Object objectInstance = new StringBuffer(); 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};
} }
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment