DATACMNS-940 - Support for Javaslang collections as repository return types.

Javaslang's collection and map types can now be used on repository query methods and get adapted similarly to nullable wrapper types (like JDK's Optional). Also made TypeInformation infrastructure aware of the map type so that a value type lookup gets handle correctly.
This commit is contained in:
Oliver Gierke
2016-12-12 20:07:37 +01:00
parent c52aba358d
commit a9c3b9f66f
5 changed files with 335 additions and 9 deletions

View File

@@ -17,7 +17,11 @@ package org.springframework.data.repository.util;
import static org.hamcrest.CoreMatchers.*;
import static org.junit.Assert.*;
import static org.springframework.data.repository.util.QueryExecutionConverters.*;
import javaslang.collection.HashMap;
import javaslang.collection.HashSet;
import javaslang.collection.Traversable;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import rx.Completable;
@@ -26,6 +30,11 @@ import rx.Single;
import scala.Option;
import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.Future;
@@ -136,7 +145,7 @@ public class QueryExecutionConvertersUnitTests {
public void turnsNullIntoGuavaOptional() {
Optional<Object> optional = conversionService.convert(new NullableWrapper(null), Optional.class);
assertThat(optional, is(Optional.<Object>absent()));
assertThat(optional, is(Optional.<Object> absent()));
}
/**
@@ -148,7 +157,7 @@ public class QueryExecutionConvertersUnitTests {
java.util.Optional<Object> optional = conversionService.convert(new NullableWrapper(null),
java.util.Optional.class);
assertThat(optional, is(java.util.Optional.<Object>empty()));
assertThat(optional, is(java.util.Optional.<Object> empty()));
}
/**
@@ -205,7 +214,7 @@ public class QueryExecutionConvertersUnitTests {
public void turnsNullIntoScalaOptionEmpty() {
assertThat((Option<Object>) conversionService.convert(new NullableWrapper(null), Option.class),
is(Option.<Object>empty()));
is(Option.<Object> empty()));
}
/**
@@ -262,6 +271,71 @@ public class QueryExecutionConvertersUnitTests {
assertThat(QueryExecutionConverters.unwrap(option("string")), is((Object) "string"));
}
/**
* @see DATACMNS-940
*/
@Test
public void conversListToJavaslang() {
assertThat(conversionService.canConvert(List.class, javaslang.collection.Traversable.class), is(true));
assertThat(conversionService.canConvert(List.class, javaslang.collection.List.class), is(true));
assertThat(conversionService.canConvert(List.class, javaslang.collection.Set.class), is(true));
assertThat(conversionService.canConvert(List.class, javaslang.collection.Map.class), is(false));
List<Integer> integers = Arrays.asList(1, 2, 3);
Traversable<?> result = conversionService.convert(integers, Traversable.class);
assertThat(result, is(instanceOf(javaslang.collection.List.class)));
}
/**
* @see DATACMNS-940
*/
@Test
public void convertsSetToJavaslang() {
assertThat(conversionService.canConvert(Set.class, javaslang.collection.Traversable.class), is(true));
assertThat(conversionService.canConvert(Set.class, javaslang.collection.Set.class), is(true));
assertThat(conversionService.canConvert(Set.class, javaslang.collection.List.class), is(true));
assertThat(conversionService.canConvert(Set.class, javaslang.collection.Map.class), is(false));
Set<Integer> integers = Collections.singleton(1);
Traversable<?> result = conversionService.convert(integers, Traversable.class);
assertThat(result, is(instanceOf(javaslang.collection.Set.class)));
}
/**
* @see DATACMNS-940
*/
@Test
public void convertsMapToJavaslang() {
assertThat(conversionService.canConvert(Map.class, javaslang.collection.Traversable.class), is(true));
assertThat(conversionService.canConvert(Map.class, javaslang.collection.Map.class), is(true));
assertThat(conversionService.canConvert(Map.class, javaslang.collection.Set.class), is(false));
assertThat(conversionService.canConvert(Map.class, javaslang.collection.List.class), is(false));
Map<String, String> map = Collections.singletonMap("key", "value");
Traversable<?> result = conversionService.convert(map, Traversable.class);
assertThat(result, is(instanceOf(javaslang.collection.Map.class)));
}
/**
* @see DATACMNS-940
*/
@Test
public void unwrapsJavaslangCollectionsToJavaOnes() {
assertThat(unwrap(javaslangList(1, 2, 3)), is(instanceOf(List.class)));
assertThat(unwrap(javaslangSet(1, 2, 3)), is(instanceOf(Set.class)));
assertThat(unwrap(javaslangMap("key", "value")), is(instanceOf(Map.class)));
}
@SuppressWarnings("unchecked")
private static javaslang.control.Option<Object> optionNone() {
@@ -275,4 +349,26 @@ public class QueryExecutionConvertersUnitTests {
Method method = ReflectionUtils.findMethod(javaslang.control.Option.class, "of", Object.class);
return (javaslang.control.Option<T>) ReflectionUtils.invokeMethod(method, null, source);
}
@SuppressWarnings("unchecked")
private static <T> javaslang.collection.List<T> javaslangList(T... values) {
Method method = ReflectionUtils.findMethod(javaslang.collection.List.class, "ofAll", Iterable.class);
return (javaslang.collection.List<T>) ReflectionUtils.invokeMethod(method, null, Arrays.asList(values));
}
@SuppressWarnings("unchecked")
private static <T> javaslang.collection.Set<T> javaslangSet(T... values) {
Method method = ReflectionUtils.findMethod(HashSet.class, "ofAll", Iterable.class);
return (javaslang.collection.Set<T>) ReflectionUtils.invokeMethod(method, null, Arrays.asList(values));
}
@SuppressWarnings("unchecked")
private static <K, V> javaslang.collection.Map<K, V> javaslangMap(K key, V value) {
Method method = ReflectionUtils.findMethod(HashMap.class, "ofAll", Map.class);
return (javaslang.collection.Map<K, V>) ReflectionUtils.invokeMethod(method, null,
Collections.singletonMap(key, value));
}
}

View File

@@ -19,6 +19,8 @@ import static org.hamcrest.Matchers.*;
import static org.junit.Assert.*;
import static org.springframework.data.util.ClassTypeInformation.*;
import javaslang.collection.Traversable;
import java.lang.reflect.Method;
import java.util.Calendar;
import java.util.Collection;
@@ -106,7 +108,7 @@ public class ClassTypeInformationUnitTests {
property = information.getProperty("rawSet");
assertEquals(Set.class, property.getType());
assertThat(property.getComponentType().getType(), is(Matchers.<Class<?>>equalTo(Object.class)));
assertThat(property.getComponentType().getType(), is(Matchers.<Class<?>> equalTo(Object.class)));
assertNull(property.getMapValueType());
}
@@ -413,6 +415,29 @@ public class ClassTypeInformationUnitTests {
assertThat(information.getProperty("field").getType(), is(typeCompatibleWith(Nested.class)));
}
/**
* @see DATACMNS-940
*/
@Test
public void detectsJavaslangTraversableComponentType() {
ClassTypeInformation<SampleTraversable> information = ClassTypeInformation.from(SampleTraversable.class);
assertThat(information.getComponentType().getType(), is(typeCompatibleWith(Integer.class)));
}
/**
* @see DATACMNS-940
*/
@Test
public void detectsJavaslangMapComponentAndValueType() {
ClassTypeInformation<SampleMap> information = ClassTypeInformation.from(SampleMap.class);
assertThat(information.getComponentType().getType(), is(typeCompatibleWith(String.class)));
assertThat(information.getMapValueType().getType(), is(typeCompatibleWith(Integer.class)));
}
static class StringMapContainer extends MapContainer<String> {
}
@@ -611,4 +636,8 @@ public class ClassTypeInformationUnitTests {
static class Nested extends SomeType<String> {}
static class Concrete extends SomeType<Nested> {}
static interface SampleTraversable extends Traversable<Integer> {}
static interface SampleMap extends javaslang.collection.Map<String, Integer> {}
}