/* * Copyright 2012-2017 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.context.properties.bind; import java.util.ArrayList; import java.util.List; import java.util.Set; import org.junit.Before; import org.junit.Test; import org.mockito.Answers; import org.mockito.InOrder; import org.springframework.boot.context.properties.source.ConfigurationProperty; import org.springframework.boot.context.properties.source.ConfigurationPropertyName; import org.springframework.boot.context.properties.source.ConfigurationPropertySource; import org.springframework.boot.context.properties.source.MockConfigurationPropertySource; import org.springframework.core.ResolvableType; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.fail; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.ArgumentMatchers.isA; import static org.mockito.Mockito.inOrder; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.withSettings; /** * Tests for {@link ArrayBinder}. * * @author Phillip Webb * @author Madhura Bhave */ public class ArrayBinderTests { private static final Bindable<List<Integer>> INTEGER_LIST = Bindable .listOf(Integer.class); private static final Bindable<Integer[]> INTEGER_ARRAY = Bindable.of(Integer[].class); private List<ConfigurationPropertySource> sources = new ArrayList<>(); private Binder binder; @Before public void setup() { this.binder = new Binder(this.sources); } @Test public void bindToArrayShouldReturnArray() throws Exception { MockConfigurationPropertySource source = new MockConfigurationPropertySource(); source.put("foo[0]", "1"); source.put("foo[1]", "2"); source.put("foo[2]", "3"); this.sources.add(source); Integer[] result = this.binder.bind("foo", INTEGER_ARRAY).get(); assertThat(result).containsExactly(1, 2, 3); } @Test public void bindToCollectionShouldTriggerOnSuccess() throws Exception { this.sources.add(new MockConfigurationPropertySource("foo[0]", "1", "line1")); BindHandler handler = mock(BindHandler.class, withSettings().defaultAnswer(Answers.CALLS_REAL_METHODS)); this.binder.bind("foo", INTEGER_LIST, handler); InOrder inOrder = inOrder(handler); inOrder.verify(handler).onSuccess(eq(ConfigurationPropertyName.of("foo[0]")), eq(Bindable.of(Integer.class)), any(), eq(1)); inOrder.verify(handler).onSuccess(eq(ConfigurationPropertyName.of("foo")), eq(INTEGER_LIST), any(), isA(List.class)); } @Test public void bindToArrayShouldReturnPrimitiveArray() throws Exception { MockConfigurationPropertySource source = new MockConfigurationPropertySource(); source.put("foo[0]", "1"); source.put("foo[1]", "2"); source.put("foo[2]", "3"); this.sources.add(source); int[] result = this.binder.bind("foo", Bindable.of(int[].class)).get(); assertThat(result).containsExactly(1, 2, 3); } @Test public void bindToArrayWhenNestedShouldReturnPopulatedArray() throws Exception { MockConfigurationPropertySource source = new MockConfigurationPropertySource(); source.put("foo[0][0]", "1"); source.put("foo[0][1]", "2"); source.put("foo[1][0]", "3"); source.put("foo[1][1]", "4"); this.sources.add(source); ResolvableType type = ResolvableType.forArrayComponent(INTEGER_ARRAY.getType()); Bindable<Integer[][]> target = Bindable.of(type); Integer[][] result = this.binder.bind("foo", target).get(); assertThat(result).hasSize(2); assertThat(result[0]).containsExactly(1, 2); assertThat(result[1]).containsExactly(3, 4); } @Test public void bindToArrayWhenNestedListShouldReturnPopulatedArray() throws Exception { MockConfigurationPropertySource source = new MockConfigurationPropertySource(); source.put("foo[0][0]", "1"); source.put("foo[0][1]", "2"); source.put("foo[1][0]", "3"); source.put("foo[1][1]", "4"); this.sources.add(source); ResolvableType type = ResolvableType.forArrayComponent(INTEGER_LIST.getType()); Bindable<List<Integer>[]> target = Bindable.of(type); List<Integer>[] result = this.binder.bind("foo", target).get(); assertThat(result).hasSize(2); assertThat(result[0]).containsExactly(1, 2); assertThat(result[1]).containsExactly(3, 4); } @Test public void bindToArrayWhenNotInOrderShouldReturnPopulatedArray() throws Exception { MockConfigurationPropertySource source = new MockConfigurationPropertySource(); source.put("foo[1]", "2"); source.put("foo[0]", "1"); source.put("foo[2]", "3"); this.sources.add(source); Integer[] result = this.binder.bind("foo", INTEGER_ARRAY).get(); assertThat(result).containsExactly(1, 2, 3); } @Test public void bindToArrayWhenNonSequentialShouldThrowException() throws Exception { MockConfigurationPropertySource source = new MockConfigurationPropertySource(); source.put("foo[0]", "2"); source.put("foo[1]", "1"); source.put("foo[3]", "3"); this.sources.add(source); try { this.binder.bind("foo", INTEGER_ARRAY); fail("No exception thrown"); } catch (BindException ex) { Set<ConfigurationProperty> unbound = ((UnboundConfigurationPropertiesException) ex .getCause()).getUnboundProperties(); assertThat(unbound.size()).isEqualTo(1); ConfigurationProperty property = unbound.iterator().next(); assertThat(property.getName().toString()).isEqualTo("foo[3]"); assertThat(property.getValue()).isEqualTo("3"); } } @Test public void bindToArrayWhenNonIterableShouldReturnPopulatedArray() throws Exception { MockConfigurationPropertySource source = new MockConfigurationPropertySource(); source.put("foo[1]", "2"); source.put("foo[0]", "1"); source.put("foo[2]", "3"); this.sources.add(source.nonIterable()); Integer[] result = this.binder.bind("foo", INTEGER_ARRAY).get(); assertThat(result).containsExactly(1, 2, 3); } @Test public void bindToArrayWhenMultipleSourceShouldOnlyUseFirst() throws Exception { MockConfigurationPropertySource source1 = new MockConfigurationPropertySource(); source1.put("bar", "baz"); this.sources.add(source1); MockConfigurationPropertySource source2 = new MockConfigurationPropertySource(); source2.put("foo[0]", "1"); source2.put("foo[1]", "2"); this.sources.add(source2); MockConfigurationPropertySource source3 = new MockConfigurationPropertySource(); source3.put("foo[0]", "7"); source3.put("foo[1]", "8"); source3.put("foo[2]", "9"); this.sources.add(source3); Integer[] result = this.binder.bind("foo", INTEGER_ARRAY).get(); assertThat(result).containsExactly(1, 2); } @Test public void bindToArrayWhenHasExistingCollectionShouldReplaceAllContents() throws Exception { this.sources.add(new MockConfigurationPropertySource("foo[0]", "1")); Integer[] existing = new Integer[2]; existing[0] = 1000; existing[1] = 1001; Integer[] result = this.binder .bind("foo", INTEGER_ARRAY.withExistingValue(existing)).get(); assertThat(result).containsExactly(1); } @Test public void bindToArrayWhenNoValueShouldReturnUnbound() throws Exception { this.sources.add(new MockConfigurationPropertySource("faf.bar", "1")); BindResult<Integer[]> result = this.binder.bind("foo", INTEGER_ARRAY); assertThat(result.isBound()).isFalse(); } @Test public void bindToArrayShouldTriggerOnSuccess() throws Exception { this.sources.add(new MockConfigurationPropertySource("foo[0]", "1", "line1")); BindHandler handler = mock(BindHandler.class, withSettings().defaultAnswer(Answers.CALLS_REAL_METHODS)); Bindable<Integer[]> target = INTEGER_ARRAY; this.binder.bind("foo", target, handler); InOrder inOrder = inOrder(handler); inOrder.verify(handler).onSuccess(eq(ConfigurationPropertyName.of("foo[0]")), eq(Bindable.of(Integer.class)), any(), eq(1)); inOrder.verify(handler).onSuccess(eq(ConfigurationPropertyName.of("foo")), eq(target), any(), isA(Integer[].class)); } @Test public void bindToArrayWhenCommaListShouldReturnPopulatedArray() throws Exception { this.sources.add(new MockConfigurationPropertySource("foo", "1,2,3")); int[] result = this.binder.bind("foo", Bindable.of(int[].class)).get(); assertThat(result).containsExactly(1, 2, 3); } @Test public void bindToArrayWhenCommaListAndIndexedShouldOnlyUseFirst() throws Exception { MockConfigurationPropertySource source1 = new MockConfigurationPropertySource(); source1.put("foo", "1,2"); this.sources.add(source1); MockConfigurationPropertySource source2 = new MockConfigurationPropertySource(); source2.put("foo[0]", "2"); source2.put("foo[1]", "3"); int[] result = this.binder.bind("foo", Bindable.of(int[].class)).get(); assertThat(result).containsExactly(1, 2); } @Test public void bindToArrayWhenIndexedAndCommaListShouldOnlyUseFirst() throws Exception { MockConfigurationPropertySource source1 = new MockConfigurationPropertySource(); source1.put("foo[0]", "1"); source1.put("foo[1]", "2"); this.sources.add(source1); MockConfigurationPropertySource source2 = new MockConfigurationPropertySource(); source2.put("foo", "2,3"); int[] result = this.binder.bind("foo", Bindable.of(int[].class)).get(); assertThat(result).containsExactly(1, 2); } @Test public void bindToArrayShouldBindCharArray() throws Exception { this.sources.add(new MockConfigurationPropertySource("foo", "word")); char[] result = this.binder.bind("foo", Bindable.of(char[].class)).get(); assertThat(result).containsExactly("word".toCharArray()); } @Test public void bindToArrayWhenEmptyStringShouldReturnEmptyArray() throws Exception { MockConfigurationPropertySource source = new MockConfigurationPropertySource(); source.put("foo", ""); this.sources.add(source); String[] result = this.binder.bind("foo", Bindable.of(String[].class)).get(); assertThat(result).isNotNull().isEmpty(); } }