Skip to content

Commit f933b45

Browse files
committed
Complete Vavr collection detection on TypeInformation and CustomConversions.
Moved Vavr collection converters into a type in the utility package. Register the converters via CustomConversions.registerConvertersIn(…) to make sure that all the Spring Data object mapping converters automatically benefit from a ConversionService that is capable of translating between Java-native collections and Vavr ones. Issue #2511.
1 parent 5b56895 commit f933b45

File tree

5 files changed

+106
-59
lines changed

5 files changed

+106
-59
lines changed

src/main/java/org/springframework/data/convert/CustomConversions.java

Lines changed: 4 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2011-2021 the original author or authors.
2+
* Copyright 2011-2022 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -16,24 +16,14 @@
1616
package org.springframework.data.convert;
1717

1818
import java.lang.annotation.Annotation;
19-
import java.util.ArrayList;
20-
import java.util.Arrays;
21-
import java.util.Collection;
22-
import java.util.Collections;
23-
import java.util.HashSet;
24-
import java.util.LinkedHashSet;
25-
import java.util.List;
26-
import java.util.Map;
27-
import java.util.Optional;
28-
import java.util.Set;
19+
import java.util.*;
2920
import java.util.concurrent.ConcurrentHashMap;
3021
import java.util.function.Function;
3122
import java.util.function.Predicate;
3223
import java.util.stream.Collectors;
3324

3425
import org.apache.commons.logging.Log;
3526
import org.apache.commons.logging.LogFactory;
36-
3727
import org.springframework.core.GenericTypeResolver;
3828
import org.springframework.core.annotation.AnnotationUtils;
3929
import org.springframework.core.convert.converter.Converter;
@@ -46,6 +36,7 @@
4636
import org.springframework.data.mapping.model.SimpleTypeHolder;
4737
import org.springframework.data.util.Predicates;
4838
import org.springframework.data.util.Streamable;
39+
import org.springframework.data.util.VavrCollectionConverters;
4940
import org.springframework.lang.Nullable;
5041
import org.springframework.util.Assert;
5142
import org.springframework.util.ObjectUtils;
@@ -178,6 +169,7 @@ public void registerConvertersIn(ConverterRegistry conversionService) {
178169
Assert.notNull(conversionService, "ConversionService must not be null!");
179170

180171
converters.forEach(it -> registerConverterIn(it, conversionService));
172+
VavrCollectionConverters.getConvertersToRegister().forEach(it -> registerConverterIn(it, conversionService));
181173
}
182174

183175
/**

src/main/java/org/springframework/data/repository/util/QueryExecutionConverters.java

Lines changed: 13 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2014-2021 the original author or authors.
2+
* Copyright 2014-2022 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -43,6 +43,7 @@
4343
import org.springframework.data.util.StreamUtils;
4444
import org.springframework.data.util.Streamable;
4545
import org.springframework.data.util.TypeInformation;
46+
import org.springframework.data.util.VavrCollectionConverters;
4647
import org.springframework.lang.Nullable;
4748
import org.springframework.scheduling.annotation.AsyncResult;
4849
import org.springframework.util.Assert;
@@ -99,7 +100,7 @@ public abstract class QueryExecutionConverters {
99100

100101
if (VAVR_PRESENT) {
101102

102-
WRAPPER_TYPES.add(VavrCollections.ToJavaConverter.INSTANCE.getWrapperType());
103+
WRAPPER_TYPES.add(VavrTraversableUnwrapper.INSTANCE.getWrapperType());
103104
UNWRAPPERS.add(VavrTraversableUnwrapper.INSTANCE);
104105

105106
// Try support
@@ -196,7 +197,7 @@ public static void registerConvertersIn(ConfigurableConversionService conversion
196197
NullableWrapperConverters.registerConvertersIn(conversionService);
197198

198199
if (VAVR_PRESENT) {
199-
conversionService.addConverter(VavrCollections.FromJavaConverter.INSTANCE);
200+
conversionService.addConverter(VavrCollectionConverters.FromJavaConverter.INSTANCE);
200201
}
201202

202203
conversionService.addConverter(new NullableWrapperToCompletableFutureConverter());
@@ -408,23 +409,29 @@ private enum VavrTraversableUnwrapper implements Converter<Object, Object> {
408409

409410
INSTANCE;
410411

412+
private static final TypeDescriptor OBJECT_DESCRIPTOR = TypeDescriptor.valueOf(Object.class);
413+
411414
/*
412415
* (non-Javadoc)
413416
* @see org.springframework.core.convert.converter.Converter#convert(java.lang.Object)
414417
*/
415418
@Nullable
416419
@Override
417-
@SuppressWarnings("unchecked")
420+
@SuppressWarnings("null")
418421
public Object convert(Object source) {
419422

420423
if (source instanceof io.vavr.collection.Traversable) {
421-
return VavrCollections.ToJavaConverter.INSTANCE.convert(source);
424+
return VavrCollectionConverters.ToJavaConverter.INSTANCE //
425+
.convert(source, TypeDescriptor.forObject(source), OBJECT_DESCRIPTOR);
422426
}
423427

424428
return source;
425429
}
426-
}
427430

431+
public WrapperType getWrapperType() {
432+
return WrapperType.multiValue(io.vavr.collection.Traversable.class);
433+
}
434+
}
428435

429436
private static class IterableToStreamableConverter implements ConditionalGenericConverter {
430437

src/main/java/org/springframework/data/util/TypeDiscoverer.java

Lines changed: 22 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2011-2021 the original author or authors.
2+
* Copyright 2011-2022 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -25,16 +25,7 @@
2525
import java.lang.reflect.Type;
2626
import java.lang.reflect.TypeVariable;
2727
import java.lang.reflect.WildcardType;
28-
import java.util.ArrayList;
29-
import java.util.Arrays;
30-
import java.util.Collection;
31-
import java.util.Collections;
32-
import java.util.HashMap;
33-
import java.util.HashSet;
34-
import java.util.List;
35-
import java.util.Map;
36-
import java.util.Optional;
37-
import java.util.Set;
28+
import java.util.*;
3829
import java.util.concurrent.ConcurrentHashMap;
3930
import java.util.stream.Collectors;
4031

@@ -384,23 +375,10 @@ public boolean isCollectionLike() {
384375

385376
return rawType.isArray() //
386377
|| Iterable.class.equals(rawType) //
387-
|| Streamable.class.isAssignableFrom(rawType)
378+
|| Streamable.class.isAssignableFrom(rawType) //
388379
|| isCollection();
389380
}
390381

391-
private boolean isCollection() {
392-
393-
Class<S> type = getType();
394-
395-
for (Class<?> collectionType : COLLECTION_TYPES) {
396-
if (collectionType.isAssignableFrom(type)) {
397-
return true;
398-
}
399-
}
400-
401-
return false;
402-
}
403-
404382
/*
405383
* (non-Javadoc)
406384
* @see org.springframework.data.util.TypeInformation#getComponentType()
@@ -427,7 +405,7 @@ protected TypeInformation<?> doGetComponentType() {
427405
return getTypeArgument(Iterable.class, 0);
428406
}
429407

430-
if(isNullableWrapper()) {
408+
if (isNullableWrapper()) {
431409
return getTypeArgument(rawType, 0);
432410
}
433411

@@ -613,6 +591,24 @@ public int hashCode() {
613591
return hashCode;
614592
}
615593

594+
/**
595+
* Returns whether the current type is considered a collection.
596+
*
597+
* @return
598+
*/
599+
private boolean isCollection() {
600+
601+
Class<S> type = getType();
602+
603+
for (Class<?> collectionType : COLLECTION_TYPES) {
604+
if (collectionType.isAssignableFrom(type)) {
605+
return true;
606+
}
607+
}
608+
609+
return false;
610+
}
611+
616612
/**
617613
* A synthetic {@link ParameterizedType}.
618614
*

src/main/java/org/springframework/data/repository/util/VavrCollections.java renamed to src/main/java/org/springframework/data/util/VavrCollectionConverters.java

Lines changed: 53 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2016-2021 the original author or authors.
2+
* Copyright 2022 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -13,50 +13,91 @@
1313
* See the License for the specific language governing permissions and
1414
* limitations under the License.
1515
*/
16-
package org.springframework.data.repository.util;
16+
package org.springframework.data.util;
1717

1818
import io.vavr.collection.LinkedHashMap;
1919
import io.vavr.collection.LinkedHashSet;
2020
import io.vavr.collection.Traversable;
2121

22+
import java.util.Arrays;
2223
import java.util.Collection;
2324
import java.util.Collections;
2425
import java.util.HashSet;
2526
import java.util.List;
2627
import java.util.Map;
2728
import java.util.Set;
29+
import java.util.stream.Collectors;
2830

2931
import org.springframework.core.convert.TypeDescriptor;
3032
import org.springframework.core.convert.converter.ConditionalGenericConverter;
31-
import org.springframework.core.convert.converter.Converter;
32-
import org.springframework.data.repository.util.QueryExecutionConverters.WrapperType;
3333
import org.springframework.lang.NonNull;
3434
import org.springframework.lang.Nullable;
35+
import org.springframework.util.ClassUtils;
3536

3637
/**
3738
* Converter implementations to map from and to Vavr collections.
3839
*
3940
* @author Oliver Gierke
4041
* @author Christoph Strobl
41-
* @since 2.0
42+
* @since 2.7
4243
*/
43-
class VavrCollections {
44+
public class VavrCollectionConverters {
4445

45-
public enum ToJavaConverter implements Converter<Object, Object> {
46+
private static final boolean VAVR_PRESENT = ClassUtils.isPresent("io.vavr.control.Option",
47+
NullableWrapperConverters.class.getClassLoader());
4648

47-
INSTANCE;
49+
public static Collection<Object> getConvertersToRegister() {
4850

49-
public WrapperType getWrapperType() {
50-
return WrapperType.multiValue(io.vavr.collection.Traversable.class);
51+
if (!VAVR_PRESENT) {
52+
return Collections.emptyList();
5153
}
5254

55+
return Arrays.asList(ToJavaConverter.INSTANCE, FromJavaConverter.INSTANCE);
56+
}
57+
58+
public enum ToJavaConverter implements ConditionalGenericConverter {
59+
60+
INSTANCE;
61+
62+
private static final TypeDescriptor TRAVERSAL_TYPE = TypeDescriptor.valueOf(Traversable.class);
63+
private static final Set<Class<?>> COLLECTIONS_AND_MAP = new HashSet<>(
64+
Arrays.asList(Collection.class, List.class, Set.class, Map.class));
65+
5366
/*
5467
* (non-Javadoc)
55-
* @see org.springframework.core.convert.converter.Converter#convert(java.lang.Object)
68+
* @see org.springframework.core.convert.converter.GenericConverter#getConvertibleTypes()
5669
*/
5770
@NonNull
5871
@Override
59-
public Object convert(Object source) {
72+
public Set<ConvertiblePair> getConvertibleTypes() {
73+
74+
return COLLECTIONS_AND_MAP.stream()
75+
.map(it -> new ConvertiblePair(Traversable.class, it))
76+
.collect(Collectors.toSet());
77+
}
78+
79+
/*
80+
* (non-Javadoc)
81+
* @see org.springframework.core.convert.converter.ConditionalConverter#matches(org.springframework.core.convert.TypeDescriptor, org.springframework.core.convert.TypeDescriptor)
82+
*/
83+
@Override
84+
public boolean matches(TypeDescriptor sourceType, TypeDescriptor targetType) {
85+
86+
return sourceType.isAssignableTo(TRAVERSAL_TYPE)
87+
&& COLLECTIONS_AND_MAP.contains(targetType.getType());
88+
}
89+
90+
/*
91+
* (non-Javadoc)
92+
* @see org.springframework.core.convert.converter.GenericConverter#convert(java.lang.Object, org.springframework.core.convert.TypeDescriptor, org.springframework.core.convert.TypeDescriptor)
93+
*/
94+
@Nullable
95+
@Override
96+
public Object convert(@Nullable Object source, TypeDescriptor sourceType, TypeDescriptor targetType) {
97+
98+
if (source == null) {
99+
return null;
100+
}
60101

61102
if (source instanceof io.vavr.collection.Seq) {
62103
return ((io.vavr.collection.Seq<?>) source).asJava();

src/test/java/org/springframework/data/convert/CustomConversionsUnitTests.java

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2011-2021 the original author or authors.
2+
* Copyright 2011-2022 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -22,6 +22,7 @@
2222
import java.util.Arrays;
2323
import java.util.Collections;
2424
import java.util.Date;
25+
import java.util.List;
2526
import java.util.Locale;
2627
import java.util.Map;
2728
import java.util.function.Predicate;
@@ -30,7 +31,6 @@
3031
import org.jmolecules.ddd.types.Identifier;
3132
import org.joda.time.DateTime;
3233
import org.junit.jupiter.api.Test;
33-
3434
import org.springframework.aop.framework.ProxyFactory;
3535
import org.springframework.core.convert.converter.Converter;
3636
import org.springframework.core.convert.converter.ConverterFactory;
@@ -46,7 +46,6 @@
4646
import org.springframework.data.convert.ThreeTenBackPortConverters.LocalDateTimeToJavaTimeInstantConverter;
4747
import org.springframework.data.geo.Point;
4848
import org.springframework.data.mapping.model.SimpleTypeHolder;
49-
5049
import org.threeten.bp.LocalDateTime;
5150

5251
/**
@@ -292,6 +291,18 @@ void addsIdentifierConvertersByDefault() {
292291
assertThat(conversions.hasCustomReadTarget(String.class, Identifier.class)).isTrue();
293292
}
294293

294+
@Test // GH-2511
295+
void registersVavrConverters() {
296+
297+
ConfigurableConversionService conversionService = new DefaultConversionService();
298+
299+
new CustomConversions(StoreConversions.NONE, Collections.emptyList())
300+
.registerConvertersIn(conversionService);
301+
302+
assertThat(conversionService.canConvert(io.vavr.collection.List.class, List.class)).isTrue();
303+
assertThat(conversionService.canConvert(List.class, io.vavr.collection.List.class)).isTrue();
304+
}
305+
295306
private static Class<?> createProxyTypeFor(Class<?> type) {
296307

297308
ProxyFactory factory = new ProxyFactory();

0 commit comments

Comments
 (0)