Skip to content

Make use of ResolvableType in TypeDiscoverer #2572

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 2 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@

<groupId>org.springframework.data</groupId>
<artifactId>spring-data-commons</artifactId>
<version>3.0.0-SNAPSHOT</version>
<version>3.0.0-GH-2312-SNAPSHOT</version>

<name>Spring Data Core</name>
<description>Core Spring concepts underpinning every Spring Data module.</description>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
import java.util.Collections;
import java.util.List;
import java.util.Optional;
import java.util.stream.Stream;

import org.springframework.core.MethodParameter;
import org.springframework.core.ResolvableType;
Expand All @@ -34,6 +35,7 @@
import org.springframework.data.util.ClassTypeInformation;
import org.springframework.data.util.Lazy;
import org.springframework.data.util.TypeInformation;
import org.springframework.data.util.TypeDiscoverer;
import org.springframework.util.Assert;

/**
Expand Down Expand Up @@ -210,24 +212,17 @@ boolean isSort() {
*/
private static boolean isDynamicProjectionParameter(MethodParameter parameter) {

Method method = parameter.getMethod();

if (method == null) {
throw new IllegalStateException(String.format("Method parameter %s is not backed by a method!", parameter));
}

ClassTypeInformation<?> ownerType = ClassTypeInformation.from(parameter.getDeclaringClass());
TypeInformation<?> parameterTypes = ownerType.getParameterTypes(method).get(parameter.getParameterIndex());

if (!parameterTypes.getType().equals(Class.class)) {
if (!parameter.getParameterType().equals(Class.class)) {
return false;
}

TypeInformation<?> bound = parameterTypes.getTypeArguments().get(0);
TypeInformation<Object> returnType = ClassTypeInformation.fromReturnTypeOf(method);
ResolvableType returnType = ResolvableType.forMethodReturnType(parameter.getMethod());
if(new TypeDiscoverer(returnType).isCollectionLike() || org.springframework.util.ClassUtils.isAssignable(Stream.class, returnType.toClass())) {
returnType = returnType.getGeneric(0);
}

return bound
.equals(QueryExecutionConverters.unwrapWrapperTypes(ReactiveWrapperConverters.unwrapWrapperTypes(returnType)));
ResolvableType type = ResolvableType.forMethodParameter(parameter);
return returnType.getType().equals(type.getGeneric(0).getType());
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -299,6 +299,7 @@ private static void assertReturnTypeAssignable(Method method, Set<Class<?>> type
Assert.notNull(method, "Method must not be null!");
Assert.notEmpty(types, "Types must not be null or empty!");

// TODO: to resolve generics fully we'd need the actual repository interface here
TypeInformation<?> returnType = ClassTypeInformation.fromReturnTypeOf(method);

returnType = QueryExecutionConverters.isSingleValue(returnType.getType()) //
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -191,6 +191,7 @@ public static void unwrapReflectionException(Exception ex) throws Throwable {
throw ex;
}

// TODO: we should also consider having the owning type here so we can resolve generics better.
private static TypeInformation<?> getEffectivelyReturnedTypeFrom(Method method) {

TypeInformation<?> returnType = ClassTypeInformation.fromReturnTypeOf(method);
Expand Down
109 changes: 30 additions & 79 deletions src/main/java/org/springframework/data/util/ClassTypeInformation.java
Original file line number Diff line number Diff line change
Expand Up @@ -16,19 +16,15 @@
package org.springframework.data.util;

import java.lang.reflect.Method;
import java.lang.reflect.Type;
import java.lang.reflect.TypeVariable;
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.Set;

import org.springframework.core.GenericTypeResolver;
import org.springframework.core.convert.TypeDescriptor;
import org.springframework.core.ResolvableType;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
import org.springframework.util.ConcurrentReferenceHashMap;
import org.springframework.util.ConcurrentReferenceHashMap.ReferenceType;
Expand All @@ -39,7 +35,6 @@
* @author Oliver Gierke
* @author Christoph Strobl
*/
@SuppressWarnings({ "unchecked", "rawtypes" })
public class ClassTypeInformation<S> extends TypeDiscoverer<S> {

public static final ClassTypeInformation<Collection> COLLECTION = new ClassTypeInformation(Collection.class);
Expand All @@ -51,95 +46,51 @@ public class ClassTypeInformation<S> extends TypeDiscoverer<S> {
private static final Map<Class<?>, ClassTypeInformation<?>> cache = new ConcurrentReferenceHashMap<>(64,
ReferenceType.WEAK);

static {
Arrays.asList(COLLECTION, LIST, SET, MAP, OBJECT).forEach(it -> cache.put(it.getType(), it));
}

private final Class<S> type;
private final Lazy<TypeDescriptor> descriptor;

/**
* Simple factory method to easily create new instances of {@link ClassTypeInformation}.
*
* @param <S>
* @param type must not be {@literal null}.
* Warning: Does not fully resolve generic arguments.
* @param method
* @return
* @deprecated since 3.0 Use {@link #fromReturnTypeOf(Method, Class)} instead.
*/
public static <S> ClassTypeInformation<S> from(Class<S> type) {

Assert.notNull(type, "Type must not be null!");

return (ClassTypeInformation<S>) cache.computeIfAbsent(type, ClassTypeInformation::new);
@Deprecated
public static TypeInformation<?> fromReturnTypeOf(Method method) {
return new TypeDiscoverer<>(ResolvableType.forMethodReturnType(method));
}

/**
* Creates a {@link TypeInformation} from the given method's return type.
*
* @param method must not be {@literal null}.
* @param method
* @param actualType can be {@literal null}.
* @return
*/
public static <S> TypeInformation<S> fromReturnTypeOf(Method method) {
public static TypeInformation<?> fromReturnTypeOf(Method method, @Nullable Class<?> actualType) {

Assert.notNull(method, "Method must not be null!");
return (TypeInformation<S>) ClassTypeInformation.from(method.getDeclaringClass())
.createInfo(method.getGenericReturnType());
if(actualType == null) {
return new TypeDiscoverer<>(ResolvableType.forMethodReturnType(method));
}
return new TypeDiscoverer<>(ResolvableType.forMethodReturnType(method, actualType));
}

/**
* Creates {@link ClassTypeInformation} for the given type.
*
* @param type
*/
ClassTypeInformation(Class<S> type) {

super(type, getTypeVariableMap(type));
Class<?> type;

this.type = type;
this.descriptor = Lazy.of(() -> TypeDescriptor.valueOf(type));
}

/**
* Little helper to allow us to create a generified map, actually just to satisfy the compiler.
*
* @param type must not be {@literal null}.
* @return
*/
private static Map<TypeVariable<?>, Type> getTypeVariableMap(Class<?> type) {
return getTypeVariableMap(type, new HashSet<>());
static {
Arrays.asList(COLLECTION, LIST, SET, MAP, OBJECT).forEach(it -> cache.put(it.getType(), it));
}

private static Map<TypeVariable<?>, Type> getTypeVariableMap(Class<?> type, Collection<Type> visited) {

if (visited.contains(type)) {
return Collections.emptyMap();
} else {
visited.add(type);
}

Map<TypeVariable, Type> source = GenericTypeResolver.getTypeVariableMap(type);
Map<TypeVariable<?>, Type> map = new HashMap<>(source.size());

for (Map.Entry<TypeVariable, Type> entry : source.entrySet()) {

Type value = entry.getValue();
map.put(entry.getKey(), entry.getValue());
public static <S> ClassTypeInformation<S> from(Class<S> type) {

if (value instanceof Class) {
Assert.notNull(type, "Type must not be null!");

for (Map.Entry<TypeVariable<?>, Type> nestedEntry : getTypeVariableMap((Class<?>) value, visited).entrySet()) {
if (!map.containsKey(nestedEntry.getKey())) {
map.put(nestedEntry.getKey(), nestedEntry.getValue());
}
}
}
}
return (ClassTypeInformation<S>) cache.computeIfAbsent(type, ClassTypeInformation::new);
}

return map;
ClassTypeInformation(Class<S> type) {
super(ResolvableType.forClass(type));
this.type = type;
}

@Override
public Class<S> getType() {
return type;
return (Class<S>) type;
}

@Override
Expand All @@ -158,12 +109,12 @@ public TypeInformation<? extends S> specialize(ClassTypeInformation<?> type) {
}

@Override
public TypeDescriptor toTypeDescriptor() {
return descriptor.get();
public String toString() {
return type.getName();
}

@Override
public String toString() {
return type.getName();
public boolean equals(Object o) {
return super.equals(o);
}
}

This file was deleted.

Loading