DATACMNS-867 - Java 8 polish of ReflectiveRepositoryInformation.

Introduced Optionals.firstNonEmpty(…) to consume a set of Suppliers until one returns a non-empty Optional.
This commit is contained in:
Oliver Gierke
2016-11-16 18:16:44 +01:00
parent d7340e391c
commit bdd55fc7ab
3 changed files with 78 additions and 124 deletions

View File

@@ -17,14 +17,14 @@ package org.springframework.data.repository.core.support;
import static org.springframework.core.GenericTypeResolver.*; import static org.springframework.core.GenericTypeResolver.*;
import lombok.AccessLevel;
import lombok.RequiredArgsConstructor;
import java.lang.reflect.Method; import java.lang.reflect.Method;
import java.lang.reflect.Type; import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.List;
import java.util.Optional; import java.util.Optional;
import java.util.function.BiPredicate; import java.util.function.BiPredicate;
import java.util.function.Supplier;
import java.util.stream.IntStream;
import org.springframework.core.MethodParameter; import org.springframework.core.MethodParameter;
import org.springframework.core.convert.ConversionService; import org.springframework.core.convert.ConversionService;
@@ -32,7 +32,8 @@ import org.springframework.data.repository.core.RepositoryInformation;
import org.springframework.data.repository.core.RepositoryMetadata; import org.springframework.data.repository.core.RepositoryMetadata;
import org.springframework.data.repository.util.QueryExecutionConverters; import org.springframework.data.repository.util.QueryExecutionConverters;
import org.springframework.data.repository.util.ReactiveWrapperConverters; import org.springframework.data.repository.util.ReactiveWrapperConverters;
import org.springframework.data.repository.util.ReactiveWrappers; import org.springframework.data.util.Optionals;
import org.springframework.data.util.Streamable;
import org.springframework.util.Assert; import org.springframework.util.Assert;
/** /**
@@ -64,33 +65,25 @@ public class ReactiveRepositoryInformation extends DefaultRepositoryInformation
* generics into account. * generics into account.
* *
* @param method must not be {@literal null}. * @param method must not be {@literal null}.
* @param baseClass can be {@literal null}. * @param baseClass must not be {@literal null}.
* @return * @return
*/ */
@Override @Override
Method getTargetClassMethod(Method method, Optional<Class<?>> baseClass) { Method getTargetClassMethod(Method method, Optional<Class<?>> baseClass) {
return baseClass.map(it -> { return baseClass.flatMap(it -> {
List<Supplier<Optional<Method>>> suppliers = new ArrayList<>();
if (usesParametersWithReactiveWrappers(method)) { if (usesParametersWithReactiveWrappers(method)) {
suppliers.add(() -> getMethodCandidate(method, it, assignableWrapperMatch(method.getParameterTypes()))); //
Method candidate = getMethodCandidate(method, it, new AssignableWrapperMatch(method.getParameterTypes())); suppliers.add(() -> getMethodCandidate(method, it, wrapperConversionMatch(method.getParameterTypes())));
if (candidate != null) {
return candidate;
}
candidate = getMethodCandidate(method, it, WrapperConversionMatch.of(method.getParameterTypes()));
if (candidate != null) {
return candidate;
}
} }
Method candidate = getMethodCandidate(method, it, suppliers
MatchParameterOrComponentType.of(method, getRepositoryInterface())); .add(() -> getMethodCandidate(method, it, matchParameterOrComponentType(method, getRepositoryInterface())));
return candidate != null ? candidate : method; return Optionals.firstNonEmpty(Streamable.of(suppliers));
}).orElse(method); }).orElse(method);
} }
@@ -132,30 +125,14 @@ public class ReactiveRepositoryInformation extends DefaultRepositoryInformation
* @param predicate must not be {@literal null}. * @param predicate must not be {@literal null}.
* @return * @return
*/ */
private static Method getMethodCandidate(Method method, Class<?> baseClass, private static Optional<Method> getMethodCandidate(Method method, Class<?> baseClass,
BiPredicate<Class<?>, Integer> predicate) { BiPredicate<Class<?>, Integer> predicate) {
for (Method baseClassMethod : baseClass.getMethods()) { return Arrays.stream(baseClass.getMethods())//
.filter(it -> method.getName().equals(it.getName()))//
// Wrong name .filter(it -> method.getParameterTypes().length == it.getParameterTypes().length)//
if (!method.getName().equals(baseClassMethod.getName())) { .filter(it -> parametersMatch(it, predicate))//
continue; .findFirst();
}
// Wrong number of arguments
if (!(method.getParameterTypes().length == baseClassMethod.getParameterTypes().length)) {
continue;
}
// Check whether all parameters match
if (!parametersMatch(method, baseClassMethod, predicate)) {
continue;
}
return baseClassMethod;
}
return null;
} }
/** /**
@@ -167,109 +144,60 @@ public class ReactiveRepositoryInformation extends DefaultRepositoryInformation
* @param predicate must not be {@literal null}. * @param predicate must not be {@literal null}.
* @return * @return
*/ */
private static boolean parametersMatch(Method method, Method baseClassMethod, private static boolean parametersMatch(Method baseClassMethod, BiPredicate<Class<?>, Integer> predicate) {
BiPredicate<Class<?>, Integer> predicate) {
Type[] genericTypes = baseClassMethod.getGenericParameterTypes();
Class<?>[] types = baseClassMethod.getParameterTypes(); Class<?>[] types = baseClassMethod.getParameterTypes();
for (int i = 0; i < genericTypes.length; i++) { return IntStream.range(0, types.length).allMatch(it -> predicate.test(types[it], it));
if (!predicate.test(types[i], i)) {
return false;
}
}
return true;
} }
/** /**
* {@link BiPredicate} to check whether a method parameter is a {@link #isNonUnwrappingWrapper(Class)} and can be * {@link BiPredicate} to check whether a method parameter is a {@link #isNonUnwrappingWrapper(Class)} and can be
* converted into a different wrapper. Usually {@link rx.Observable} to {@link org.reactivestreams.Publisher} * converted into a different wrapper. Usually {@link rx.Observable} to {@link org.reactivestreams.Publisher}
* conversion. * conversion.
*
* @param declaredParameterTypes
* @return
*/ */
@RequiredArgsConstructor(staticName = "of") private static BiPredicate<Class<?>, Integer> wrapperConversionMatch(Class<?>[] declaredParameterTypes) {
static class WrapperConversionMatch implements BiPredicate<Class<?>, Integer> {
private final Class<?>[] declaredParameterTypes; return (type, index) -> isNonUnwrappingWrapper(type) //
&& isNonUnwrappingWrapper(declaredParameterTypes[index]) //
/* && ReactiveWrapperConverters.canConvert(declaredParameterTypes[index], type);
* (non-Javadoc)
* @see java.util.function.BiPredicate#test(java.lang.Object, java.lang.Object)
*/
@Override
public boolean test(Class<?> candidateParameterType, Integer index) {
if (!isNonUnwrappingWrapper(candidateParameterType)) {
return false;
}
if (!isNonUnwrappingWrapper(declaredParameterTypes[index])) {
return false;
}
return ReactiveWrappers.isAvailable()
&& ReactiveWrapperConverters.canConvert(declaredParameterTypes[index], candidateParameterType);
}
} }
/** /**
* {@link BiPredicate} to check parameter assignability between a {@link #isNonUnwrappingWrapper(Class)} parameter and * {@link BiPredicate} to check parameter assignability between a {@link #isNonUnwrappingWrapper(Class)} parameter and
* a declared parameter. Usually {@link reactor.core.publisher.Flux} vs. {@link org.reactivestreams.Publisher} * a declared parameter. Usually {@link reactor.core.publisher.Flux} vs. {@link org.reactivestreams.Publisher}
* conversion. * conversion.
*
* @param declaredParameterTypes
* @return
*/ */
@RequiredArgsConstructor(staticName = "of") private static BiPredicate<Class<?>, Integer> assignableWrapperMatch(Class<?>[] declaredParameterTypes) {
static class AssignableWrapperMatch implements BiPredicate<Class<?>, Integer> {
private final Class<?>[] declaredParameterTypes; return (type, index) -> isNonUnwrappingWrapper(type) //
&& isNonUnwrappingWrapper(declaredParameterTypes[index]) //
/* && declaredParameterTypes[index].isAssignableFrom(type);
* (non-Javadoc)
* @see java.util.function.BiPredicate#test(java.lang.Object, java.lang.Object)
*/
@Override
public boolean test(Class<?> candidateParameterType, Integer index) {
if (!isNonUnwrappingWrapper(candidateParameterType)) {
return false;
}
if (!isNonUnwrappingWrapper(declaredParameterTypes[index])) {
return false;
}
return declaredParameterTypes[index].isAssignableFrom(candidateParameterType);
}
} }
/** /**
* {@link BiPredicate} to check parameter assignability between a parameters in which the declared parameter may be * {@link BiPredicate} to check parameter assignability between a parameters in which the declared parameter may be
* wrapped but supports unwrapping. Usually types like {@link java.util.Optional} or {@link java.util.stream.Stream}. * wrapped but supports unwrapping. Usually types like {@link java.util.Optional} or {@link java.util.stream.Stream}.
* *
* @param method
* @param repositoryInterface
* @return
* @see QueryExecutionConverters * @see QueryExecutionConverters
*/ */
@RequiredArgsConstructor(access = AccessLevel.PRIVATE) private static BiPredicate<Class<?>, Integer> matchParameterOrComponentType(Method method,
static class MatchParameterOrComponentType implements BiPredicate<Class<?>, Integer> { Class<?> repositoryInterface) {
private final Method declaredMethod; return (type, index) -> {
private final Class<?>[] declaredParameterTypes;
private final Class<?> repositoryInterface;
public static MatchParameterOrComponentType of(Method declaredMethod, Class<?> repositoryInterface) { Class<?> parameterType = resolveParameterType(new MethodParameter(method, index), repositoryInterface);
return new MatchParameterOrComponentType(declaredMethod, declaredMethod.getParameterTypes(), repositoryInterface);
}
/* return type.isAssignableFrom(parameterType) && type.equals(method.getParameterTypes()[index]);
* (non-Javadoc) };
* @see java.util.function.BiPredicate#test(java.lang.Object, java.lang.Object)
*/
@Override
public boolean test(Class<?> candidateParameterType, Integer index) {
MethodParameter parameter = new MethodParameter(declaredMethod, index);
Class<?> parameterType = resolveParameterType(parameter, repositoryInterface);
return candidateParameterType.isAssignableFrom(parameterType)
&& candidateParameterType.equals(declaredParameterTypes[index]);
}
} }
} }

View File

@@ -549,12 +549,7 @@ public abstract class RepositoryFactorySupport implements BeanClassLoaderAware,
* @return * @return
*/ */
private boolean isCustomMethodInvocation(MethodInvocation invocation) { private boolean isCustomMethodInvocation(MethodInvocation invocation) {
return customImplementation.map(it -> repositoryInformation.isCustomMethod(invocation.getMethod())).orElse(false);
if (null == customImplementation) {
return false;
}
return repositoryInformation.isCustomMethod(invocation.getMethod());
} }
} }

View File

@@ -21,6 +21,7 @@ import java.util.Arrays;
import java.util.Iterator; import java.util.Iterator;
import java.util.Optional; import java.util.Optional;
import java.util.function.Function; import java.util.function.Function;
import java.util.function.Supplier;
import java.util.stream.Stream; import java.util.stream.Stream;
import org.springframework.util.Assert; import org.springframework.util.Assert;
@@ -96,6 +97,36 @@ public class Optionals {
.findFirst().orElse(defaultValue); .findFirst().orElse(defaultValue);
} }
/**
* Invokes the given {@link Supplier}s for {@link Optional} results one by one and returns the first non-empty one.
*
* @param suppliers must not be {@literal null}.
* @return
*/
@SafeVarargs
public static <T> Optional<T> firstNonEmpty(Supplier<Optional<T>>... suppliers) {
Assert.notNull(suppliers, "Suppliers must not be null!");
return firstNonEmpty(Streamable.of(suppliers));
}
/**
* Invokes the given {@link Supplier}s for {@link Optional} results one by one and returns the first non-empty one.
*
* @param suppliers must not be {@literal null}.
* @return
*/
public static <T> Optional<T> firstNonEmpty(Iterable<Supplier<Optional<T>>> suppliers) {
Assert.notNull(suppliers, "Suppliers must not be null!");
return Streamable.of(suppliers).stream()//
.map(it -> it.get())//
.filter(it -> it.isPresent())//
.findFirst().orElse(Optional.empty());
}
/** /**
* Returns the next element of the given {@link Iterator} or {@link Optional#empty()} in case there is no next * Returns the next element of the given {@link Iterator} or {@link Optional#empty()} in case there is no next
* element. * element.