Add support for property hint

Create a new section in the meta-data called "hints" where users can
provide hints about a given property. The most basic use case for now
is to provide a list of values that a property can have. Each value may
have a description.

This sample JSON provides a basic example for a property called `foo.mode`
that exposes 3 values: "auto", "basic" and "advanced".

```
 "hints": [
    {
      "id": "foo.mode",
      "values": [
        {
          "value": "auto",
          "description": "Some smart description."
        },
        {
          "name": "basic"
        },
        {
          "name": "advanced"
        }
      ]
    }
]
```

This information can be read by tools (such as IDE) and offer an
auto-completion with the list of values.

Closes gh-2054
This commit is contained in:
Stephane Nicoll
2015-06-23 11:37:49 +02:00
parent 9aa445bf46
commit bc9321734f
7 changed files with 505 additions and 27 deletions
@@ -7,7 +7,9 @@ contextual help and "`code completion`" as users are working with `application.p
or `application.yml` files.
The majority of the meta-data file is generated automatically at compile time by
processing all items annotated with `@ConfigurationProperties`.
processing all items annotated with `@ConfigurationProperties`. However, it is possible
to <<configuration-metadata-additional-metadata,write part of the meta-data manually>>
for corner cases or more advanced use cases.
@@ -15,7 +17,8 @@ processing all items annotated with `@ConfigurationProperties`.
=== Meta-data format
Configuration meta-data files are located inside jars under
`META-INF/spring-configuration-metadata.json` They use a simple JSON format with items
categorized under either "`groups`" or "`properties`":
categorized under either "`groups`" or "`properties`" and additional values hint
categorized under "hints":
[source,json,indent=0]
----
@@ -24,6 +27,12 @@ categorized under either "`groups`" or "`properties`":
"name": "server",
"type": "org.springframework.boot.autoconfigure.web.ServerProperties",
"sourceType": "org.springframework.boot.autoconfigure.web.ServerProperties"
},
{
"name": "server.tomcat",
"type": "org.springframework.boot.autoconfigure.web.ServerProperties$Tomcat",
"sourceType": "org.springframework.boot.autoconfigure.web.ServerProperties",
"sourceMethod": "getTomcat()"
}
...
],"properties": [
@@ -37,8 +46,33 @@ categorized under either "`groups`" or "`properties`":
"type": "java.lang.String",
"sourceType": "org.springframework.boot.autoconfigure.web.ServerProperties",
"defaultValue": "/"
}
},
{
"name": "server.tomcat.compression",
"type": "java.lang.String",
"description": "Controls response compression.",
"sourceType": "org.springframework.boot.autoconfigure.web.ServerProperties$Tomcat",
"defaultValue": "off"
}
...
],"hints": [
{
"name": "server.tomcat.compression",
"values": [
{
"value": "off",
"description": "Disable compression."
},
{
"value": "on",
"description": "Enable compression of responses over 2048 byte."
},
{
"value": "force",
"description": "Enable compression of all responses."
},
]
}
]}
----
@@ -59,6 +93,9 @@ provide a contextual grouping for properties. For example the `server.port` and
NOTE: It is not required that every "`property`" has a "`group`", some properties might
just exist in their own right.
Finally, "`hints`" are additional information used to assist the user in configuring a
given property. When configuring the `server.tomcat.compression` property, a tool can
use it to offer some auto-completion help for the `off`, `on` and `force` values.
[[configuration-metadata-group-attributes]]
@@ -152,6 +189,46 @@ The JSON object contained in the `properties` array can contain the following at
|===
[[configuration-metadata-hints-attributes]]
==== Hint Attributes
The JSON object contained in the `hints` array can contain the following attributes:
[cols="1,1,4"]
|===
|Name | Type |Purpose
|`name`
| String
| The full name of the property that this hint refers to. Names are in lowercase dashed
form (e.g. `server.servlet-path`). If the property refers to a map (e.g.
`system.contexts`) the hint either applies to the _keys_ of the map (`system.context.keys`)
or the values (`system.context.values`). This attribute is mandatory.
|`values`
| ValueHint[]
| A list of valid values as defined by the `ValueHint` object (see below). Each entry defines
the value and may have a description
|===
The JSON object contained in the `values` array of each `hint` element can contain the
following attributes:
[cols="1,1,4"]
|===
|Name | Type |Purpose
|`value`
| Object
| A valid value for the element to which the hint refers to. Can also be an array of value(s)
if the type of the property is an array. This attribute is mandatory.
|`description`
| String
| A short description of the value that can be displayed to users. May be omitted if no
description is available. It is recommended that descriptions are a short paragraphs,
with the first line providing a concise summary. The last line in the description should
end with a period (`.`).
|===
[[configuration-metadata-repeated-items]]
==== Repeated meta-data items
@@ -161,7 +238,19 @@ appear multiple times within a meta-data file. For example, Spring Boot binds
offering overlap of property names. Consumers of meta-data should take care to ensure
that they support such scenarios.
=== Providing manual hints
To improve the user experience and further assist the user in configuring a given
property, you can provide additional meta-data that describes the list of potential
values for a property.
The `name` attribute of each hint refers to the `name` of a property. In the initial
example above, we provide 3 values for the `server.tomcat.compression` property: `on`,
`off` and `force`.
If your property is of type `Map`, you can provide hints for both the keys and the
values (but not for the map itself). The special `.keys` and `.values` suffixes must
be used to refer to the keys and the values respectively.
[[configuration-metadata-annotation-processor]]
=== Generating your own meta-data using the annotation processor
@@ -250,8 +339,9 @@ if it were nested.
==== Adding additional meta-data
Spring Boot's configuration file handling is quite flexible; and it often the case that
properties may exist that are not bound to a `@ConfigurationProperties` bean. To support
such cases, the annotation processor will automatically merge items from
`META-INF/additional-spring-configuration-metadata.json` into the main meta-data file.
such cases and allow you to provide custom "hints", the annotation processor will
automatically merge items from `META-INF/additional-spring-configuration-metadata.json`
into the main meta-data file.
The format of the `additional-spring-configuration-metadata.json` file is exactly the same
as the regular `spring-configuration-metadata.json`. The additional properties file is
@@ -1,5 +1,5 @@
/*
* Copyright 2012-2014 the original author or authors.
* Copyright 2012-2015 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.
@@ -36,12 +36,16 @@ public class ConfigurationMetadata {
private final List<ItemMetadata> items;
private final List<ItemHint> hints;
public ConfigurationMetadata() {
this.items = new ArrayList<ItemMetadata>();
this.hints = new ArrayList<ItemHint>();
}
public ConfigurationMetadata(ConfigurationMetadata metadata) {
this.items = new ArrayList<ItemMetadata>(metadata.getItems());
this.hints = new ArrayList<ItemHint>(metadata.getHints());
}
/**
@@ -53,6 +57,11 @@ public class ConfigurationMetadata {
Collections.sort(this.items);
}
public void add(ItemHint itemHint) {
this.hints.add(itemHint);
Collections.sort(this.hints);
}
/**
* Add all properties from another {@link ConfigurationMetadata}.
* @param metadata the {@link ConfigurationMetadata} instance to merge
@@ -60,6 +69,8 @@ public class ConfigurationMetadata {
public void addAll(ConfigurationMetadata metadata) {
this.items.addAll(metadata.getItems());
Collections.sort(this.items);
this.hints.addAll(metadata.getHints());
Collections.sort(this.hints);
}
/**
@@ -69,6 +80,13 @@ public class ConfigurationMetadata {
return Collections.unmodifiableList(this.items);
}
/**
* @return the meta-data hints.
*/
public List<ItemHint> getHints() {
return Collections.unmodifiableList(this.hints);
}
public static String nestedPrefix(String prefix, String name) {
String nestedPrefix = (prefix == null ? "" : prefix);
String dashedName = toDashedCase(name);
@@ -0,0 +1,107 @@
/*
* Copyright 2012-2015 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.metadata;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
/**
* Provide hints on an {@link ItemMetadata}. Defines the list of possible values for
* a particular item as {@link ItemHint.ValueHint} instances.
* <p>
* The {@code name} of the hint is the name of the related property with one major
* exception for map types as both the keys and values of the map can have hints. In
* such a case, the hint should be suffixed by ".key" or ".values" respectively. Creating
* a hint for a map using its property name is therefore invalid.
*
* @author Stephane Nicoll
* @since 1.3.0
*/
public class ItemHint implements Comparable<ItemHint> {
private final String name;
private final List<ValueHint> values;
public ItemHint(String name, List<ValueHint> values) {
this.name = toCanonicalName(name);
this.values = new ArrayList<ValueHint>(values);
}
private String toCanonicalName(String name) {
int dot = name.lastIndexOf('.');
if (dot != -1) {
String prefix = name.substring(0, dot);
String originalName = name.substring(dot, name.length());
return prefix + ConfigurationMetadata.toDashedCase(originalName);
}
return ConfigurationMetadata.toDashedCase(name);
}
public String getName() {
return this.name;
}
public List<ValueHint> getValues() {
return Collections.unmodifiableList(this.values);
}
@Override
public int compareTo(ItemHint other) {
return getName().compareTo(other.getName());
}
public static ItemHint newHint(String name, ValueHint... values) {
return new ItemHint(name, Arrays.asList(values));
}
@Override
public String toString() {
return "ItemHint{" + "name='" + this.name + '\'' +
", values=" + this.values +
'}';
}
public static class ValueHint {
private final Object value;
private final String description;
public ValueHint(Object value, String description) {
this.value = value;
this.description = description;
}
public Object getValue() {
return this.value;
}
public String getDescription() {
return this.description;
}
@Override
public String toString() {
return "ValueHint{" + "value=" + this.value +
", description='" + this.description + '\'' +
'}';
}
}
}
@@ -1,5 +1,5 @@
/*
* Copyright 2012-2014 the original author or authors.
* Copyright 2012-2015 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.
@@ -22,7 +22,10 @@ import java.io.InputStreamReader;
import java.io.OutputStream;
import java.lang.reflect.Array;
import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.Collection;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Set;
import org.json.JSONArray;
@@ -48,6 +51,7 @@ public class JsonMarshaller {
JSONObject object = new JSONObject();
object.put("groups", toJsonArray(metadata, ItemType.GROUP));
object.put("properties", toJsonArray(metadata, ItemType.PROPERTY));
object.put("hints", toJsonArray(metadata.getHints()));
outputStream.write(object.toString(2).getBytes(UTF_8));
}
@@ -61,6 +65,14 @@ public class JsonMarshaller {
return jsonArray;
}
private JSONArray toJsonArray(Collection<ItemHint> hints) {
JSONArray jsonArray = new JSONArray();
for (ItemHint hint : hints) {
jsonArray.put(toJsonObject(hint));
}
return jsonArray;
}
private JSONObject toJsonObject(ItemMetadata item) {
JSONObject jsonObject = new JSONOrderedObject();
jsonObject.put("name", item.getName());
@@ -78,13 +90,40 @@ public class JsonMarshaller {
return jsonObject;
}
private JSONObject toJsonObject(ItemHint hint) {
JSONObject jsonObject = new JSONOrderedObject();
jsonObject.put("name", hint.getName());
if (!hint.getValues().isEmpty()) {
JSONArray valuesArray = new JSONArray();
for (ItemHint.ValueHint valueHint : hint.getValues()) {
JSONObject valueObject = new JSONOrderedObject();
putHintValue(valueObject, valueHint.getValue());
putIfPresent(valueObject, "description", valueHint.getDescription());
valuesArray.put(valueObject);
}
jsonObject.put("values", valuesArray);
}
return jsonObject;
}
private void putIfPresent(JSONObject jsonObject, String name, Object value) {
if (value != null) {
jsonObject.put(name, value);
}
}
private void putHintValue(JSONObject jsonObject, Object value) {
Object hintValue = extractItemValue(value);
jsonObject.put("value", hintValue);
}
private void putDefaultValue(JSONObject jsonObject, Object value) {
Object defaultValue = extractItemValue(value);
jsonObject.put("defaultValue", defaultValue);
}
private Object extractItemValue(Object value) {
Object defaultValue = value;
if (value.getClass().isArray()) {
JSONArray array = new JSONArray();
@@ -95,7 +134,7 @@ public class JsonMarshaller {
defaultValue = array;
}
jsonObject.put("defaultValue", defaultValue);
return defaultValue;
}
public ConfigurationMetadata read(InputStream inputStream) throws IOException {
@@ -114,6 +153,12 @@ public class JsonMarshaller {
ItemType.PROPERTY));
}
}
JSONArray hints = object.optJSONArray("hints");
if (hints != null) {
for (int i = 0; i < hints.length(); i++) {
metadata.add(toItemHint((JSONObject) hints.get(i)));
}
}
return metadata;
}
@@ -123,23 +168,41 @@ public class JsonMarshaller {
String description = object.optString("description", null);
String sourceType = object.optString("sourceType", null);
String sourceMethod = object.optString("sourceMethod", null);
Object defaultValue = readDefaultValue(object);
Object defaultValue = readItemValue(object.opt("defaultValue"));
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;
private ItemHint toItemHint(JSONObject object) {
String name = object.getString("name");
List<ItemHint.ValueHint> values = new ArrayList<ItemHint.ValueHint>();
if (object.has("values")) {
JSONArray valuesArray = object.getJSONArray("values");
for (int i = 0; i < valuesArray.length(); i++) {
values.add(toValueHint((JSONObject) valuesArray.get(i)));
}
}
return new ItemHint(name, values);
}
private ItemHint.ValueHint toValueHint(JSONObject object) {
Object value = readItemValue(object.get("value"));
String description = object.optString("description", null);
return new ItemHint.ValueHint(value, description);
}
private Object readItemValue(Object value) {
if (value instanceof JSONArray) {
JSONArray array = (JSONArray) value;
Object[] content = new Object[array.length()];
for (int i = 0; i < array.length(); i++) {
content[i] = array.get(i);
}
return content;
}
return defaultValue;
return value;
}
private String toString(InputStream inputStream) throws IOException {
@@ -27,6 +27,7 @@ import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.TemporaryFolder;
import org.springframework.boot.configurationprocessor.metadata.ConfigurationMetadata;
import org.springframework.boot.configurationprocessor.metadata.ItemHint;
import org.springframework.boot.configurationsample.incremental.BarProperties;
import org.springframework.boot.configurationsample.incremental.FooProperties;
import org.springframework.boot.configurationsample.incremental.RenamedBarProperties;
@@ -58,6 +59,7 @@ import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertThat;
import static org.junit.Assert.assertTrue;
import static org.springframework.boot.configurationprocessor.ConfigurationMetadataMatchers.containsGroup;
import static org.springframework.boot.configurationprocessor.ConfigurationMetadataMatchers.containsHint;
import static org.springframework.boot.configurationprocessor.ConfigurationMetadataMatchers.containsProperty;
import static org.springframework.boot.configurationprocessor.MetadataStore.METADATA_PATH;
@@ -324,12 +326,8 @@ public class ConfigurationMetadataAnnotationProcessorTests {
}
@Test
public void mergingOfAdditionalMetadata() throws Exception {
File metaInfFolder = new File(this.compiler.getOutputLocation(), "META-INF");
metaInfFolder.mkdirs();
File additionalMetadataFile = new File(metaInfFolder,
"additional-spring-configuration-metadata.json");
additionalMetadataFile.createNewFile();
public void mergingOfAdditionalProperty() throws Exception {
File additionalMetadataFile = createAdditionalMetadataFile();
JSONObject property = new JSONObject();
property.put("name", "foo");
@@ -339,10 +337,8 @@ public class ConfigurationMetadataAnnotationProcessorTests {
properties.put(property);
JSONObject additionalMetadata = new JSONObject();
additionalMetadata.put("properties", properties);
FileWriter writer = new FileWriter(additionalMetadataFile);
additionalMetadata.write(writer);
writer.flush();
writeMetadata(additionalMetadataFile, additionalMetadata);
ConfigurationMetadata metadata = compile(SimpleProperties.class);
assertThat(metadata, containsProperty("simple.comparator"));
@@ -352,6 +348,28 @@ public class ConfigurationMetadataAnnotationProcessorTests {
.fromSource(AdditionalMetadata.class));
}
@Test
public void mergingOfSimpleHint() throws Exception {
writeAdditionalHints(
ItemHint.newHint("simple.the-name", new ItemHint.ValueHint("boot", "Bla bla"),
new ItemHint.ValueHint("spring", null)));
ConfigurationMetadata metadata = compile(SimpleProperties.class);
assertThat(metadata, containsHint("simple.the-name")
.withValue(0, "boot", "Bla bla")
.withValue(1, "spring", null));
}
@Test
public void mergingOfHintWithNonCanonicalName() throws Exception {
writeAdditionalHints(
ItemHint.newHint("simple.theName", new ItemHint.ValueHint("boot", "Bla bla")));
ConfigurationMetadata metadata = compile(SimpleProperties.class);
assertThat(metadata, containsHint("simple.the-name")
.withValue(0, "boot", "Bla bla"));
}
@Test
public void incrementalBuild() throws Exception {
TestProject project = new TestProject(this.temporaryFolder, FooProperties.class,
@@ -448,6 +466,50 @@ public class ConfigurationMetadataAnnotationProcessorTests {
return processor.getMetadata();
}
private void writeAdditionalHints(ItemHint... hints) throws IOException {
File additionalMetadataFile = createAdditionalMetadataFile();
JSONArray hintsArray = new JSONArray();
for (ItemHint hint : hints) {
JSONObject jsonObject = new JSONObject();
jsonObject.put("name", hint.getName());
JSONArray valuesArray = new JSONArray();
for (ItemHint.ValueHint valueHint : hint.getValues()) {
JSONObject valueJsonObject = new JSONObject();
valueJsonObject.put("value", valueHint.getValue());
String description = valueHint.getDescription();
if (description != null) {
valueJsonObject.put("description", description);
}
valuesArray.put(valueJsonObject);
}
jsonObject.put("values", valuesArray);
hintsArray.put(jsonObject);
}
JSONObject additionalMetadata = new JSONObject();
additionalMetadata.put("hints", hints);
writeMetadata(additionalMetadataFile, additionalMetadata);
}
private File createAdditionalMetadataFile() throws IOException {
File metaInfFolder = new File(this.compiler.getOutputLocation(), "META-INF");
metaInfFolder.mkdirs();
File additionalMetadataFile = new File(metaInfFolder,
"additional-spring-configuration-metadata.json");
additionalMetadataFile.createNewFile();
return additionalMetadataFile;
}
private void writeMetadata(File metadataFile, JSONObject metadata) throws IOException {
FileWriter writer = new FileWriter(metadataFile);
try {
metadata.write(writer);
}
finally {
writer.close();
}
}
private static class AdditionalMetadata {
}
@@ -16,10 +16,14 @@
package org.springframework.boot.configurationprocessor;
import java.util.ArrayList;
import java.util.List;
import org.hamcrest.BaseMatcher;
import org.hamcrest.Description;
import org.hamcrest.Matcher;
import org.springframework.boot.configurationprocessor.metadata.ConfigurationMetadata;
import org.springframework.boot.configurationprocessor.metadata.ItemHint;
import org.springframework.boot.configurationprocessor.metadata.ItemMetadata;
import org.springframework.boot.configurationprocessor.metadata.ItemMetadata.ItemType;
@@ -27,6 +31,7 @@ import org.springframework.boot.configurationprocessor.metadata.ItemMetadata.Ite
* Hamcrest {@link Matcher} to help test {@link ConfigurationMetadata}.
*
* @author Phillip Webb
* @author Stephane Nicoll
*/
public class ConfigurationMetadataMatchers {
@@ -54,6 +59,10 @@ public class ConfigurationMetadataMatchers {
return new ContainsItemMatcher(ItemType.PROPERTY, name).ofType(type);
}
public static ContainsHintMatcher containsHint(String name) {
return new ContainsHintMatcher(name);
}
public static class ContainsItemMatcher extends BaseMatcher<ConfigurationMetadata> {
private final ItemType itemType;
@@ -89,7 +98,7 @@ public class ConfigurationMetadataMatchers {
@Override
public boolean matches(Object item) {
ConfigurationMetadata metadata = (ConfigurationMetadata) item;
ItemMetadata itemMetadata = getFirstPropertyWithName(metadata, this.name);
ItemMetadata itemMetadata = getFirstItemWithName(metadata, this.name);
if (itemMetadata == null) {
return false;
}
@@ -117,7 +126,7 @@ public class ConfigurationMetadataMatchers {
@Override
public void describeMismatch(Object item, Description description) {
ConfigurationMetadata metadata = (ConfigurationMetadata) item;
ItemMetadata property = getFirstPropertyWithName(metadata, this.name);
ItemMetadata property = getFirstItemWithName(metadata, this.name);
if (property == null) {
description.appendText("missing "
+ this.itemType.toString().toLowerCase() + " " + this.name);
@@ -179,7 +188,7 @@ public class ConfigurationMetadataMatchers {
this.sourceType, this.description, this.defaultValue, true);
}
private ItemMetadata getFirstPropertyWithName(ConfigurationMetadata metadata,
private ItemMetadata getFirstItemWithName(ConfigurationMetadata metadata,
String name) {
for (ItemMetadata item : metadata.getItems()) {
if (item.isOfItemType(this.itemType) && name.equals(item.getName())) {
@@ -191,4 +200,127 @@ public class ConfigurationMetadataMatchers {
}
public static class ContainsHintMatcher extends BaseMatcher<ConfigurationMetadata> {
private final String name;
private final List<ValueHintMatcher> values;
public ContainsHintMatcher(String name) {
this(name, new ArrayList<ValueHintMatcher>());
}
public ContainsHintMatcher(String name, List<ValueHintMatcher> values) {
this.name = name;
this.values = values;
}
@Override
public boolean matches(Object item) {
ConfigurationMetadata metadata = (ConfigurationMetadata) item;
ItemHint itemHint = getFirstHintWithName(metadata, this.name);
if (itemHint == null) {
return false;
}
if (this.name != null && !this.name.equals(itemHint.getName())) {
return false;
}
for (ValueHintMatcher value : this.values) {
if (!value.matches(itemHint)) {
return false;
}
}
return true;
}
@Override
public void describeMismatch(Object item, Description description) {
ConfigurationMetadata metadata = (ConfigurationMetadata) item;
ItemHint itemHint = getFirstHintWithName(metadata, this.name);
if (itemHint == null) {
description.appendText("missing hint " + this.name);
}
else {
description.appendText(
"was hint ").appendValue(itemHint);
}
}
@Override
public void describeTo(Description description) {
description.appendText("hints for " + this.name);
if (this.values != null) {
description.appendText(" values ").appendValue(this.values);
}
}
public ContainsHintMatcher withValue(int index, Object value, String description) {
List<ValueHintMatcher> values = new ArrayList<ValueHintMatcher>(this.values);
values.add(new ValueHintMatcher(index, value, description));
return new ContainsHintMatcher(this.name, values);
}
private ItemHint getFirstHintWithName(ConfigurationMetadata metadata,
String name) {
for (ItemHint hint : metadata.getHints()) {
if (name.equals(hint.getName())) {
return hint;
}
}
return null;
}
}
public static class ValueHintMatcher extends BaseMatcher<ItemHint> {
private final int index;
private final Object value;
private final String description;
public ValueHintMatcher(int index, Object value, String description) {
this.index = index;
this.value = value;
this.description = description;
}
@Override
public boolean matches(Object item) {
ItemHint hint = (ItemHint) item;
if (this.index + 1 > hint.getValues().size()) {
return false;
}
ItemHint.ValueHint valueHint = hint.getValues().get(this.index);
if (this.value != null
&& !this.value.equals(valueHint.getValue())) {
return false;
}
if (this.description != null
&& !this.description.equals(valueHint.getDescription())) {
return false;
}
return true;
}
@Override
public void describeTo(Description description) {
description.appendText("value hint at index '"+this.index+"'");
if (this.value != null) {
description.appendText(" value ").appendValue(this.value);
}
if (this.description != null) {
description.appendText(" description ").appendValue(this.description);
}
}
private ItemHint.ValueHint getValueHint(ItemHint hint) {
for (ItemHint.ValueHint valueHint : hint.getValues()) {
if (this.value.equals(valueHint.getValue())) {
return valueHint;
}
}
return null;
}
}
}
@@ -1,5 +1,5 @@
/*
* Copyright 2012-2014 the original author or authors.
* Copyright 2012-2015 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.
@@ -26,6 +26,7 @@ import org.junit.Test;
import static org.hamcrest.CoreMatchers.is;
import static org.junit.Assert.assertThat;
import static org.springframework.boot.configurationprocessor.ConfigurationMetadataMatchers.containsGroup;
import static org.springframework.boot.configurationprocessor.ConfigurationMetadataMatchers.containsHint;
import static org.springframework.boot.configurationprocessor.ConfigurationMetadataMatchers.containsProperty;
/**
@@ -52,6 +53,9 @@ public class JsonMarshallerTests {
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(ItemHint.newHint("a.b"));
metadata.add(ItemHint.newHint("c", new ItemHint.ValueHint(123, "hey"),
new ItemHint.ValueHint(456, null)));
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
JsonMarshaller marshaller = new JsonMarshaller();
marshaller.write(metadata, outputStream);
@@ -69,6 +73,8 @@ public class JsonMarshallerTests {
assertThat(read,
containsProperty("f").withDefaultValue(is(new boolean[] { true, false })));
assertThat(read, containsGroup("d"));
assertThat(read, containsHint("a.b"));
assertThat(read, containsHint("c").withValue(0, 123, "hey").withValue(1, 456, null));
}
}