Add converter support for Stream

Add StreamConverter to provide full support for converting
java.util.stream.Stream instances to and from collections or arrays.

Also attempt to convert the element type if necessary.

StreamConverter is registered by default in the DefaultConversionService
as long as Java8 is available.

Issue: SPR-12175
This commit is contained in:
Stephane Nicoll
2015-02-22 10:29:43 +01:00
parent c3408d869c
commit 018adb04f2
5 changed files with 410 additions and 33 deletions

View File

@@ -36,6 +36,7 @@ import java.util.Map;
import java.util.Optional;
import java.util.Properties;
import java.util.Set;
import java.util.stream.Stream;
import org.junit.Test;
@@ -53,6 +54,7 @@ import static org.junit.Assert.*;
/**
* @author Keith Donald
* @author Juergen Hoeller
* @author Stephane Nicoll
*/
public class DefaultConversionTests {
@@ -81,24 +83,24 @@ public class DefaultConversionTests {
@Test
public void testStringToBooleanTrue() {
assertEquals(Boolean.valueOf(true), conversionService.convert("true", Boolean.class));
assertEquals(Boolean.valueOf(true), conversionService.convert("on", Boolean.class));
assertEquals(Boolean.valueOf(true), conversionService.convert("yes", Boolean.class));
assertEquals(Boolean.valueOf(true), conversionService.convert("1", Boolean.class));
assertEquals(Boolean.valueOf(true), conversionService.convert("TRUE", Boolean.class));
assertEquals(Boolean.valueOf(true), conversionService.convert("ON", Boolean.class));
assertEquals(Boolean.valueOf(true), conversionService.convert("YES", Boolean.class));
assertEquals(true, conversionService.convert("true", Boolean.class));
assertEquals(true, conversionService.convert("on", Boolean.class));
assertEquals(true, conversionService.convert("yes", Boolean.class));
assertEquals(true, conversionService.convert("1", Boolean.class));
assertEquals(true, conversionService.convert("TRUE", Boolean.class));
assertEquals(true, conversionService.convert("ON", Boolean.class));
assertEquals(true, conversionService.convert("YES", Boolean.class));
}
@Test
public void testStringToBooleanFalse() {
assertEquals(Boolean.valueOf(false), conversionService.convert("false", Boolean.class));
assertEquals(Boolean.valueOf(false), conversionService.convert("off", Boolean.class));
assertEquals(Boolean.valueOf(false), conversionService.convert("no", Boolean.class));
assertEquals(Boolean.valueOf(false), conversionService.convert("0", Boolean.class));
assertEquals(Boolean.valueOf(false), conversionService.convert("FALSE", Boolean.class));
assertEquals(Boolean.valueOf(false), conversionService.convert("OFF", Boolean.class));
assertEquals(Boolean.valueOf(false), conversionService.convert("NO", Boolean.class));
assertEquals(false, conversionService.convert("false", Boolean.class));
assertEquals(false, conversionService.convert("off", Boolean.class));
assertEquals(false, conversionService.convert("no", Boolean.class));
assertEquals(false, conversionService.convert("0", Boolean.class));
assertEquals(false, conversionService.convert("FALSE", Boolean.class));
assertEquals(false, conversionService.convert("OFF", Boolean.class));
assertEquals(false, conversionService.convert("NO", Boolean.class));
}
@Test
@@ -123,7 +125,7 @@ public class DefaultConversionTests {
@Test
public void testByteToString() {
assertEquals("65", conversionService.convert(new String("A").getBytes()[0], String.class));
assertEquals("65", conversionService.convert("A".getBytes()[0], String.class));
}
@Test
@@ -227,11 +229,11 @@ public class DefaultConversionTests {
assertEquals("BAR", conversionService.convert(Foo.BAR, String.class));
}
public static enum Foo {
public enum Foo {
BAR, BAZ
}
public static enum SubFoo {
public enum SubFoo {
BAR {
@Override
@@ -262,12 +264,12 @@ public class DefaultConversionTests {
@Test
public void testNumberToNumber() {
assertEquals(Long.valueOf(1), conversionService.convert(Integer.valueOf(1), Long.class));
assertEquals(Long.valueOf(1), conversionService.convert(1, Long.class));
}
@Test(expected=ConversionFailedException.class)
public void testNumberToNumberNotSupportedNumber() {
conversionService.convert(Integer.valueOf(1), CustomNumber.class);
conversionService.convert(1, CustomNumber.class);
}
@SuppressWarnings("serial")
@@ -297,7 +299,7 @@ public class DefaultConversionTests {
@Test
public void testNumberToCharacter() {
assertEquals(Character.valueOf('A'), conversionService.convert(Integer.valueOf(65), Character.class));
assertEquals(Character.valueOf('A'), conversionService.convert(65, Character.class));
}
@Test
@@ -319,6 +321,7 @@ public class DefaultConversionTests {
@Test
public void convertArrayToCollectionGenericTypeConversion() throws Exception {
@SuppressWarnings("unchecked")
List<Integer> result = (List<Integer>) conversionService.convert(new String[] { "1", "2", "3" }, TypeDescriptor
.valueOf(String[].class), new TypeDescriptor(getClass().getDeclaredField("genericList")));
assertEquals(new Integer("1"), result.get(0));
@@ -326,11 +329,26 @@ public class DefaultConversionTests {
assertEquals(new Integer("3"), result.get(2));
}
public Stream<Integer> genericStream;
@Test
public void convertArrayToStream() throws Exception {
String[] source = {"1", "3", "4"};
@SuppressWarnings("unchecked")
Stream<Integer> result = (Stream<Integer>) this.conversionService.convert(source,
TypeDescriptor.valueOf(String[].class),
new TypeDescriptor(getClass().getDeclaredField("genericStream")));
assertEquals(8, result.mapToInt((x) -> x).sum());
}
@Test
public void testSpr7766() throws Exception {
ConverterRegistry registry = (conversionService);
registry.addConverter(new ColorConverter());
List<Color> colors = (List<Color>) conversionService.convert(new String[] { "ffffff", "#000000" }, TypeDescriptor.valueOf(String[].class), new TypeDescriptor(new MethodParameter(getClass().getMethod("handlerMethod", List.class), 0)));
@SuppressWarnings("unchecked")
List<Color> colors = (List<Color>) conversionService.convert(new String[] { "ffffff", "#000000" },
TypeDescriptor.valueOf(String[].class),
new TypeDescriptor(new MethodParameter(getClass().getMethod("handlerMethod", List.class), 0)));
assertEquals(2, colors.size());
assertEquals(Color.WHITE, colors.get(0));
assertEquals(Color.BLACK, colors.get(1));
@@ -474,14 +492,14 @@ public class DefaultConversionTests {
@Test
public void convertCollectionToString() {
List<String> list = Arrays.asList(new String[] { "foo", "bar" });
List<String> list = Arrays.asList("foo", "bar");
String result = conversionService.convert(list, String.class);
assertEquals("foo,bar", result);
}
@Test
public void convertCollectionToStringWithElementConversion() throws Exception {
List<Integer> list = Arrays.asList(new Integer[] { 3, 5 });
List<Integer> list = Arrays.asList(3, 5);
String result = (String) conversionService.convert(list,
new TypeDescriptor(getClass().getField("genericList")), TypeDescriptor.valueOf(String.class));
assertEquals("3,5", result);
@@ -501,9 +519,9 @@ public class DefaultConversionTests {
List result = (List) conversionService.convert("1,2,3", TypeDescriptor.valueOf(String.class),
new TypeDescriptor(getClass().getField("genericList")));
assertEquals(3, result.size());
assertEquals(new Integer(1), result.get(0));
assertEquals(new Integer(2), result.get(1));
assertEquals(new Integer(3), result.get(2));
assertEquals(1, result.get(0));
assertEquals(2, result.get(1));
assertEquals(3, result.get(2));
}
@Test
@@ -551,6 +569,7 @@ public class DefaultConversionTests {
@Test
public void convertObjectToCollection() {
@SuppressWarnings("unchecked")
List<String> result = (List<String>) conversionService.convert(3L, List.class);
assertEquals(1, result.size());
assertEquals(3L, result.get(0));
@@ -558,6 +577,7 @@ public class DefaultConversionTests {
@Test
public void convertObjectToCollectionWithElementConversion() throws Exception {
@SuppressWarnings("unchecked")
List<Integer> result = (List<Integer>) conversionService.convert(3L, TypeDescriptor.valueOf(Long.class),
new TypeDescriptor(getClass().getField("genericList")));
assertEquals(1, result.size());
@@ -594,6 +614,7 @@ public class DefaultConversionTests {
foo.add("1");
foo.add("2");
foo.add("3");
@SuppressWarnings("unchecked")
List<Integer> bar = (List<Integer>) conversionService.convert(foo, TypeDescriptor.forObject(foo),
new TypeDescriptor(getClass().getField("genericList")));
assertEquals(new Integer(1), bar.get(0));
@@ -603,6 +624,7 @@ public class DefaultConversionTests {
@Test
public void convertCollectionToCollectionNull() throws Exception {
@SuppressWarnings("unchecked")
List<Integer> bar = (List<Integer>) conversionService.convert(null,
TypeDescriptor.valueOf(LinkedHashSet.class), new TypeDescriptor(getClass().getField("genericList")));
assertNull(bar);
@@ -621,6 +643,7 @@ public class DefaultConversionTests {
assertEquals("3", bar.get(2));
}
@SuppressWarnings("unchecked")
@Test
public void convertCollectionToCollectionSpecialCaseSourceImpl() throws Exception {
Map map = new LinkedHashMap();
@@ -641,7 +664,9 @@ public class DefaultConversionTests {
List<String> strings = new ArrayList<String>();
strings.add("3");
strings.add("9");
List<Integer> integers = (List<Integer>) conversionService.convert(strings, TypeDescriptor.collection(List.class, TypeDescriptor.valueOf(Integer.class)));
@SuppressWarnings("unchecked")
List<Integer> integers = (List<Integer>) conversionService.convert(strings,
TypeDescriptor.collection(List.class, TypeDescriptor.valueOf(Integer.class)));
assertEquals(new Integer(3), integers.get(0));
assertEquals(new Integer(9), integers.get(1));
}
@@ -653,7 +678,8 @@ public class DefaultConversionTests {
Map<String, String> foo = new HashMap<String, String>();
foo.put("1", "BAR");
foo.put("2", "BAZ");
Map<String, FooEnum> map = (Map<String, FooEnum>) conversionService.convert(foo,
@SuppressWarnings("unchecked")
Map<Integer, FooEnum> map = (Map<Integer, FooEnum>) conversionService.convert(foo,
TypeDescriptor.forObject(foo), new TypeDescriptor(getClass().getField("genericMap")));
assertEquals(FooEnum.BAR, map.get(1));
assertEquals(FooEnum.BAZ, map.get(2));
@@ -664,7 +690,9 @@ public class DefaultConversionTests {
Map<String, String> strings = new HashMap<String, String>();
strings.put("3", "9");
strings.put("6", "31");
Map<Integer, Integer> integers = (Map<Integer, Integer>) conversionService.convert(strings, TypeDescriptor.map(Map.class, TypeDescriptor.valueOf(Integer.class), TypeDescriptor.valueOf(Integer.class)));
@SuppressWarnings("unchecked")
Map<Integer, Integer> integers = (Map<Integer, Integer>) conversionService.convert(strings,
TypeDescriptor.map(Map.class, TypeDescriptor.valueOf(Integer.class), TypeDescriptor.valueOf(Integer.class)));
assertEquals(new Integer(9), integers.get(3));
assertEquals(new Integer(31), integers.get(6));
}
@@ -747,7 +775,8 @@ public class DefaultConversionTests {
@Test
public void convertObjectToObjectFinderMethodWithNull() {
TestEntity e = (TestEntity) conversionService.convert(null, TypeDescriptor.valueOf(String.class), TypeDescriptor.valueOf(TestEntity.class));
TestEntity e = (TestEntity) conversionService.convert(null,
TypeDescriptor.valueOf(String.class), TypeDescriptor.valueOf(TestEntity.class));
assertNull(e);
}
@@ -803,7 +832,8 @@ public class DefaultConversionTests {
@Test
public void convertObjectToOptionalNull() {
assertSame(Optional.empty(), conversionService.convert(null, TypeDescriptor.valueOf(Object.class), TypeDescriptor.valueOf(Optional.class)));
assertSame(Optional.empty(), conversionService.convert(null, TypeDescriptor.valueOf(Object.class),
TypeDescriptor.valueOf(Optional.class)));
assertSame(Optional.empty(), conversionService.convert(null, Optional.class));
}

View File

@@ -0,0 +1,188 @@
/*
* Copyright 2002-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.core.convert.support;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Stream;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.ExpectedException;
import org.springframework.core.convert.ConversionFailedException;
import org.springframework.core.convert.ConverterNotFoundException;
import org.springframework.core.convert.TypeDescriptor;
import org.springframework.core.convert.converter.Converter;
import static org.hamcrest.CoreMatchers.*;
import static org.hamcrest.core.Is.is;
import static org.junit.Assert.*;
public class StreamConverterTest {
@Rule
public final ExpectedException thrown = ExpectedException.none();
private GenericConversionService conversionService;
private StreamConverter streamConverter;
@Before
public void setup() {
this.conversionService = new GenericConversionService();
this.streamConverter = new StreamConverter(this.conversionService);
this.conversionService.addConverter(new CollectionToCollectionConverter(this.conversionService));
this.conversionService.addConverter(new ArrayToCollectionConverter(this.conversionService));
this.conversionService.addConverter(new CollectionToArrayConverter(this.conversionService));
this.conversionService.addConverter(this.streamConverter);
}
@Test
public void convertFromStreamToList() throws NoSuchFieldException {
this.conversionService.addConverter(Number.class, String.class, new ObjectToStringConverter());
Stream<Integer> stream = Arrays.asList(1, 2, 3).stream();
TypeDescriptor listOfStrings = new TypeDescriptor(Types.class.getField("listOfStrings")); ;
Object result = this.conversionService.convert(stream, listOfStrings);
assertNotNull("converted object must not be null", result);
assertTrue("Converted object must be a list", result instanceof List);
@SuppressWarnings("unchecked")
List<String> content = (List<String>) result;
assertEquals("1", content.get(0));
assertEquals("2", content.get(1));
assertEquals("3", content.get(2));
assertEquals("Wrong number of elements", 3, content.size());
}
@Test
public void convertFromStreamToArray() throws NoSuchFieldException {
this.conversionService.addConverterFactory(new NumberToNumberConverterFactory());
Stream<Integer> stream = Arrays.asList(1, 2, 3).stream();
TypeDescriptor arrayOfLongs = new TypeDescriptor(Types.class.getField("arrayOfLongs")); ;
Object result = this.conversionService.convert(stream, arrayOfLongs);
assertNotNull("converted object must not be null", result);
assertTrue("Converted object must be an array", result.getClass().isArray());
Long[] content = (Long[]) result;
assertEquals(Long.valueOf(1L), content[0]);
assertEquals(Long.valueOf(2L), content[1]);
assertEquals(Long.valueOf(3L), content[2]);
assertEquals("Wrong number of elements", 3, content.length);
}
@Test
public void convertFromStreamToRawList() throws NoSuchFieldException {
Stream<Integer> stream = Arrays.asList(1, 2, 3).stream();
TypeDescriptor listOfStrings = new TypeDescriptor(Types.class.getField("rawList")); ;
Object result = this.conversionService.convert(stream, listOfStrings);
assertNotNull("converted object must not be null", result);
assertTrue("Converted object must be a list", result instanceof List);
@SuppressWarnings("unchecked")
List<Object> content = (List<Object>) result;
assertEquals(1, content.get(0));
assertEquals(2, content.get(1));
assertEquals(3, content.get(2));
assertEquals("Wrong number of elements", 3, content.size());
}
@Test
public void convertFromStreamToArrayNoConverter() throws NoSuchFieldException {
Stream<Integer> stream = Arrays.asList(1, 2, 3).stream();
TypeDescriptor arrayOfLongs = new TypeDescriptor(Types.class.getField("arrayOfLongs")); ;
thrown.expect(ConversionFailedException.class);
thrown.expectCause(is(instanceOf(ConverterNotFoundException.class)));
this.conversionService.convert(stream, arrayOfLongs);
}
@Test
public void convertFromListToStream() throws NoSuchFieldException {
this.conversionService.addConverterFactory(new StringToNumberConverterFactory());
List<String> stream = Arrays.asList("1", "2", "3");
TypeDescriptor streamOfInteger = new TypeDescriptor(Types.class.getField("streamOfIntegers")); ;
Object result = this.conversionService.convert(stream, streamOfInteger);
assertNotNull("converted object must not be null", result);
assertTrue("Converted object must be a stream", result instanceof Stream);
@SuppressWarnings("unchecked")
Stream<Integer> content = (Stream<Integer>) result;
assertEquals(6, content.mapToInt((x) -> x).sum());
}
@Test
public void convertFromArrayToStream() throws NoSuchFieldException {
Integer[] stream = new Integer[] {1, 0, 1};
this.conversionService.addConverter(new Converter<Integer, Boolean>() {
@Override
public Boolean convert(Integer source) {
return source == 1;
}
});
TypeDescriptor streamOfBoolean = new TypeDescriptor(Types.class.getField("streamOfBooleans")); ;
Object result = this.conversionService.convert(stream, streamOfBoolean);
assertNotNull("converted object must not be null", result);
assertTrue("Converted object must be a stream", result instanceof Stream);
@SuppressWarnings("unchecked")
Stream<Boolean> content = (Stream<Boolean>) result;
assertEquals(2, content.filter(x -> x).count());
}
@Test
public void convertFromListToRawStream() throws NoSuchFieldException {
List<String> stream = Arrays.asList("1", "2", "3");
TypeDescriptor streamOfInteger = new TypeDescriptor(Types.class.getField("rawStream")); ;
Object result = this.conversionService.convert(stream, streamOfInteger);
assertNotNull("converted object must not be null", result);
assertTrue("Converted object must be a stream", result instanceof Stream);
@SuppressWarnings("unchecked")
Stream<Object> content = (Stream<Object>) result;
StringBuilder sb = new StringBuilder();
content.forEach(sb::append);
assertEquals("123", sb.toString());
}
@Test
public void doesNotMatchIfNoStream() throws NoSuchFieldException {
assertFalse("Should not match non stream type", this.streamConverter.matches(
new TypeDescriptor(Types.class.getField("listOfStrings")),
new TypeDescriptor(Types.class.getField("arrayOfLongs"))));
}
@Test
public void shouldFailToConvertIfNoStream() throws NoSuchFieldException {
thrown.expect(IllegalStateException.class);
this.streamConverter.convert(new Object(), new TypeDescriptor(Types.class.getField("listOfStrings")),
new TypeDescriptor(Types.class.getField("arrayOfLongs")));
}
@SuppressWarnings("unused")
static class Types {
public List<String> listOfStrings;
public Long[] arrayOfLongs;
public Stream<Integer> streamOfIntegers;
public Stream<Boolean> streamOfBooleans;
public Stream rawStream;
public List rawList;
}
}