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.
This commit is contained in:
@@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright 2011-2021 the original author or authors.
|
* Copyright 2011-2022 the original author or authors.
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
@@ -16,16 +16,7 @@
|
|||||||
package org.springframework.data.convert;
|
package org.springframework.data.convert;
|
||||||
|
|
||||||
import java.lang.annotation.Annotation;
|
import java.lang.annotation.Annotation;
|
||||||
import java.util.ArrayList;
|
import java.util.*;
|
||||||
import java.util.Arrays;
|
|
||||||
import java.util.Collection;
|
|
||||||
import java.util.Collections;
|
|
||||||
import java.util.HashSet;
|
|
||||||
import java.util.LinkedHashSet;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Map;
|
|
||||||
import java.util.Optional;
|
|
||||||
import java.util.Set;
|
|
||||||
import java.util.concurrent.ConcurrentHashMap;
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
import java.util.function.Function;
|
import java.util.function.Function;
|
||||||
import java.util.function.Predicate;
|
import java.util.function.Predicate;
|
||||||
@@ -33,7 +24,6 @@ import java.util.stream.Collectors;
|
|||||||
|
|
||||||
import org.apache.commons.logging.Log;
|
import org.apache.commons.logging.Log;
|
||||||
import org.apache.commons.logging.LogFactory;
|
import org.apache.commons.logging.LogFactory;
|
||||||
|
|
||||||
import org.springframework.core.GenericTypeResolver;
|
import org.springframework.core.GenericTypeResolver;
|
||||||
import org.springframework.core.annotation.AnnotationUtils;
|
import org.springframework.core.annotation.AnnotationUtils;
|
||||||
import org.springframework.core.convert.converter.Converter;
|
import org.springframework.core.convert.converter.Converter;
|
||||||
@@ -46,6 +36,7 @@ import org.springframework.data.convert.ConverterBuilder.ConverterAware;
|
|||||||
import org.springframework.data.mapping.model.SimpleTypeHolder;
|
import org.springframework.data.mapping.model.SimpleTypeHolder;
|
||||||
import org.springframework.data.util.Predicates;
|
import org.springframework.data.util.Predicates;
|
||||||
import org.springframework.data.util.Streamable;
|
import org.springframework.data.util.Streamable;
|
||||||
|
import org.springframework.data.util.VavrCollectionConverters;
|
||||||
import org.springframework.lang.Nullable;
|
import org.springframework.lang.Nullable;
|
||||||
import org.springframework.util.Assert;
|
import org.springframework.util.Assert;
|
||||||
import org.springframework.util.ObjectUtils;
|
import org.springframework.util.ObjectUtils;
|
||||||
@@ -178,6 +169,7 @@ public class CustomConversions {
|
|||||||
Assert.notNull(conversionService, "ConversionService must not be null!");
|
Assert.notNull(conversionService, "ConversionService must not be null!");
|
||||||
|
|
||||||
converters.forEach(it -> registerConverterIn(it, conversionService));
|
converters.forEach(it -> registerConverterIn(it, conversionService));
|
||||||
|
VavrCollectionConverters.getConvertersToRegister().forEach(it -> registerConverterIn(it, conversionService));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright 2014-2021 the original author or authors.
|
* Copyright 2014-2022 the original author or authors.
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
@@ -43,6 +43,7 @@ import org.springframework.data.util.NullableWrapperConverters;
|
|||||||
import org.springframework.data.util.StreamUtils;
|
import org.springframework.data.util.StreamUtils;
|
||||||
import org.springframework.data.util.Streamable;
|
import org.springframework.data.util.Streamable;
|
||||||
import org.springframework.data.util.TypeInformation;
|
import org.springframework.data.util.TypeInformation;
|
||||||
|
import org.springframework.data.util.VavrCollectionConverters;
|
||||||
import org.springframework.lang.Nullable;
|
import org.springframework.lang.Nullable;
|
||||||
import org.springframework.scheduling.annotation.AsyncResult;
|
import org.springframework.scheduling.annotation.AsyncResult;
|
||||||
import org.springframework.util.Assert;
|
import org.springframework.util.Assert;
|
||||||
@@ -99,7 +100,7 @@ public abstract class QueryExecutionConverters {
|
|||||||
|
|
||||||
if (VAVR_PRESENT) {
|
if (VAVR_PRESENT) {
|
||||||
|
|
||||||
WRAPPER_TYPES.add(VavrCollections.ToJavaConverter.INSTANCE.getWrapperType());
|
WRAPPER_TYPES.add(VavrTraversableUnwrapper.INSTANCE.getWrapperType());
|
||||||
UNWRAPPERS.add(VavrTraversableUnwrapper.INSTANCE);
|
UNWRAPPERS.add(VavrTraversableUnwrapper.INSTANCE);
|
||||||
|
|
||||||
// Try support
|
// Try support
|
||||||
@@ -196,7 +197,7 @@ public abstract class QueryExecutionConverters {
|
|||||||
NullableWrapperConverters.registerConvertersIn(conversionService);
|
NullableWrapperConverters.registerConvertersIn(conversionService);
|
||||||
|
|
||||||
if (VAVR_PRESENT) {
|
if (VAVR_PRESENT) {
|
||||||
conversionService.addConverter(VavrCollections.FromJavaConverter.INSTANCE);
|
conversionService.addConverter(VavrCollectionConverters.FromJavaConverter.INSTANCE);
|
||||||
}
|
}
|
||||||
|
|
||||||
conversionService.addConverter(new NullableWrapperToCompletableFutureConverter());
|
conversionService.addConverter(new NullableWrapperToCompletableFutureConverter());
|
||||||
@@ -408,23 +409,29 @@ public abstract class QueryExecutionConverters {
|
|||||||
|
|
||||||
INSTANCE;
|
INSTANCE;
|
||||||
|
|
||||||
|
private static final TypeDescriptor OBJECT_DESCRIPTOR = TypeDescriptor.valueOf(Object.class);
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* (non-Javadoc)
|
* (non-Javadoc)
|
||||||
* @see org.springframework.core.convert.converter.Converter#convert(java.lang.Object)
|
* @see org.springframework.core.convert.converter.Converter#convert(java.lang.Object)
|
||||||
*/
|
*/
|
||||||
@Nullable
|
@Nullable
|
||||||
@Override
|
@Override
|
||||||
@SuppressWarnings("unchecked")
|
@SuppressWarnings("null")
|
||||||
public Object convert(Object source) {
|
public Object convert(Object source) {
|
||||||
|
|
||||||
if (source instanceof io.vavr.collection.Traversable) {
|
if (source instanceof io.vavr.collection.Traversable) {
|
||||||
return VavrCollections.ToJavaConverter.INSTANCE.convert(source);
|
return VavrCollectionConverters.ToJavaConverter.INSTANCE //
|
||||||
|
.convert(source, TypeDescriptor.forObject(source), OBJECT_DESCRIPTOR);
|
||||||
}
|
}
|
||||||
|
|
||||||
return source;
|
return source;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
|
public WrapperType getWrapperType() {
|
||||||
|
return WrapperType.multiValue(io.vavr.collection.Traversable.class);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private static class IterableToStreamableConverter implements ConditionalGenericConverter {
|
private static class IterableToStreamableConverter implements ConditionalGenericConverter {
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright 2011-2021 the original author or authors.
|
* Copyright 2011-2022 the original author or authors.
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
@@ -25,16 +25,7 @@ import java.lang.reflect.ParameterizedType;
|
|||||||
import java.lang.reflect.Type;
|
import java.lang.reflect.Type;
|
||||||
import java.lang.reflect.TypeVariable;
|
import java.lang.reflect.TypeVariable;
|
||||||
import java.lang.reflect.WildcardType;
|
import java.lang.reflect.WildcardType;
|
||||||
import java.util.ArrayList;
|
import java.util.*;
|
||||||
import java.util.Arrays;
|
|
||||||
import java.util.Collection;
|
|
||||||
import java.util.Collections;
|
|
||||||
import java.util.HashMap;
|
|
||||||
import java.util.HashSet;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Map;
|
|
||||||
import java.util.Optional;
|
|
||||||
import java.util.Set;
|
|
||||||
import java.util.concurrent.ConcurrentHashMap;
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
@@ -384,23 +375,10 @@ class TypeDiscoverer<S> implements TypeInformation<S> {
|
|||||||
|
|
||||||
return rawType.isArray() //
|
return rawType.isArray() //
|
||||||
|| Iterable.class.equals(rawType) //
|
|| Iterable.class.equals(rawType) //
|
||||||
|| Streamable.class.isAssignableFrom(rawType)
|
|| Streamable.class.isAssignableFrom(rawType) //
|
||||||
|| isCollection();
|
|| isCollection();
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean isCollection() {
|
|
||||||
|
|
||||||
Class<S> type = getType();
|
|
||||||
|
|
||||||
for (Class<?> collectionType : COLLECTION_TYPES) {
|
|
||||||
if (collectionType.isAssignableFrom(type)) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* (non-Javadoc)
|
* (non-Javadoc)
|
||||||
* @see org.springframework.data.util.TypeInformation#getComponentType()
|
* @see org.springframework.data.util.TypeInformation#getComponentType()
|
||||||
@@ -427,7 +405,7 @@ class TypeDiscoverer<S> implements TypeInformation<S> {
|
|||||||
return getTypeArgument(Iterable.class, 0);
|
return getTypeArgument(Iterable.class, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
if(isNullableWrapper()) {
|
if (isNullableWrapper()) {
|
||||||
return getTypeArgument(rawType, 0);
|
return getTypeArgument(rawType, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -613,6 +591,24 @@ class TypeDiscoverer<S> implements TypeInformation<S> {
|
|||||||
return hashCode;
|
return hashCode;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns whether the current type is considered a collection.
|
||||||
|
*
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
private boolean isCollection() {
|
||||||
|
|
||||||
|
Class<S> type = getType();
|
||||||
|
|
||||||
|
for (Class<?> collectionType : COLLECTION_TYPES) {
|
||||||
|
if (collectionType.isAssignableFrom(type)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A synthetic {@link ParameterizedType}.
|
* A synthetic {@link ParameterizedType}.
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright 2016-2021 the original author or authors.
|
* Copyright 2022 the original author or authors.
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
@@ -13,50 +13,91 @@
|
|||||||
* See the License for the specific language governing permissions and
|
* See the License for the specific language governing permissions and
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
package org.springframework.data.repository.util;
|
package org.springframework.data.util;
|
||||||
|
|
||||||
import io.vavr.collection.LinkedHashMap;
|
import io.vavr.collection.LinkedHashMap;
|
||||||
import io.vavr.collection.LinkedHashSet;
|
import io.vavr.collection.LinkedHashSet;
|
||||||
import io.vavr.collection.Traversable;
|
import io.vavr.collection.Traversable;
|
||||||
|
|
||||||
|
import java.util.Arrays;
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
import org.springframework.core.convert.TypeDescriptor;
|
import org.springframework.core.convert.TypeDescriptor;
|
||||||
import org.springframework.core.convert.converter.ConditionalGenericConverter;
|
import org.springframework.core.convert.converter.ConditionalGenericConverter;
|
||||||
import org.springframework.core.convert.converter.Converter;
|
|
||||||
import org.springframework.data.repository.util.QueryExecutionConverters.WrapperType;
|
|
||||||
import org.springframework.lang.NonNull;
|
import org.springframework.lang.NonNull;
|
||||||
import org.springframework.lang.Nullable;
|
import org.springframework.lang.Nullable;
|
||||||
|
import org.springframework.util.ClassUtils;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Converter implementations to map from and to Vavr collections.
|
* Converter implementations to map from and to Vavr collections.
|
||||||
*
|
*
|
||||||
* @author Oliver Gierke
|
* @author Oliver Gierke
|
||||||
* @author Christoph Strobl
|
* @author Christoph Strobl
|
||||||
* @since 2.0
|
* @since 2.7
|
||||||
*/
|
*/
|
||||||
class VavrCollections {
|
public class VavrCollectionConverters {
|
||||||
|
|
||||||
public enum ToJavaConverter implements Converter<Object, Object> {
|
private static final boolean VAVR_PRESENT = ClassUtils.isPresent("io.vavr.control.Option",
|
||||||
|
NullableWrapperConverters.class.getClassLoader());
|
||||||
|
|
||||||
|
public static Collection<Object> getConvertersToRegister() {
|
||||||
|
|
||||||
|
if (!VAVR_PRESENT) {
|
||||||
|
return Collections.emptyList();
|
||||||
|
}
|
||||||
|
|
||||||
|
return Arrays.asList(ToJavaConverter.INSTANCE, FromJavaConverter.INSTANCE);
|
||||||
|
}
|
||||||
|
|
||||||
|
public enum ToJavaConverter implements ConditionalGenericConverter {
|
||||||
|
|
||||||
INSTANCE;
|
INSTANCE;
|
||||||
|
|
||||||
public WrapperType getWrapperType() {
|
private static final TypeDescriptor TRAVERSAL_TYPE = TypeDescriptor.valueOf(Traversable.class);
|
||||||
return WrapperType.multiValue(io.vavr.collection.Traversable.class);
|
private static final Set<Class<?>> COLLECTIONS_AND_MAP = new HashSet<>(
|
||||||
|
Arrays.asList(Collection.class, List.class, Set.class, Map.class));
|
||||||
|
|
||||||
|
/*
|
||||||
|
* (non-Javadoc)
|
||||||
|
* @see org.springframework.core.convert.converter.GenericConverter#getConvertibleTypes()
|
||||||
|
*/
|
||||||
|
@NonNull
|
||||||
|
@Override
|
||||||
|
public Set<ConvertiblePair> getConvertibleTypes() {
|
||||||
|
|
||||||
|
return COLLECTIONS_AND_MAP.stream()
|
||||||
|
.map(it -> new ConvertiblePair(Traversable.class, it))
|
||||||
|
.collect(Collectors.toSet());
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* (non-Javadoc)
|
* (non-Javadoc)
|
||||||
* @see org.springframework.core.convert.converter.Converter#convert(java.lang.Object)
|
* @see org.springframework.core.convert.converter.ConditionalConverter#matches(org.springframework.core.convert.TypeDescriptor, org.springframework.core.convert.TypeDescriptor)
|
||||||
*/
|
*/
|
||||||
@NonNull
|
|
||||||
@Override
|
@Override
|
||||||
public Object convert(Object source) {
|
public boolean matches(TypeDescriptor sourceType, TypeDescriptor targetType) {
|
||||||
|
|
||||||
|
return sourceType.isAssignableTo(TRAVERSAL_TYPE)
|
||||||
|
&& COLLECTIONS_AND_MAP.contains(targetType.getType());
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* (non-Javadoc)
|
||||||
|
* @see org.springframework.core.convert.converter.GenericConverter#convert(java.lang.Object, org.springframework.core.convert.TypeDescriptor, org.springframework.core.convert.TypeDescriptor)
|
||||||
|
*/
|
||||||
|
@Nullable
|
||||||
|
@Override
|
||||||
|
public Object convert(@Nullable Object source, TypeDescriptor sourceType, TypeDescriptor targetType) {
|
||||||
|
|
||||||
|
if (source == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
if (source instanceof io.vavr.collection.Seq) {
|
if (source instanceof io.vavr.collection.Seq) {
|
||||||
return ((io.vavr.collection.Seq<?>) source).asJava();
|
return ((io.vavr.collection.Seq<?>) source).asJava();
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright 2011-2021 the original author or authors.
|
* Copyright 2011-2022 the original author or authors.
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
@@ -22,6 +22,7 @@ import static org.mockito.Mockito.*;
|
|||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.Date;
|
import java.util.Date;
|
||||||
|
import java.util.List;
|
||||||
import java.util.Locale;
|
import java.util.Locale;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.function.Predicate;
|
import java.util.function.Predicate;
|
||||||
@@ -30,7 +31,6 @@ import org.jmolecules.ddd.types.Association;
|
|||||||
import org.jmolecules.ddd.types.Identifier;
|
import org.jmolecules.ddd.types.Identifier;
|
||||||
import org.joda.time.DateTime;
|
import org.joda.time.DateTime;
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
import org.springframework.aop.framework.ProxyFactory;
|
import org.springframework.aop.framework.ProxyFactory;
|
||||||
import org.springframework.core.convert.converter.Converter;
|
import org.springframework.core.convert.converter.Converter;
|
||||||
import org.springframework.core.convert.converter.ConverterFactory;
|
import org.springframework.core.convert.converter.ConverterFactory;
|
||||||
@@ -46,7 +46,6 @@ import org.springframework.data.convert.Jsr310Converters.LocalDateTimeToDateConv
|
|||||||
import org.springframework.data.convert.ThreeTenBackPortConverters.LocalDateTimeToJavaTimeInstantConverter;
|
import org.springframework.data.convert.ThreeTenBackPortConverters.LocalDateTimeToJavaTimeInstantConverter;
|
||||||
import org.springframework.data.geo.Point;
|
import org.springframework.data.geo.Point;
|
||||||
import org.springframework.data.mapping.model.SimpleTypeHolder;
|
import org.springframework.data.mapping.model.SimpleTypeHolder;
|
||||||
|
|
||||||
import org.threeten.bp.LocalDateTime;
|
import org.threeten.bp.LocalDateTime;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -292,6 +291,18 @@ class CustomConversionsUnitTests {
|
|||||||
assertThat(conversions.hasCustomReadTarget(String.class, Identifier.class)).isTrue();
|
assertThat(conversions.hasCustomReadTarget(String.class, Identifier.class)).isTrue();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test // GH-2511
|
||||||
|
void registersVavrConverters() {
|
||||||
|
|
||||||
|
ConfigurableConversionService conversionService = new DefaultConversionService();
|
||||||
|
|
||||||
|
new CustomConversions(StoreConversions.NONE, Collections.emptyList())
|
||||||
|
.registerConvertersIn(conversionService);
|
||||||
|
|
||||||
|
assertThat(conversionService.canConvert(io.vavr.collection.List.class, List.class)).isTrue();
|
||||||
|
assertThat(conversionService.canConvert(List.class, io.vavr.collection.List.class)).isTrue();
|
||||||
|
}
|
||||||
|
|
||||||
private static Class<?> createProxyTypeFor(Class<?> type) {
|
private static Class<?> createProxyTypeFor(Class<?> type) {
|
||||||
|
|
||||||
ProxyFactory factory = new ProxyFactory();
|
ProxyFactory factory = new ProxyFactory();
|
||||||
|
|||||||
Reference in New Issue
Block a user