From 018adb04f26ba51e680449eb60b357de0b9c960f Mon Sep 17 00:00:00 2001 From: Stephane Nicoll Date: Sun, 22 Feb 2015 10:29:43 +0100 Subject: [PATCH] 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 --- .../core/convert/TypeDescriptor.java | 27 ++- .../support/DefaultConversionService.java | 12 +- .../core/convert/support/StreamConverter.java | 124 ++++++++++++ .../support/DefaultConversionTests.java | 92 ++++++--- .../convert/support/StreamConverterTest.java | 188 ++++++++++++++++++ 5 files changed, 410 insertions(+), 33 deletions(-) create mode 100644 spring-core/src/main/java/org/springframework/core/convert/support/StreamConverter.java create mode 100644 spring-core/src/test/java/org/springframework/core/convert/support/StreamConverterTest.java diff --git a/spring-core/src/main/java/org/springframework/core/convert/TypeDescriptor.java b/spring-core/src/main/java/org/springframework/core/convert/TypeDescriptor.java index 7345b526f6..f7176c7f1a 100644 --- a/spring-core/src/main/java/org/springframework/core/convert/TypeDescriptor.java +++ b/spring-core/src/main/java/org/springframework/core/convert/TypeDescriptor.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2014 the original author or authors. + * 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. @@ -23,9 +23,11 @@ import java.lang.reflect.Type; import java.util.Collection; import java.util.HashMap; import java.util.Map; +import java.util.stream.Stream; import org.springframework.core.MethodParameter; import org.springframework.core.ResolvableType; +import org.springframework.lang.UsesJava8; import org.springframework.util.Assert; import org.springframework.util.ClassUtils; import org.springframework.util.ObjectUtils; @@ -38,6 +40,7 @@ import org.springframework.util.ObjectUtils; * @author Juergen Hoeller * @author Phillip Webb * @author Sam Brannen + * @author Stephane Nicoll * @since 3.0 */ @SuppressWarnings("serial") @@ -45,6 +48,9 @@ public class TypeDescriptor implements Serializable { static final Annotation[] EMPTY_ANNOTATION_ARRAY = new Annotation[0]; + private static final boolean streamAvailable = ClassUtils.isPresent( + "java.util.stream.Stream", TypeDescriptor.class.getClassLoader()); + private static final Map, TypeDescriptor> commonTypesCache = new HashMap, TypeDescriptor>(18); private static final Class[] CACHED_COMMON_TYPES = { @@ -316,6 +322,7 @@ public class TypeDescriptor implements Serializable { /** * If this type is an array, returns the array's component type. + * If this type is a {@code Stream}, returns the stream's component type. * If this type is a {@link Collection} and it is parameterized, returns the Collection's element type. * If the Collection is not parameterized, returns {@code null} indicating the element type is not declared. * @return the array component type or Collection element type, or {@code null} if this type is a @@ -326,6 +333,9 @@ public class TypeDescriptor implements Serializable { if (this.resolvableType.isArray()) { return new TypeDescriptor(this.resolvableType.getComponentType(), null, this.annotations); } + if (streamAvailable && StreamHelper.isStream(this.type)) { + return StreamHelper.getStreamElementType(this); + } return getRelatedIfResolvable(this, this.resolvableType.asCollection().getGeneric()); } @@ -681,4 +691,19 @@ public class TypeDescriptor implements Serializable { return new TypeDescriptor(type, null, source.annotations); } + /** + * Inner class to avoid a hard dependency on Java 8. + */ + @UsesJava8 + private static class StreamHelper { + + private static boolean isStream(Class type) { + return Stream.class.isAssignableFrom(type); + } + + private static TypeDescriptor getStreamElementType(TypeDescriptor source) { + return getRelatedIfResolvable(source, source.resolvableType.as(Stream.class).getGeneric()); + } + } + } diff --git a/spring-core/src/main/java/org/springframework/core/convert/support/DefaultConversionService.java b/spring-core/src/main/java/org/springframework/core/convert/support/DefaultConversionService.java index 2b4007bf19..6946e14b69 100644 --- a/spring-core/src/main/java/org/springframework/core/convert/support/DefaultConversionService.java +++ b/spring-core/src/main/java/org/springframework/core/convert/support/DefaultConversionService.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2013 the original author or authors. + * 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. @@ -33,6 +33,7 @@ import org.springframework.util.ClassUtils; * * @author Chris Beams * @author Juergen Hoeller + * @author Stephane Nicoll * @since 3.1 */ public class DefaultConversionService extends GenericConversionService { @@ -45,6 +46,11 @@ public class DefaultConversionService extends GenericConversionService { private static final boolean jsr310Available = ClassUtils.isPresent("java.time.ZoneId", DefaultConversionService.class.getClassLoader()); + /** Java 8's java.util.stream.Stream class available? */ + private static final boolean streamAvailable = ClassUtils.isPresent( + "java.util.stream.Stream", DefaultConversionService.class.getClassLoader()); + + /** * Create a new {@code DefaultConversionService} with the set of @@ -132,6 +138,10 @@ public class DefaultConversionService extends GenericConversionService { converterRegistry.addConverter(new CollectionToObjectConverter(conversionService)); converterRegistry.addConverter(new ObjectToCollectionConverter(conversionService)); + + if (streamAvailable) { + converterRegistry.addConverter(new StreamConverter(conversionService)); + } } diff --git a/spring-core/src/main/java/org/springframework/core/convert/support/StreamConverter.java b/spring-core/src/main/java/org/springframework/core/convert/support/StreamConverter.java new file mode 100644 index 0000000000..0d51c2af18 --- /dev/null +++ b/spring-core/src/main/java/org/springframework/core/convert/support/StreamConverter.java @@ -0,0 +1,124 @@ +/* + * 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.Collection; +import java.util.HashSet; +import java.util.List; +import java.util.Set; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +import org.springframework.core.convert.ConversionService; +import org.springframework.core.convert.TypeDescriptor; +import org.springframework.core.convert.converter.ConditionalGenericConverter; +import org.springframework.lang.UsesJava8; + +/** + * Convert a {@link Stream} to an from a collection or array, converting the + * element type if necessary. + * + * @author Stephane Nicoll + * @since 4.2 + */ +@UsesJava8 +public class StreamConverter implements ConditionalGenericConverter { + + private static final TypeDescriptor STREAM_TYPE = TypeDescriptor.valueOf(Stream.class); + + private static final Set CONVERTIBLE_TYPES = createConvertibleTypes(); + + private final ConversionService conversionService; + + public StreamConverter(ConversionService conversionService) { + this.conversionService = conversionService; + } + + @Override + public Set getConvertibleTypes() { + return CONVERTIBLE_TYPES; + } + + @Override + public boolean matches(TypeDescriptor sourceType, TypeDescriptor targetType) { + if (sourceType.isAssignableTo(STREAM_TYPE)) { + return matchesFromStream(sourceType.getElementTypeDescriptor(), targetType); + } + if (targetType.isAssignableTo(STREAM_TYPE)) { + return matchesToStream(targetType.getElementTypeDescriptor(), sourceType); + } + return false; + } + + /** + * Validate that a {@link Collection} of the elements held within the stream can be + * converted to the specified {@code targetType}. + * @param elementType the type of the stream elements + * @param targetType the type to convert to + */ + public boolean matchesFromStream(TypeDescriptor elementType, TypeDescriptor targetType) { + TypeDescriptor collectionOfElement = TypeDescriptor.collection(Collection.class, elementType); + return this.conversionService.canConvert(collectionOfElement, targetType); + } + + /** + * Validate that the specified {@code sourceType} can be converted to a {@link Collection} of + * type type of the stream elements + * @param elementType the type of the stream elements + * @param sourceType the type to convert from + */ + public boolean matchesToStream(TypeDescriptor elementType, TypeDescriptor sourceType) { + TypeDescriptor collectionOfElement = TypeDescriptor.collection(Collection.class, elementType); + return this.conversionService.canConvert(sourceType, collectionOfElement); + } + + @Override + public Object convert(Object source, TypeDescriptor sourceType, TypeDescriptor targetType) { + if (sourceType.isAssignableTo(STREAM_TYPE)) { + return convertFromStream((Stream) source, sourceType, targetType); + } + if (targetType.isAssignableTo(STREAM_TYPE)) { + return convertToStream(source, sourceType, targetType); + } + // Should not happen + throw new IllegalStateException("Unexpected source/target types"); + } + + private Object convertFromStream(Stream source, TypeDescriptor streamType, TypeDescriptor targetType) { + List content = source.collect(Collectors.toList()); + TypeDescriptor listType = TypeDescriptor.collection(List.class, streamType.getElementTypeDescriptor()); + return this.conversionService.convert(content, listType, targetType); + } + + private Object convertToStream(Object source, TypeDescriptor sourceType, TypeDescriptor streamType) { + TypeDescriptor targetCollection = + TypeDescriptor.collection(List.class, streamType.getElementTypeDescriptor()); + List target = (List) this.conversionService.convert(source, sourceType, targetCollection); + return target.stream(); + } + + + private static Set createConvertibleTypes() { + Set convertiblePairs = new HashSet(); + convertiblePairs.add(new ConvertiblePair(Stream.class, Collection.class)); + convertiblePairs.add(new ConvertiblePair(Stream.class, Object[].class)); + convertiblePairs.add(new ConvertiblePair(Collection.class, Stream.class)); + convertiblePairs.add(new ConvertiblePair(Object[].class, Stream.class)); + return convertiblePairs; + } + +} diff --git a/spring-core/src/test/java/org/springframework/core/convert/support/DefaultConversionTests.java b/spring-core/src/test/java/org/springframework/core/convert/support/DefaultConversionTests.java index d932aa2eba..43b94f324e 100644 --- a/spring-core/src/test/java/org/springframework/core/convert/support/DefaultConversionTests.java +++ b/spring-core/src/test/java/org/springframework/core/convert/support/DefaultConversionTests.java @@ -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 result = (List) 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 genericStream; + + @Test + public void convertArrayToStream() throws Exception { + String[] source = {"1", "3", "4"}; + @SuppressWarnings("unchecked") + Stream result = (Stream) 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 colors = (List) conversionService.convert(new String[] { "ffffff", "#000000" }, TypeDescriptor.valueOf(String[].class), new TypeDescriptor(new MethodParameter(getClass().getMethod("handlerMethod", List.class), 0))); + @SuppressWarnings("unchecked") + List colors = (List) 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 list = Arrays.asList(new String[] { "foo", "bar" }); + List list = Arrays.asList("foo", "bar"); String result = conversionService.convert(list, String.class); assertEquals("foo,bar", result); } @Test public void convertCollectionToStringWithElementConversion() throws Exception { - List list = Arrays.asList(new Integer[] { 3, 5 }); + List 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 result = (List) 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 result = (List) 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 bar = (List) 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 bar = (List) 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 strings = new ArrayList(); strings.add("3"); strings.add("9"); - List integers = (List) conversionService.convert(strings, TypeDescriptor.collection(List.class, TypeDescriptor.valueOf(Integer.class))); + @SuppressWarnings("unchecked") + List integers = (List) 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 foo = new HashMap(); foo.put("1", "BAR"); foo.put("2", "BAZ"); - Map map = (Map) conversionService.convert(foo, + @SuppressWarnings("unchecked") + Map map = (Map) 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 strings = new HashMap(); strings.put("3", "9"); strings.put("6", "31"); - Map integers = (Map) conversionService.convert(strings, TypeDescriptor.map(Map.class, TypeDescriptor.valueOf(Integer.class), TypeDescriptor.valueOf(Integer.class))); + @SuppressWarnings("unchecked") + Map integers = (Map) 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)); } diff --git a/spring-core/src/test/java/org/springframework/core/convert/support/StreamConverterTest.java b/spring-core/src/test/java/org/springframework/core/convert/support/StreamConverterTest.java new file mode 100644 index 0000000000..d0f80ba521 --- /dev/null +++ b/spring-core/src/test/java/org/springframework/core/convert/support/StreamConverterTest.java @@ -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 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 content = (List) 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 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 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 content = (List) 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 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 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 content = (Stream) 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() { + @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 content = (Stream) result; + assertEquals(2, content.filter(x -> x).count()); + } + + @Test + public void convertFromListToRawStream() throws NoSuchFieldException { + List 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 content = (Stream) 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 listOfStrings; + + public Long[] arrayOfLongs; + + public Stream streamOfIntegers; + + public Stream streamOfBooleans; + + public Stream rawStream; + + public List rawList; + } + +} \ No newline at end of file