diff --git a/spring-boot-tools/spring-boot-configuration-metadata/src/main/java/org/springframework/boot/configurationmetadata/ConfigurationMetadataHint.java b/spring-boot-tools/spring-boot-configuration-metadata/src/main/java/org/springframework/boot/configurationmetadata/ConfigurationMetadataHint.java index cef75b9c02..9f5e1f2049 100644 --- a/spring-boot-tools/spring-boot-configuration-metadata/src/main/java/org/springframework/boot/configurationmetadata/ConfigurationMetadataHint.java +++ b/spring-boot-tools/spring-boot-configuration-metadata/src/main/java/org/springframework/boot/configurationmetadata/ConfigurationMetadataHint.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2015 the original author or authors. + * Copyright 2012-2016 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. @@ -27,12 +27,34 @@ import java.util.List; */ class ConfigurationMetadataHint { + private static final String KEY_SUFFIX = ".keys"; + + private static final String VALUE_SUFFIX = ".values"; + private String id; private final List valueHints = new ArrayList(); private final List valueProviders = new ArrayList(); + public boolean isMapKeyHints() { + return (this.id != null && this.id.endsWith(KEY_SUFFIX)); + } + + public boolean isMapValueHints() { + return (this.id != null && this.id.endsWith(VALUE_SUFFIX)); + } + + public String resolveId() { + if (isMapKeyHints()) { + return this.id.substring(0, this.id.length() - KEY_SUFFIX.length()); + } + if (isMapValueHints()) { + return this.id.substring(0, this.id.length() - VALUE_SUFFIX.length()); + } + return this.id; + } + public String getId() { return this.id; } diff --git a/spring-boot-tools/spring-boot-configuration-metadata/src/main/java/org/springframework/boot/configurationmetadata/ConfigurationMetadataProperty.java b/spring-boot-tools/spring-boot-configuration-metadata/src/main/java/org/springframework/boot/configurationmetadata/ConfigurationMetadataProperty.java index 1c2c602df9..9977464900 100644 --- a/spring-boot-tools/spring-boot-configuration-metadata/src/main/java/org/springframework/boot/configurationmetadata/ConfigurationMetadataProperty.java +++ b/spring-boot-tools/spring-boot-configuration-metadata/src/main/java/org/springframework/boot/configurationmetadata/ConfigurationMetadataProperty.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2015 the original author or authors. + * Copyright 2012-2016 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. @@ -17,7 +17,6 @@ package org.springframework.boot.configurationmetadata; import java.io.Serializable; -import java.util.ArrayList; import java.util.List; /** @@ -44,9 +43,7 @@ public class ConfigurationMetadataProperty implements Serializable { private Object defaultValue; - private final List valueHints = new ArrayList(); - - private final List valueProviders = new ArrayList(); + private final Hints hints = new Hints(); private Deprecation deprecation; @@ -136,14 +133,24 @@ public class ConfigurationMetadataProperty implements Serializable { this.defaultValue = defaultValue; } + /** + * Return the hints of this item. + * @return the hints + */ + public Hints getHints() { + return this.hints; + } + /** * The list of well-defined values, if any. If no extra {@link ValueProvider provider} * is specified, these values are to be considered a closed-set of the available * values for this item. * @return the value hints + * @see #getHints() */ + @Deprecated public List getValueHints() { - return this.valueHints; + return this.hints.getValueHints(); } /** @@ -151,9 +158,11 @@ public class ConfigurationMetadataProperty implements Serializable { * {@link ValueProvider} is enabled for an item: the first in the list that is * supported should be used. * @return the value providers + * @see #getHints() */ + @Deprecated public List getValueProviders() { - return this.valueProviders; + return this.hints.getValueProviders(); } /** diff --git a/spring-boot-tools/spring-boot-configuration-metadata/src/main/java/org/springframework/boot/configurationmetadata/ConfigurationMetadataRepositoryJsonBuilder.java b/spring-boot-tools/spring-boot-configuration-metadata/src/main/java/org/springframework/boot/configurationmetadata/ConfigurationMetadataRepositoryJsonBuilder.java index da032f815e..0f1a1d04a0 100644 --- a/spring-boot-tools/spring-boot-configuration-metadata/src/main/java/org/springframework/boot/configurationmetadata/ConfigurationMetadataRepositoryJsonBuilder.java +++ b/spring-boot-tools/spring-boot-configuration-metadata/src/main/java/org/springframework/boot/configurationmetadata/ConfigurationMetadataRepositoryJsonBuilder.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2015 the original author or authors. + * Copyright 2012-2016 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. @@ -127,8 +127,22 @@ public final class ConfigurationMetadataRepositoryJsonBuilder { for (ConfigurationMetadataHint hint : metadata.getHints()) { ConfigurationMetadataProperty property = allProperties.get(hint.getId()); if (property != null) { - property.getValueHints().addAll(hint.getValueHints()); - property.getValueProviders().addAll(hint.getValueProviders()); + property.getHints().getValueHints().addAll(hint.getValueHints()); + property.getHints().getValueProviders().addAll(hint.getValueProviders()); + } + else { + String id = hint.resolveId(); + property = allProperties.get(id); + if (property != null) { + if (hint.isMapKeyHints()) { + property.getHints().getKeyHints().addAll(hint.getValueHints()); + property.getHints().getKeyProviders().addAll(hint.getValueProviders()); + } + else { + property.getHints().getValueHints().addAll(hint.getValueHints()); + property.getHints().getValueProviders().addAll(hint.getValueProviders()); + } + } } } return repository; diff --git a/spring-boot-tools/spring-boot-configuration-metadata/src/main/java/org/springframework/boot/configurationmetadata/Hints.java b/spring-boot-tools/spring-boot-configuration-metadata/src/main/java/org/springframework/boot/configurationmetadata/Hints.java new file mode 100644 index 0000000000..57f2d180a1 --- /dev/null +++ b/spring-boot-tools/spring-boot-configuration-metadata/src/main/java/org/springframework/boot/configurationmetadata/Hints.java @@ -0,0 +1,82 @@ +/* + * Copyright 2012-2016 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.configurationmetadata; + +import java.util.ArrayList; +import java.util.List; + +/** + * Hints of an item to provide the list of values and/or the name of the provider + * responsible to identify suitable values. If the type of the related item is + * a {@link java.util.Map} it can have both key and value hints. + * + * @author Stephane Nicoll + * @since 1.4.0 + */ +public class Hints { + + private final List keyHints = new ArrayList(); + + private final List keyProviders = new ArrayList(); + + private final List valueHints = new ArrayList(); + + private final List valueProviders = new ArrayList(); + + /** + * The list of well-defined keys, if any. Only applicable if the type of the related + * item is a {@link java.util.Map}. If no extra {@link ValueProvider provider} + * is specified, these values are to be considered a closed-set of the available + * keys for the map. + * @return the key hints + */ + public List getKeyHints() { + return this.keyHints; + } + + /** + * The value providers that are applicable to the keys of this item. Only applicable + * if the type of the related item is a {@link java.util.Map}. Only one + * {@link ValueProvider} is enabled for a key: the first in the list that is + * supported should be used. + * @return the key providers + */ + public List getKeyProviders() { + return this.keyProviders; + } + + /** + * The list of well-defined values, if any. If no extra {@link ValueProvider provider} + * is specified, these values are to be considered a closed-set of the available + * values for this item. + * @return the value hints + */ + public List getValueHints() { + return this.valueHints; + } + + /** + * The value providers that are applicable to this item. Only one + * {@link ValueProvider} is enabled for an item: the first in the list that is + * supported should be used. + * @return the value providers + */ + public List getValueProviders() { + return this.valueProviders; + } + +} diff --git a/spring-boot-tools/spring-boot-configuration-metadata/src/test/java/org/springframework/boot/configurationmetadata/ConfigurationMetadataRepositoryJsonBuilderTests.java b/spring-boot-tools/spring-boot-configuration-metadata/src/test/java/org/springframework/boot/configurationmetadata/ConfigurationMetadataRepositoryJsonBuilderTests.java index 068412e8a1..1eab036bae 100644 --- a/spring-boot-tools/spring-boot-configuration-metadata/src/test/java/org/springframework/boot/configurationmetadata/ConfigurationMetadataRepositoryJsonBuilderTests.java +++ b/spring-boot-tools/spring-boot-configuration-metadata/src/test/java/org/springframework/boot/configurationmetadata/ConfigurationMetadataRepositoryJsonBuilderTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2015 the original author or authors. + * Copyright 2012-2016 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. @@ -55,6 +55,23 @@ public class ConfigurationMetadataRepositoryJsonBuilderTests } } + @Test + public void hintsOnMaps() throws IOException { + InputStream map = getInputStreamFor("map"); + try { + ConfigurationMetadataRepository repo = ConfigurationMetadataRepositoryJsonBuilder + .create(map).build(); + validateMap(repo); + assertThat(repo.getAllGroups()).hasSize(1); + contains(repo.getAllProperties(), "spring.map.first", "spring.map.second", + "spring.map.keys", "spring.map.values"); + assertThat(repo.getAllProperties()).hasSize(4); + } + finally { + map.close(); + } + } + @Test public void severalRepositoriesNoConflict() throws IOException { InputStream foo = getInputStreamFor("foo"); @@ -182,10 +199,44 @@ public class ConfigurationMetadataRepositoryJsonBuilderTests validatePropertyHints(repo.getAllProperties().get("spring.bar.counter"), 0, 0); } + private void validateMap(ConfigurationMetadataRepository repo) { + ConfigurationMetadataGroup group = repo.getAllGroups().get("spring.map"); + ConfigurationMetadataSource source = group.getSources().get("org.acme.Map"); + contains(source.getProperties(), "spring.map.first", "spring.map.second", + "spring.map.keys", "spring.map.values"); + assertThat(source.getProperties()).hasSize(4); + ConfigurationMetadataProperty first = repo.getAllProperties().get("spring.map.first"); + assertThat(first.getHints().getKeyHints()).hasSize(2); + assertThat(first.getHints().getValueProviders()).hasSize(0); + assertThat(first.getHints().getKeyHints().get(0).getValue()).isEqualTo("one"); + assertThat(first.getHints().getKeyHints().get(0).getDescription()).isEqualTo("First."); + assertThat(first.getHints().getKeyHints().get(1).getValue()).isEqualTo("two"); + assertThat(first.getHints().getKeyHints().get(1).getDescription()).isEqualTo("Second."); + ConfigurationMetadataProperty second = repo.getAllProperties().get("spring.map.second"); + assertThat(second.getHints().getValueHints()).hasSize(2); + assertThat(second.getHints().getValueProviders()).hasSize(0); + assertThat(second.getHints().getValueHints().get(0).getValue()).isEqualTo("42"); + assertThat(second.getHints().getValueHints().get(0).getDescription()).isEqualTo("Choose me."); + assertThat(second.getHints().getValueHints().get(1).getValue()).isEqualTo("24"); + assertThat(second.getHints().getValueHints().get(1).getDescription()).isNull(); + ConfigurationMetadataProperty keys = repo.getAllProperties().get("spring.map.keys"); + assertThat(keys.getHints().getValueHints()).hasSize(0); + assertThat(keys.getHints().getValueProviders()).hasSize(1); + assertThat(keys.getHints().getValueProviders().get(0).getName()).isEqualTo("any"); + ConfigurationMetadataProperty values = repo.getAllProperties().get("spring.map.values"); + assertThat(values.getHints().getValueHints()).hasSize(0); + assertThat(values.getHints().getValueProviders()).hasSize(1); + assertThat(values.getHints().getValueProviders().get(0).getName()).isEqualTo("handle-as"); + assertThat(values.getHints().getValueProviders().get(0).getParameters()).hasSize(1); + assertThat(values.getHints().getValueProviders().get(0).getParameters().get("target")) + .isEqualTo("java.lang.Integer"); + } + + private void validatePropertyHints(ConfigurationMetadataProperty property, int valueHints, int valueProviders) { - assertThat(property.getValueHints().size()).isEqualTo(valueHints); - assertThat(property.getValueHints().size()).isEqualTo(valueProviders); + assertThat(property.getHints().getValueHints().size()).isEqualTo(valueHints); + assertThat(property.getHints().getValueProviders().size()).isEqualTo(valueProviders); } private void contains(Map source, String... keys) { diff --git a/spring-boot-tools/spring-boot-configuration-metadata/src/test/resources/metadata/configuration-metadata-map.json b/spring-boot-tools/spring-boot-configuration-metadata/src/test/resources/metadata/configuration-metadata-map.json new file mode 100644 index 0000000000..599ed64f19 --- /dev/null +++ b/spring-boot-tools/spring-boot-configuration-metadata/src/test/resources/metadata/configuration-metadata-map.json @@ -0,0 +1,79 @@ +{ + "groups": [ + { + "name": "spring.map", + "type": "org.acme.Map", + "sourceType": "org.acme.config.MapApp", + "sourceMethod": "map()", + "description": "This is Map." + } + ], + "properties": [ + { + "name": "spring.map.first", + "type": "java.util.Map", + "sourceType": "org.acme.Map" + }, + { + "name": "spring.map.second", + "type": "java.util.Map", + "sourceType": "org.acme.Map" + }, + { + "name": "spring.map.keys", + "type": "java.lang.String", + "sourceType": "org.acme.Map" + }, + { + "name": "spring.map.values", + "type": "java.lang.String", + "sourceType": "org.acme.Map" + } + ], + "hints": [ + { + "name": "spring.map.first.keys", + "values": [ + { + "value": "one", + "description": "First." + }, + { + "value": "two", + "description": "Second." + } + ] + }, + { + "name": "spring.map.second.values", + "values": [ + { + "value": "42", + "description": "Choose me." + }, + { + "value": "24" + } + ] + }, + { + "name": "spring.map.keys", + "providers": [ + { + "name": "any" + } + ] + }, + { + "name": "spring.map.values", + "providers": [ + { + "name": "handle-as", + "parameters": { + "target": "java.lang.Integer" + } + } + ] + } + ] +} \ No newline at end of file