Skip to content

Commit 90fb5c7

Browse files
committed
Support for Eclipse Collections.
Using the newly introduced CustomCollectionsRegistrar SPI to provide support for Eclipse Collections (Imm|M)utable(List|Set|Bag|Map). Fixes #2618.
1 parent 6ebe288 commit 90fb5c7

File tree

5 files changed

+365
-4
lines changed

5 files changed

+365
-4
lines changed

pom.xml

+17
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
<properties>
1919
<javaslang>2.0.6</javaslang>
2020
<vavr>0.10.4</vavr>
21+
<eclipse-collections>11.0.0</eclipse-collections>
2122
<scala>2.11.7</scala>
2223
<xmlbeam>1.4.23</xmlbeam>
2324

@@ -223,6 +224,22 @@
223224
<version>${vavr}</version>
224225
<optional>true</optional>
225226
</dependency>
227+
228+
<!-- Eclipse Collections -->
229+
230+
<dependency>
231+
<groupId>org.eclipse.collections</groupId>
232+
<artifactId>eclipse-collections-api</artifactId>
233+
<version>${eclipse-collections}</version>
234+
<optional>true</optional>
235+
</dependency>
236+
237+
<dependency>
238+
<groupId>org.eclipse.collections</groupId>
239+
<artifactId>eclipse-collections</artifactId>
240+
<version>${eclipse-collections}</version>
241+
<scope>test</scope>
242+
</dependency>
226243

227244
<dependency>
228245
<groupId>javax.el</groupId>

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

+244
Original file line numberDiff line numberDiff line change
@@ -34,8 +34,24 @@
3434
import java.util.function.Supplier;
3535
import java.util.stream.Collectors;
3636

37+
import org.eclipse.collections.api.RichIterable;
38+
import org.eclipse.collections.api.bag.ImmutableBag;
39+
import org.eclipse.collections.api.bag.MutableBag;
40+
import org.eclipse.collections.api.factory.Bags;
41+
import org.eclipse.collections.api.factory.Lists;
42+
import org.eclipse.collections.api.factory.Maps;
43+
import org.eclipse.collections.api.factory.Sets;
44+
import org.eclipse.collections.api.list.ImmutableList;
45+
import org.eclipse.collections.api.list.MutableList;
46+
import org.eclipse.collections.api.map.ImmutableMap;
47+
import org.eclipse.collections.api.map.MapIterable;
48+
import org.eclipse.collections.api.map.MutableMap;
49+
import org.eclipse.collections.api.set.ImmutableSet;
50+
import org.eclipse.collections.api.set.MutableSet;
3751
import org.springframework.core.convert.TypeDescriptor;
52+
import org.springframework.core.convert.converter.ConditionalConverter;
3853
import org.springframework.core.convert.converter.ConditionalGenericConverter;
54+
import org.springframework.core.convert.converter.Converter;
3955
import org.springframework.core.convert.converter.ConverterRegistry;
4056
import org.springframework.core.io.support.SpringFactoriesLoader;
4157
import org.springframework.lang.NonNull;
@@ -488,4 +504,232 @@ public Object convert(@Nullable Object source, TypeDescriptor sourceDescriptor,
488504
}
489505
}
490506
}
507+
508+
static class EclipseCollections implements CustomCollectionRegistrar {
509+
510+
/*
511+
* (non-Javadoc)
512+
* @see org.springframework.data.util.CustomCollectionRegistrar#isAvailable()
513+
*/
514+
@Override
515+
public boolean isAvailable() {
516+
return ClassUtils.isPresent("org.eclipse.collections.api.list.ImmutableList",
517+
EclipseCollections.class.getClassLoader());
518+
}
519+
520+
/*
521+
* (non-Javadoc)
522+
* @see org.springframework.data.util.CustomCollectionRegistrar#getCollectionTypes()
523+
*/
524+
@Override
525+
public Collection<Class<?>> getCollectionTypes() {
526+
return Arrays.asList(ImmutableList.class, ImmutableSet.class, ImmutableBag.class, //
527+
MutableList.class, MutableSet.class, MutableBag.class);
528+
}
529+
530+
/*
531+
* (non-Javadoc)
532+
* @see org.springframework.data.util.CustomCollectionRegistrar#getMapTypes()
533+
*/
534+
@Override
535+
public Collection<Class<?>> getMapTypes() {
536+
return Arrays.asList(ImmutableMap.class, MutableMap.class);
537+
}
538+
539+
/*
540+
* (non-Javadoc)
541+
* @see org.springframework.data.util.CustomCollectionRegistrar#getAllowedPaginationReturnTypes()
542+
*/
543+
@Override
544+
public Collection<Class<?>> getAllowedPaginationReturnTypes() {
545+
return Arrays.asList(ImmutableList.class, MutableList.class);
546+
}
547+
548+
/*
549+
* (non-Javadoc)
550+
* @see org.springframework.data.util.CustomCollectionRegistrar#toJavaNativeCollection()
551+
*/
552+
@Override
553+
public Function<Object, Object> toJavaNativeCollection() {
554+
555+
return source -> source instanceof RichIterable
556+
? EclipseToJavaConverter.INSTANCE.convert(source)
557+
: source;
558+
}
559+
560+
/*
561+
* (non-Javadoc)
562+
* @see org.springframework.data.util.CustomCollectionRegistrar#registerConvertersIn(org.springframework.core.convert.converter.ConverterRegistry)
563+
*/
564+
@Override
565+
public void registerConvertersIn(ConverterRegistry registry) {
566+
567+
registry.addConverter(EclipseToJavaConverter.INSTANCE);
568+
registry.addConverter(JavaToEclipseConverter.INSTANCE);
569+
}
570+
571+
enum EclipseToJavaConverter implements Converter<Object, Object>, ConditionalConverter {
572+
573+
INSTANCE;
574+
575+
private static final TypeDescriptor RICH_ITERABLE_DESCRIPTOR = TypeDescriptor.valueOf(RichIterable.class);
576+
577+
/*
578+
* (non-Javadoc)
579+
* @see org.springframework.core.convert.converter.ConditionalConverter#matches(org.springframework.core.convert.TypeDescriptor, org.springframework.core.convert.TypeDescriptor)
580+
*/
581+
@Override
582+
public boolean matches(TypeDescriptor sourceType, TypeDescriptor targetType) {
583+
584+
return sourceType.isAssignableTo(RICH_ITERABLE_DESCRIPTOR)
585+
&& COLLECTIONS_AND_MAP.contains(targetType.getType());
586+
}
587+
588+
/*
589+
* (non-Javadoc)
590+
* @see org.springframework.core.convert.converter.Converter#convert(java.lang.Object)
591+
*/
592+
@Nullable
593+
@Override
594+
public Object convert(@Nullable Object source) {
595+
596+
if (source instanceof ImmutableList) {
597+
return ((ImmutableList<?>) source).toList();
598+
}
599+
600+
if (source instanceof ImmutableBag) {
601+
return ((ImmutableBag<?>) source).toList();
602+
}
603+
604+
if (source instanceof ImmutableSet) {
605+
return ((ImmutableSet<?>) source).toSet();
606+
}
607+
608+
if (source instanceof ImmutableMap) {
609+
return ((ImmutableMap<?, ?>) source).toMap();
610+
}
611+
612+
return source;
613+
}
614+
}
615+
616+
enum JavaToEclipseConverter implements ConditionalGenericConverter {
617+
618+
INSTANCE;
619+
620+
private static final Set<ConvertiblePair> CONVERTIBLE_PAIRS;
621+
622+
static {
623+
624+
Set<ConvertiblePair> pairs = new HashSet<>();
625+
pairs.add(new ConvertiblePair(Collection.class, RichIterable.class));
626+
627+
pairs.add(new ConvertiblePair(Set.class, MutableSet.class));
628+
pairs.add(new ConvertiblePair(Set.class, MutableList.class));
629+
pairs.add(new ConvertiblePair(Set.class, ImmutableSet.class));
630+
pairs.add(new ConvertiblePair(Set.class, ImmutableList.class));
631+
632+
pairs.add(new ConvertiblePair(List.class, MutableList.class));
633+
pairs.add(new ConvertiblePair(List.class, ImmutableList.class));
634+
635+
pairs.add(new ConvertiblePair(Map.class, RichIterable.class));
636+
pairs.add(new ConvertiblePair(Map.class, MutableMap.class));
637+
pairs.add(new ConvertiblePair(Map.class, ImmutableMap.class));
638+
639+
CONVERTIBLE_PAIRS = Collections.unmodifiableSet(pairs);
640+
}
641+
642+
/*
643+
* (non-Javadoc)
644+
* @see org.springframework.core.convert.converter.GenericConverter#getConvertibleTypes()
645+
*/
646+
@NonNull
647+
@Override
648+
public Set<ConvertiblePair> getConvertibleTypes() {
649+
return CONVERTIBLE_PAIRS;
650+
}
651+
652+
/*
653+
* (non-Javadoc)
654+
* @see org.springframework.core.convert.converter.ConditionalConverter#matches(org.springframework.core.convert.TypeDescriptor, org.springframework.core.convert.TypeDescriptor)
655+
*/
656+
@Override
657+
public boolean matches(TypeDescriptor sourceType, TypeDescriptor targetType) {
658+
659+
// Prevent collections to be mapped to maps
660+
if (sourceType.isCollection() && MapIterable.class.isAssignableFrom(targetType.getType())) {
661+
return false;
662+
}
663+
664+
// Prevent maps to be mapped to collections
665+
if (sourceType.isMap() //
666+
&& !(MapIterable.class.isAssignableFrom(targetType.getType())
667+
|| targetType.getType().equals(RichIterable.class))) {
668+
return false;
669+
}
670+
671+
return true;
672+
}
673+
674+
/*
675+
* (non-Javadoc)
676+
* @see org.springframework.core.convert.converter.GenericConverter#convert(java.lang.Object, org.springframework.core.convert.TypeDescriptor, org.springframework.core.convert.TypeDescriptor)
677+
*/
678+
@Nullable
679+
@Override
680+
public Object convert(@Nullable Object source, TypeDescriptor sourceDescriptor, TypeDescriptor targetDescriptor) {
681+
682+
Class<?> targetType = targetDescriptor.getType();
683+
684+
if (ImmutableList.class.isAssignableFrom(targetType)) {
685+
return Lists.immutable.ofAll((Iterable<?>) source);
686+
}
687+
688+
if (ImmutableSet.class.isAssignableFrom(targetType)) {
689+
return Sets.immutable.ofAll((Iterable<?>) source);
690+
}
691+
692+
if (ImmutableBag.class.isAssignableFrom(targetType)) {
693+
return Bags.immutable.ofAll((Iterable<?>) source);
694+
}
695+
696+
if (ImmutableMap.class.isAssignableFrom(targetType)) {
697+
return Maps.immutable.ofAll((Map<?, ?>) source);
698+
}
699+
700+
if (MutableList.class.isAssignableFrom(targetType)) {
701+
return Lists.mutable.ofAll((Iterable<?>) source);
702+
}
703+
704+
if (MutableSet.class.isAssignableFrom(targetType)) {
705+
return Sets.mutable.ofAll((Iterable<?>) source);
706+
}
707+
708+
if (MutableBag.class.isAssignableFrom(targetType)) {
709+
return Bags.mutable.ofAll((Iterable<?>) source);
710+
}
711+
712+
if (MutableMap.class.isAssignableFrom(targetType)) {
713+
return Maps.mutable.ofMap((Map<?, ?>) source);
714+
}
715+
716+
// No dedicated type asked for, probably RichIterable.
717+
// Try to stay as close to the source value.
718+
719+
if (source instanceof List) {
720+
return Lists.mutable.ofAll((Iterable<?>) source);
721+
}
722+
723+
if (source instanceof Set) {
724+
return Sets.mutable.ofAll((Iterable<?>) source);
725+
}
726+
727+
if (source instanceof Map) {
728+
return Maps.mutable.ofMap((Map<?, ?>) source);
729+
}
730+
731+
return source;
732+
}
733+
}
734+
}
491735
}
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,3 @@
11
org.springframework.data.web.config.SpringDataJacksonModules=org.springframework.data.web.config.SpringDataJacksonConfiguration
2-
org.springframework.data.util.CustomCollectionRegistrar=org.springframework.data.util.CustomCollections.VavrCollections
2+
org.springframework.data.util.CustomCollectionRegistrar=org.springframework.data.util.CustomCollections.VavrCollections, \
3+
org.springframework.data.util.CustomCollections.EclipseCollections

src/test/java/org/springframework/data/repository/query/QueryMethodUnitTests.java

+13-2
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919

2020
import io.vavr.collection.Seq;
2121
import io.vavr.control.Option;
22+
import reactor.core.publisher.Mono;
2223

2324
import java.io.Serializable;
2425
import java.lang.reflect.Method;
@@ -27,9 +28,8 @@
2728
import java.util.concurrent.Future;
2829
import java.util.stream.Stream;
2930

31+
import org.eclipse.collections.api.list.ImmutableList;
3032
import org.junit.jupiter.api.Test;
31-
import reactor.core.publisher.Mono;
32-
3333
import org.springframework.data.domain.Page;
3434
import org.springframework.data.domain.Pageable;
3535
import org.springframework.data.domain.Slice;
@@ -249,6 +249,15 @@ void detectsReactiveSliceQuery() throws Exception {
249249
assertThat(returnedType.getDomainType()).isEqualTo(User.class);
250250
}
251251

252+
@Test // #1817
253+
void considersEclipseCollectionCollectionQuery() throws Exception {
254+
255+
Method method = SampleRepository.class.getMethod("returnsEclipseCollection");
256+
QueryMethod queryMethod = new QueryMethod(method, metadata, factory);
257+
258+
assertThat(queryMethod.isCollectionQuery()).isTrue();
259+
}
260+
252261
interface SampleRepository extends Repository<User, Serializable> {
253262

254263
String pagingMethodWithInvalidReturnType(Pageable pageable);
@@ -295,6 +304,8 @@ interface SampleRepository extends Repository<User, Serializable> {
295304
Future<Option<User>> returnsFutureOfOption();
296305

297306
Mono<Slice<User>> reactiveSlice();
307+
308+
ImmutableList<User> returnsEclipseCollection();
298309
}
299310

300311
class User {

0 commit comments

Comments
 (0)