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}; + }