Use ResolvableType to back TypeInformation.

Reworked the implementation of the TypeInformation type hierarchy to be based on Spring's ResolvableType for most of the heavy-lifting.

Original pull request: #2572.
Related ticket: #2312.
This commit is contained in:
Christoph Strobl
2021-11-18 09:06:15 +01:00
committed by Oliver Drotbohm
parent 8721ab4170
commit 1d6331ccb6
16 changed files with 530 additions and 1269 deletions

View File

@@ -23,6 +23,7 @@ import java.util.Arrays;
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;
@@ -34,6 +35,7 @@ import org.springframework.data.repository.util.ReactiveWrapperConverters;
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;
/**
@@ -211,24 +213,17 @@ public class Parameter {
*/
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());
}
/**

View File

@@ -300,6 +300,7 @@ public class QueryMethod {
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()) //

View File

@@ -192,6 +192,7 @@ public abstract class ClassUtils {
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);

View File

@@ -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;
@@ -39,7 +35,6 @@ import org.springframework.util.ConcurrentReferenceHashMap.ReferenceType;
* @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);
@@ -51,20 +46,36 @@ public class ClassTypeInformation<S> extends TypeDiscoverer<S> {
private static final Map<Class<?>, ClassTypeInformation<?>> cache = new ConcurrentReferenceHashMap<>(64,
ReferenceType.WEAK);
/**
* Warning: Does not fully resolve generic arguments.
* @param method
* @return
* @deprecated since 3.0 Use {@link #fromReturnTypeOf(Method, Class)} instead.
*/
@Deprecated
public static TypeInformation<?> fromReturnTypeOf(Method method) {
return new TypeDiscoverer<>(ResolvableType.forMethodReturnType(method));
}
/**
* @param method
* @param actualType can be {@literal null}.
* @return
*/
public static TypeInformation<?> fromReturnTypeOf(Method method, @Nullable Class<?> actualType) {
if(actualType == null) {
return new TypeDiscoverer<>(ResolvableType.forMethodReturnType(method));
}
return new TypeDiscoverer<>(ResolvableType.forMethodReturnType(method, actualType));
}
Class<?> type;
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}.
* @return
*/
public static <S> ClassTypeInformation<S> from(Class<S> type) {
Assert.notNull(type, "Type must not be null");
@@ -72,74 +83,14 @@ public class ClassTypeInformation<S> extends TypeDiscoverer<S> {
return (ClassTypeInformation<S>) cache.computeIfAbsent(type, ClassTypeInformation::new);
}
/**
* Creates a {@link TypeInformation} from the given method's return type.
*
* @param method must not be {@literal null}.
* @return
*/
public static <S> TypeInformation<S> fromReturnTypeOf(Method method) {
Assert.notNull(method, "Method must not be null");
return (TypeInformation<S>) ClassTypeInformation.from(method.getDeclaringClass())
.createInfo(method.getGenericReturnType());
}
/**
* Creates {@link ClassTypeInformation} for the given type.
*
* @param type
*/
ClassTypeInformation(Class<S> type) {
super(type, getTypeVariableMap(type));
super(ResolvableType.forClass(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<>());
}
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());
if (value instanceof Class) {
for (Map.Entry<TypeVariable<?>, Type> nestedEntry : getTypeVariableMap((Class<?>) value, visited).entrySet()) {
if (!map.containsKey(nestedEntry.getKey())) {
map.put(nestedEntry.getKey(), nestedEntry.getValue());
}
}
}
}
return map;
}
@Override
public Class<S> getType() {
return type;
return (Class<S>) type;
}
@Override
@@ -157,13 +108,13 @@ public class ClassTypeInformation<S> extends TypeDiscoverer<S> {
return (TypeInformation<? extends S>) type;
}
@Override
public TypeDescriptor toTypeDescriptor() {
return descriptor.get();
}
@Override
public String toString() {
return type.getName();
}
@Override
public boolean equals(Object o) {
return super.equals(o);
}
}

View File

@@ -1,64 +0,0 @@
/*
* Copyright 2011-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.util;
import java.lang.reflect.Array;
import java.lang.reflect.GenericArrayType;
import java.lang.reflect.Type;
import org.springframework.lang.NonNull;
/**
* Special {@link TypeDiscoverer} handling {@link GenericArrayType}s.
*
* @author Oliver Gierke
*/
class GenericArrayTypeInformation<S> extends ParentTypeAwareTypeInformation<S> {
private final GenericArrayType type;
/**
* Creates a new {@link GenericArrayTypeInformation} for the given {@link GenericArrayTypeInformation} and
* {@link TypeDiscoverer}.
*
* @param type must not be {@literal null}.
* @param parent must not be {@literal null}.
*/
protected GenericArrayTypeInformation(GenericArrayType type, TypeDiscoverer<?> parent) {
super(type, parent);
this.type = type;
}
@Override
@SuppressWarnings("unchecked")
public Class<S> getType() {
return (Class<S>) Array.newInstance(resolveType(type.getGenericComponentType()), 0).getClass();
}
@Override
@NonNull
protected TypeInformation<?> doGetComponentType() {
Type componentType = type.getGenericComponentType();
return createInfo(componentType);
}
@Override
public String toString() {
return type.toString();
}
}

View File

@@ -1,272 +0,0 @@
/*
* Copyright 2011-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.util;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.lang.reflect.TypeVariable;
import java.util.ArrayList;
import java.util.Arrays;
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.stream.IntStream;
import org.springframework.lang.Nullable;
import org.springframework.util.StringUtils;
/**
* Base class for all types that include parameterization of some kind. Crucial as we have to take note of the parent
* class we will have to resolve generic parameters against.
*
* @author Oliver Gierke
* @author Mark Paluch
* @author Christoph Strobl
* @author Jürgen Diez
*/
class ParameterizedTypeInformation<T> extends ParentTypeAwareTypeInformation<T> {
private final ParameterizedType type;
private final Lazy<Boolean> resolved;
/**
* Creates a new {@link ParameterizedTypeInformation} for the given {@link Type} and parent {@link TypeDiscoverer}.
*
* @param type must not be {@literal null}
* @param parent must not be {@literal null}
*/
public ParameterizedTypeInformation(ParameterizedType type, TypeDiscoverer<?> parent) {
super(type, parent, calculateTypeVariables(type, parent));
this.type = type;
this.resolved = Lazy.of(() -> isResolvedCompletely());
}
@Override
@Nullable
protected TypeInformation<?> doGetMapValueType() {
if (isMap()) {
Type[] arguments = type.getActualTypeArguments();
if (arguments.length > 1) {
return createInfo(arguments[1]);
}
}
Class<?> rawType = getType();
Set<Type> supertypes = new HashSet<>();
Optional.ofNullable(rawType.getGenericSuperclass()).ifPresent(supertypes::add);
supertypes.addAll(Arrays.asList(rawType.getGenericInterfaces()));
Optional<TypeInformation<?>> result = supertypes.stream()//
.map(it -> Pair.of(it, resolveType(it)))//
.filter(it -> Map.class.isAssignableFrom(it.getSecond()))//
.<TypeInformation<?>> map(it -> {
ParameterizedType parameterizedSupertype = (ParameterizedType) it.getFirst();
Type[] arguments = parameterizedSupertype.getActualTypeArguments();
return createInfo(arguments[1]);
}).findFirst();
return result.orElseGet(super::doGetMapValueType);
}
@Override
public List<TypeInformation<?>> getTypeArguments() {
List<TypeInformation<?>> result = new ArrayList<>();
for (Type argument : type.getActualTypeArguments()) {
result.add(createInfo(argument));
}
return result;
}
@Override
public boolean isAssignableFrom(TypeInformation<?> target) {
if (this.equals(target)) {
return true;
}
Class<T> rawType = getType();
Class<?> rawTargetType = target.getType();
if (!rawType.isAssignableFrom(rawTargetType)) {
return false;
}
TypeInformation<?> otherTypeInformation = rawType.equals(rawTargetType) ? target
: target.getSuperTypeInformation(rawType);
List<TypeInformation<?>> myParameters = getTypeArguments();
List<TypeInformation<?>> typeParameters = otherTypeInformation == null //
? java.util.Collections.emptyList() //
: otherTypeInformation.getTypeArguments();
if (myParameters.size() != typeParameters.size()) {
return false;
}
for (int i = 0; i < myParameters.size(); i++) {
if (!myParameters.get(i).isAssignableFrom(typeParameters.get(i))) {
return false;
}
}
return true;
}
@Override
@Nullable
protected TypeInformation<?> doGetComponentType() {
Class<?> type = getType();
return isMap() && !CustomCollections.isMapBaseType(type)
? getRequiredSuperTypeInformation(CustomCollections.getMapBaseType(type)).getComponentType()
: createInfo(this.type.getActualTypeArguments()[0]);
}
@Override
@SuppressWarnings("unchecked")
public TypeInformation<? extends T> specialize(ClassTypeInformation<?> type) {
if (isResolvedCompletely()) {
return (TypeInformation<? extends T>) type;
}
TypeInformation<?> asSupertype = type.getSuperTypeInformation(getType());
if (asSupertype == null || !ParameterizedTypeInformation.class.isInstance(asSupertype)) {
return super.specialize(type);
}
return ((ParameterizedTypeInformation<?>) asSupertype).isResolvedCompletely() //
? (TypeInformation<? extends T>) type //
: super.specialize(type);
}
@Override
public boolean equals(@Nullable Object obj) {
if (obj == this) {
return true;
}
if (!(obj instanceof ParameterizedTypeInformation<?> that)) {
return false;
}
if (this.isResolved() && that.isResolved()) {
return this.type.equals(that.type);
}
return super.equals(obj);
}
@Override
public int hashCode() {
return isResolved() ? this.type.hashCode() : super.hashCode();
}
@Override
public String toString() {
return String.format("%s<%s>", getType().getName(),
StringUtils.collectionToCommaDelimitedString(getTypeArguments()));
}
private boolean isResolved() {
return resolved.get();
}
private boolean isResolvedCompletely() {
Type[] typeArguments = type.getActualTypeArguments();
if (typeArguments.length == 0) {
return false;
}
for (Type typeArgument : typeArguments) {
TypeInformation<?> info = createInfo(typeArgument);
if (info instanceof ParameterizedTypeInformation) {
if (!((ParameterizedTypeInformation<?>) info).isResolvedCompletely()) {
return false;
}
}
if (!(info instanceof ClassTypeInformation)) {
return false;
}
}
return true;
}
/**
* Resolves the type variables to be used. Uses the parent's type variable map but overwrites variables locally
* declared.
*
* @param type must not be {@literal null}.
* @param parent must not be {@literal null}.
* @return will never be {@literal null}.
*/
private static Map<TypeVariable<?>, Type> calculateTypeVariables(ParameterizedType type, TypeDiscoverer<?> parent) {
Class<?> resolvedType = parent.resolveType(type);
TypeVariable<?>[] typeParameters = resolvedType.getTypeParameters();
Type[] arguments = type.getActualTypeArguments();
Map<TypeVariable<?>, Type> localTypeVariables = new HashMap<>(parent.getTypeVariableMap());
IntStream.range(0, typeParameters.length) //
.mapToObj(it -> Pair.of(typeParameters[it], flattenTypeVariable(arguments[it], localTypeVariables))) //
.forEach(it -> localTypeVariables.put(it.getFirst(), it.getSecond()));
return localTypeVariables;
}
/**
* Recursively resolves the type bound to the given {@link Type} in case it's a {@link TypeVariable} and there's an
* entry in the given type variables.
*
* @param source must not be {@literal null}.
* @param variables must not be {@literal null}.
* @return will never be {@literal null}.
*/
private static Type flattenTypeVariable(Type source, Map<TypeVariable<?>, Type> variables) {
if (!(source instanceof TypeVariable)) {
return source;
}
Type value = variables.get(source);
return value == null ? source : flattenTypeVariable(value, variables);
}
}

View File

@@ -1,103 +0,0 @@
/*
* Copyright 2011-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.util;
import java.lang.reflect.Type;
import java.lang.reflect.TypeVariable;
import java.util.Map;
import org.springframework.core.ResolvableType;
import org.springframework.core.convert.TypeDescriptor;
import org.springframework.lang.Nullable;
/**
* Base class for {@link TypeInformation} implementations that need parent type awareness.
*
* @author Oliver Gierke
*/
public abstract class ParentTypeAwareTypeInformation<S> extends TypeDiscoverer<S> {
private final TypeDiscoverer<?> parent;
private final Lazy<TypeDescriptor> descriptor;
private int hashCode;
/**
* Creates a new {@link ParentTypeAwareTypeInformation}.
*
* @param type must not be {@literal null}.
* @param parent must not be {@literal null}.
*/
protected ParentTypeAwareTypeInformation(Type type, TypeDiscoverer<?> parent) {
this(type, parent, parent.getTypeVariableMap());
}
protected ParentTypeAwareTypeInformation(Type type, TypeDiscoverer<?> parent, Map<TypeVariable<?>, Type> map) {
super(type, map);
this.parent = parent;
this.descriptor = Lazy.of(() -> new TypeDescriptor(toResolvableType(), null, null));
}
@Override
public TypeDescriptor toTypeDescriptor() {
return descriptor.get();
}
@Override
protected TypeInformation<?> createInfo(Type fieldType) {
if (parent.getType().equals(fieldType)) {
return parent;
}
return super.createInfo(fieldType);
}
@Override
protected ResolvableType toResolvableType() {
return ResolvableType.forType(getType(), parent.toResolvableType());
}
@Override
public boolean equals(@Nullable Object obj) {
if (!super.equals(obj)) {
return false;
}
if (obj == null) {
return false;
}
if (!this.getClass().equals(obj.getClass())) {
return false;
}
ParentTypeAwareTypeInformation<?> that = (ParentTypeAwareTypeInformation<?>) obj;
return this.parent == null ? that.parent == null : this.parent.equals(that.parent);
}
@Override
public int hashCode() {
if (this.hashCode == 0) {
this.hashCode = super.hashCode() + (31 * parent.hashCode());
}
return this.hashCode;
}
}

View File

@@ -17,29 +17,26 @@ package org.springframework.data.util;
import java.beans.PropertyDescriptor;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.GenericArrayType;
import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.lang.reflect.TypeVariable;
import java.lang.reflect.WildcardType;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Collection;
import java.util.Collections;
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.stream.Collectors;
import org.springframework.beans.BeanUtils;
import org.springframework.core.GenericTypeResolver;
import org.springframework.core.MethodParameter;
import org.springframework.core.ResolvableType;
import org.springframework.core.convert.TypeDescriptor;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
import org.springframework.util.ClassUtils;
import org.springframework.util.ObjectUtils;
import org.springframework.util.ReflectionUtils;
@@ -53,170 +50,116 @@ import org.springframework.util.ReflectionUtils;
* @author Alessandro Nistico
* @author Johannes Englmeier
*/
class TypeDiscoverer<S> implements TypeInformation<S> {
public class TypeDiscoverer<S> implements TypeInformation<S> {
private final Type type;
private final Map<TypeVariable<?>, Type> typeVariableMap;
private final Map<String, Optional<TypeInformation<?>>> fieldTypes = new ConcurrentHashMap<>();
private final int hashCode;
protected static final Class<?>[] MAP_TYPES;
private static final Class<?>[] COLLECTION_TYPES;
static {
var classLoader = TypeDiscoverer.class.getClassLoader();
Set<Class<?>> mapTypes = new HashSet<>();
mapTypes.add(Map.class);
try {
mapTypes.add(ClassUtils.forName("io.vavr.collection.Map", classLoader));
} catch (ClassNotFoundException o_O) {}
MAP_TYPES = mapTypes.toArray(new Class[0]);
Set<Class<?>> collectionTypes = new HashSet<>();
collectionTypes.add(Collection.class);
try {
collectionTypes.add(ClassUtils.forName("io.vavr.collection.Seq", classLoader));
} catch (ClassNotFoundException o_O) {}
try {
collectionTypes.add(ClassUtils.forName("io.vavr.collection.Set", classLoader));
} catch (ClassNotFoundException o_O) {}
COLLECTION_TYPES = collectionTypes.toArray(new Class[0]);
}
ResolvableType resolvableType;
private Map<String, Optional<TypeInformation<?>>> fields = new ConcurrentHashMap<>();
private final Lazy<Class<S>> resolvedType;
private final Lazy<TypeInformation<?>> componentType;
private final Lazy<TypeInformation<?>> valueType;
/**
* Creates a new {@link TypeDiscoverer} for the given type, type variable map and parent.
*
* @param type must not be {@literal null}.
* @param typeVariableMap must not be {@literal null}.
*/
protected TypeDiscoverer(Type type, Map<TypeVariable<?>, Type> typeVariableMap) {
public TypeDiscoverer(Class<?> type) {
this(ResolvableType.forClass(type));
}
public TypeDiscoverer(ResolvableType type) {
Assert.notNull(type, "Type must not be null");
Assert.notNull(typeVariableMap, "TypeVariableMap must not be null");
this.type = type;
this.resolvedType = Lazy.of(() -> resolveType(type));
this.resolvableType = type;
this.componentType = Lazy.of(this::doGetComponentType);
this.valueType = Lazy.of(this::doGetMapValueType);
this.typeVariableMap = typeVariableMap;
this.hashCode = 17 + (31 * type.hashCode()) + (31 * typeVariableMap.hashCode());
}
/**
* Returns the type variable map.
*
* @return
*/
protected Map<TypeVariable<?>, Type> getTypeVariableMap() {
return typeVariableMap;
}
/**
* Creates {@link TypeInformation} for the given {@link Type}.
*
* @param fieldType must not be {@literal null}.
* @return
*/
@SuppressWarnings({ "rawtypes", "unchecked" })
protected TypeInformation<?> createInfo(Type fieldType) {
Assert.notNull(fieldType, "Field type must not be null");
if (fieldType.equals(this.type)) {
return this;
}
if (fieldType instanceof Class) {
return ClassTypeInformation.from((Class<?>) fieldType);
}
if (fieldType instanceof ParameterizedType parameterizedType) {
return new ParameterizedTypeInformation(parameterizedType, this);
}
if (fieldType instanceof TypeVariable<?> variable) {
return new TypeVariableTypeInformation(variable, this);
}
if (fieldType instanceof GenericArrayType) {
return new GenericArrayTypeInformation((GenericArrayType) fieldType, this);
}
if (fieldType instanceof WildcardType wildcardType) {
Type[] bounds = wildcardType.getLowerBounds();
if (bounds.length > 0) {
return createInfo(bounds[0]);
}
bounds = wildcardType.getUpperBounds();
if (bounds.length > 0) {
return createInfo(bounds[0]);
}
}
throw new IllegalArgumentException();
}
/**
* Resolves the given type into a plain {@link Class}.
*
* @param type
* @return
*/
@SuppressWarnings({ "unchecked", "rawtypes" })
protected Class<S> resolveType(Type type) {
Map<TypeVariable, Type> map = new HashMap<>();
map.putAll(getTypeVariableMap());
return (Class<S>) GenericTypeResolver.resolveType(type, map);
}
@Override
public List<TypeInformation<?>> getParameterTypes(Constructor<?> constructor) {
Assert.notNull(constructor, "Constructor must not be null");
List<TypeInformation<?>> parameterTypes = new ArrayList<>(constructor.getParameterCount());
for (Parameter parameter : constructor.getParameters()) {
parameterTypes.add(createInfo(parameter.getParameterizedType()));
List<TypeInformation<?>> target = new ArrayList<>();
for (int i = 0; i < constructor.getParameterCount(); i++) {
target.add(new TypeDiscoverer<>(ResolvableType.forConstructorParameter(constructor, i)));
}
return parameterTypes;
return target;
}
@Override
public TypeDescriptor toTypeDescriptor() {
return new TypeDescriptor(resolvableType, null, null);
}
@Nullable
public TypeInformation<?> getProperty(String fieldname) {
@Override
public TypeInformation<?> getProperty(String name) {
int separatorIndex = fieldname.indexOf('.');
var separatorIndex = name.indexOf('.');
if (separatorIndex == -1) {
return fieldTypes.computeIfAbsent(fieldname, this::getPropertyInformation).orElse(null);
return fields.computeIfAbsent(name, this::getPropertyInformation).orElse(null);
}
String head = fieldname.substring(0, separatorIndex);
TypeInformation<?> info = getProperty(head);
var head = name.substring(0, separatorIndex);
var info = getProperty(head);
if (info == null) {
return null;
}
return info.getProperty(fieldname.substring(separatorIndex + 1));
return info.getProperty(name.substring(separatorIndex + 1));
}
/**
* Returns the {@link TypeInformation} for the given atomic field. Will inspect fields first and return the type of a
* field if available. Otherwise it will fall back to a {@link PropertyDescriptor}.
*
* @see #getGenericType(PropertyDescriptor)
* @param fieldname
* @return
*/
@SuppressWarnings("null")
private Optional<TypeInformation<?>> getPropertyInformation(String fieldname) {
Class<?> rawType = getType();
Field field = ReflectionUtils.findField(rawType, fieldname);
Class<?> rawType = resolvableType.toClass();
var field = ReflectionUtils.findField(rawType, fieldname);
if (field != null) {
return Optional.of(createInfo(field.getGenericType()));
return Optional.of(new TypeDiscoverer(ResolvableType.forField(field, resolvableType)));
}
return findPropertyDescriptor(rawType, fieldname).map(it -> createInfo(getGenericType(it)));
return findPropertyDescriptor(rawType, fieldname).map(it -> {
if (it.getReadMethod() != null) {
return new TypeDiscoverer(ResolvableType.forMethodReturnType(it.getReadMethod(), rawType));
}
if (it.getWriteMethod() != null) {
return new TypeDiscoverer(ResolvableType.forMethodParameter(it.getWriteMethod(), 0, rawType));
}
return new TypeDiscoverer(ResolvableType.forType(it.getPropertyType(), resolvableType));
});
}
/**
* Finds the {@link PropertyDescriptor} for the property with the given name on the given type.
*
* @param type must not be {@literal null}.
* @param fieldname must not be {@literal null} or empty.
* @return
*/
private static Optional<PropertyDescriptor> findPropertyDescriptor(Class<?> type, String fieldname) {
private Optional<PropertyDescriptor> findPropertyDescriptor(Class<?> type, String fieldname) {
PropertyDescriptor descriptor = BeanUtils.getPropertyDescriptor(type, fieldname);
@@ -233,50 +176,153 @@ class TypeDiscoverer<S> implements TypeInformation<S> {
.findFirst();
}
/**
* Returns the generic type for the given {@link PropertyDescriptor}. Will inspect its read method followed by the
* first parameter of the write method.
*
* @param descriptor must not be {@literal null}
* @return
*/
@Nullable
private static Type getGenericType(PropertyDescriptor descriptor) {
@Override
public boolean isCollectionLike() {
Method method = descriptor.getReadMethod();
Class<S> type = getType();
if (method != null) {
return method.getGenericReturnType();
for (Class<?> collectionType : COLLECTION_TYPES) {
if (collectionType.isAssignableFrom(type)) {
return true;
}
}
method = descriptor.getWriteMethod();
return type.isArray() //
|| Iterable.class.equals(type) //
|| Collection.class.isAssignableFrom(type) //
|| Streamable.class.isAssignableFrom(type)
|| CustomCollections.isCollection(type);
}
if (method == null) {
@Nullable
@Override
public TypeInformation<?> getComponentType() {
return componentType.orElse(null);
}
@Nullable
protected TypeInformation<?> doGetComponentType() {
var rawType = getType();
if (rawType.isArray()) {
return new TypeDiscoverer<>(resolvableType.getComponentType());
}
if (isMap()) {
if (ClassUtils.isAssignable(Map.class, rawType)) {
ResolvableType mapValueType = resolvableType.asMap().getGeneric(0);
if (ResolvableType.NONE.equals(mapValueType)) {
return null;
}
return mapValueType != null ? new TypeDiscoverer(mapValueType) : new ClassTypeInformation<>(Object.class);
}
if (resolvableType.hasGenerics()) {
ResolvableType mapValueType = resolvableType.getGeneric(0);
return mapValueType != null ? new TypeDiscoverer(mapValueType) : new ClassTypeInformation<>(Object.class);
}
return Arrays.stream(resolvableType.getInterfaces()).filter(ResolvableType::hasGenerics)
.findFirst()
.map(it -> it.getGeneric(0))
.map(TypeDiscoverer::new)
.orElse(null);
}
if (Iterable.class.isAssignableFrom(rawType)) {
ResolvableType iterableType = resolvableType.as(Iterable.class);
ResolvableType mapValueType = iterableType.getGeneric(0);
if (ResolvableType.NONE.equals(mapValueType)) {
return null;
}
if (resolvableType.hasGenerics()) {
mapValueType = resolvableType.getGeneric(0);
return mapValueType != null ? new TypeDiscoverer(mapValueType) : new ClassTypeInformation<>(Object.class);
}
return mapValueType.resolve() != null ? new TypeDiscoverer<>(mapValueType) : null;
}
if (isNullableWrapper()) {
ResolvableType mapValueType = resolvableType.getGeneric(0);
if (ResolvableType.NONE.equals(mapValueType)) {
return null;
}
return mapValueType != null ? new TypeDiscoverer(mapValueType) : new ClassTypeInformation<>(Object.class);
}
if (resolvableType.hasGenerics()) {
ResolvableType mapValueType = resolvableType.getGeneric(0);
return mapValueType != null ? new TypeDiscoverer(mapValueType) : new ClassTypeInformation<>(Object.class);
}
return null;
}
private boolean isNullableWrapper() {
return NullableWrapperConverters.supports(getType());
}
@Override
public boolean isMap() {
return CustomCollections.isMap(getType());
}
@Nullable
@Override
public TypeInformation<?> getMapValueType() {
return valueType.orElse(null);
}
@Nullable
protected TypeInformation<?> doGetMapValueType() {
if (isMap()) {
if (ClassUtils.isAssignable(Map.class, getType())) {
ResolvableType mapValueType = resolvableType.asMap().getGeneric(1);
if (ResolvableType.NONE.equals(mapValueType)) {
return null;
}
return mapValueType != null ? new TypeDiscoverer(mapValueType) : new ClassTypeInformation<>(Object.class);
}
if (resolvableType.hasGenerics()) {
ResolvableType mapValueType = resolvableType.getGeneric(1);
return mapValueType != null ? new TypeDiscoverer(mapValueType) : new ClassTypeInformation<>(Object.class);
}
return Arrays.stream(resolvableType.getInterfaces()).filter(ResolvableType::hasGenerics)
.findFirst()
.map(it -> it.getGeneric(1))
.map(TypeDiscoverer::new)
.orElse(null);
}
if (!resolvableType.hasGenerics()) {
return null;
}
ResolvableType x = Arrays.stream(resolvableType.getGenerics()).skip(1).findFirst().orElse(null);
if ((x == null) || ResolvableType.NONE.equals(x)) {
return null;
}
Type[] parameterTypes = method.getGenericParameterTypes();
return parameterTypes.length == 0 ? null : parameterTypes[0];
return new TypeDiscoverer<>(x);
}
@Override
public Class<S> getType() {
return resolvedType.get();
}
@Override
public TypeDescriptor toTypeDescriptor() {
return new TypeDescriptor(toResolvableType(), getType(), null);
return (Class<S>) resolvableType.toClass();
}
@Override
public ClassTypeInformation<?> getRawTypeInformation() {
return ClassTypeInformation.from(getType()).getRawTypeInformation();
return new ClassTypeInformation<>(this.resolvableType.getRawClass());
}
@Nullable
@Override
public TypeInformation<?> getActualType() {
if (isMap()) {
return getMapValueType();
}
@@ -294,80 +340,27 @@ class TypeDiscoverer<S> implements TypeInformation<S> {
return this;
}
public boolean isMap() {
return CustomCollections.isMap(getType());
}
@Nullable
public TypeInformation<?> getMapValueType() {
return valueType.orElse(null);
}
@Nullable
protected TypeInformation<?> doGetMapValueType() {
return isMap() //
? getTypeArgument(CustomCollections.getMapBaseType(getType()), 1)
: getTypeArguments().stream().skip(1).findFirst().orElse(null);
}
public boolean isCollectionLike() {
Class<?> rawType = getType();
return rawType.isArray() //
|| Iterable.class.equals(rawType) //
|| Streamable.class.isAssignableFrom(rawType) //
|| CustomCollections.isCollection(rawType);
}
@Nullable
public final TypeInformation<?> getComponentType() {
return componentType.orElse(null);
}
@Nullable
protected TypeInformation<?> doGetComponentType() {
Class<S> rawType = getType();
if (rawType.isArray()) {
return createInfo(rawType.getComponentType());
}
if (isMap()) {
return getTypeArgument(CustomCollections.getMapBaseType(rawType), 0);
}
if (Iterable.class.isAssignableFrom(rawType)) {
return getTypeArgument(Iterable.class, 0);
}
if (isNullableWrapper()) {
return getTypeArgument(rawType, 0);
}
List<TypeInformation<?>> arguments = getTypeArguments();
return arguments.size() > 0 ? arguments.get(0) : null;
}
@Override
public TypeInformation<?> getReturnType(Method method) {
Assert.notNull(method, "Method must not be null");
return createInfo(method.getGenericReturnType());
return new TypeDiscoverer(ResolvableType.forMethodReturnType(method, getType()));
}
@Override
public List<TypeInformation<?>> getParameterTypes(Method method) {
Assert.notNull(method, "Method most not be null");
return Streamable.of(method.getGenericParameterTypes()).stream()//
.map(this::createInfo)//
return Streamable.of(method.getParameters()).stream().map(MethodParameter::forParameter)
.map(it -> ResolvableType.forMethodParameter(it, resolvableType)).map(TypeDiscoverer::new)
.collect(Collectors.toList());
}
@Nullable
@Override
public TypeInformation<?> getSuperTypeInformation(Class<?> superType) {
Class<?> rawType = getType();
@@ -376,181 +369,135 @@ class TypeDiscoverer<S> implements TypeInformation<S> {
return null;
}
if (getType().equals(superType)) {
if (rawType.equals(superType)) {
return this;
}
List<Type> candidates = new ArrayList<>();
Type genericSuperclass = rawType.getGenericSuperclass();
List<ResolvableType> candidates = new ArrayList<>();
if (genericSuperclass != null) {
ResolvableType genericSuperclass = resolvableType.getSuperType();
if ((genericSuperclass != null) && !genericSuperclass.equals(ResolvableType.NONE)) {
candidates.add(genericSuperclass);
}
candidates.addAll(Arrays.asList(rawType.getGenericInterfaces()));
candidates.addAll(Arrays.asList(resolvableType.getInterfaces()));
for (Type candidate : candidates) {
for (var candidate : candidates) {
if (ObjectUtils.nullSafeEquals(superType, candidate.toClass())) {
TypeInformation<?> candidateInfo = createInfo(candidate);
if (resolvableType.getType() instanceof Class) {
if (superType.equals(candidateInfo.getType())) {
return candidateInfo;
if (ObjectUtils.isEmpty(((Class) resolvableType.getType()).getTypeParameters())) {
Class<?>[] classes = candidate.resolveGenerics(null);
if (!Arrays.stream(classes).filter(it -> it != null).findAny().isPresent()) {
return new TypeDiscoverer<>(ResolvableType.forRawClass(superType));
}
}
}
return new TypeDiscoverer(ResolvableType.forClass(superType, getType()));
} else {
TypeInformation<?> nestedSuperType = candidateInfo.getSuperTypeInformation(superType);
if (nestedSuperType != null) {
return nestedSuperType;
var sup = candidate.getSuperType();
if ((sup != null) && !ResolvableType.NONE.equals(sup)) {
if (sup.equals(resolvableType)) {
return this;
}
return new TypeDiscoverer(sup);
}
}
}
return null;
}
public List<TypeInformation<?>> getTypeArguments() {
return java.util.Collections.emptyList();
return new TypeDiscoverer(resolvableType.as(superType));
}
/* (non-Javadoc)
* @see org.springframework.data.util.TypeInformation#isAssignableFrom(org.springframework.data.util.TypeInformation)
*/
public boolean isAssignableFrom(TypeInformation<?> target) {
TypeInformation<?> superTypeInformation = target.getSuperTypeInformation(getType());
return superTypeInformation != null && superTypeInformation.equals(this);
if (superTypeInformation == null) {
return false;
}
if (superTypeInformation.equals(this)) {
return true;
}
if (resolvableType.isAssignableFrom(target.getType())) {
return true;
}
return false;
}
@Override
public List<TypeInformation<?>> getTypeArguments() {
if (!resolvableType.hasGenerics()) {
return Collections.emptyList();
}
return Arrays.stream(resolvableType.getGenerics()).map(it -> {
if ((it == null) || ResolvableType.NONE.equals(it)) {
return null;
}
return new TypeDiscoverer<>(it);
}).collect(Collectors.toList());
}
@Override
@SuppressWarnings("unchecked")
public TypeInformation<? extends S> specialize(ClassTypeInformation<?> type) {
// if(isAssignableFrom(type)) {
// return new ClassTypeInformation(type.getType());
// }
// return new NewTypeDiscoverer(type.resolvableType.as(getType()));
// if(type.resolvableType.isAssignableFrom(type.resolvableType)) {
// return (TypeInformation<? extends S>) type;
// }
Assert.notNull(type, "Type must not be null");
Assert.isTrue(getType().isAssignableFrom(type.getType()),
() -> String.format("%s must be assignable from %s", getType(), type.getType()));
List<TypeInformation<?>> typeArguments = getTypeArguments();
return (TypeInformation<? extends S>) (typeArguments.isEmpty() //
? type //
: type.createInfo(new SyntheticParamterizedType(type, getTypeArguments())));
}
@Nullable
private TypeInformation<?> getTypeArgument(Class<?> bound, int index) {
Class<?>[] arguments = GenericTypeResolver.resolveTypeArguments(getType(), bound);
if (arguments != null) {
return createInfo(arguments[index]);
if (this.resolvableType.getGenerics().length == type.resolvableType.getGenerics().length) {
return new TypeDiscoverer<>(
ResolvableType.forClassWithGenerics(type.getType(), this.resolvableType.getGenerics()));
}
return getSuperTypeInformation(bound) instanceof ParameterizedTypeInformation //
? ClassTypeInformation.OBJECT //
: null;
}
protected ResolvableType toResolvableType() {
return ResolvableType.forType(type);
return new ClassTypeInformation(type.getType());
}
@Override
public boolean equals(@Nullable Object obj) {
if (obj == this) {
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (obj == null) {
if ((o == null) || !ClassUtils.isAssignable(getClass(), o.getClass())) {
return false;
}
if (!this.getClass().equals(obj.getClass())) {
TypeDiscoverer<?> that = (TypeDiscoverer<?>) o;
if (!ObjectUtils.nullSafeEquals(getType(), that.getType())) {
return false;
}
TypeDiscoverer<?> that = (TypeDiscoverer<?>) obj;
List<? extends Class<?>> collect1 = Arrays.stream(resolvableType.getGenerics()).map(ResolvableType::toClass)
.collect(Collectors.toList());
List<? extends Class<?>> collect2 = Arrays.stream(that.resolvableType.getGenerics()).map(ResolvableType::toClass)
.collect(Collectors.toList());
if (!this.type.equals(that.type)) {
if (!ObjectUtils.nullSafeEquals(collect1, collect2)) {
return false;
}
if (this.typeVariableMap.isEmpty() && that.typeVariableMap.isEmpty()) {
return true;
}
return this.typeVariableMap.equals(that.typeVariableMap);
return true;
}
@Override
public int hashCode() {
return hashCode;
return ObjectUtils.nullSafeHashCode(resolvableType.toClass());
}
private boolean isNullableWrapper() {
return NullableWrapperConverters.supports(getType());
}
/**
* A synthetic {@link ParameterizedType}.
*
* @author Oliver Gierke
* @since 1.11
*/
private static class SyntheticParamterizedType implements ParameterizedType {
private final ClassTypeInformation<?> typeInformation;
private final List<TypeInformation<?>> typeParameters;
public SyntheticParamterizedType(ClassTypeInformation<?> typeInformation, List<TypeInformation<?>> typeParameters) {
this.typeInformation = typeInformation;
this.typeParameters = typeParameters;
}
@Override
public Type getRawType() {
return typeInformation.getType();
}
@Override
@Nullable
public Type getOwnerType() {
return null;
}
@Override
public Type[] getActualTypeArguments() {
Type[] result = new Type[typeParameters.size()];
for (int i = 0; i < typeParameters.size(); i++) {
result[i] = typeParameters.get(i).getType();
}
return result;
}
@Override
public boolean equals(@Nullable Object o) {
if (this == o) {
return true;
}
if (!(o instanceof SyntheticParamterizedType that)) {
return false;
}
if (!ObjectUtils.nullSafeEquals(typeInformation, that.typeInformation)) {
return false;
}
return ObjectUtils.nullSafeEquals(typeParameters, that.typeParameters);
}
@Override
public int hashCode() {
int result = ObjectUtils.nullSafeHashCode(typeInformation);
result = (31 * result) + ObjectUtils.nullSafeHashCode(typeParameters);
return result;
}
@Override
public String toString() {
return getType().getName();
}
}

View File

@@ -279,6 +279,9 @@ public interface TypeInformation<S> {
*/
TypeInformation<? extends S> specialize(ClassTypeInformation<?> type);
default TypeInformation<? extends S> specialize(TypeInformation<?> type) {
return specialize(ClassTypeInformation.from(type.getType()));
}
/**
* Returns whether the current type is a sub type of the given one, i.e. whether it's assignable but not the same one.
*

View File

@@ -1,96 +0,0 @@
/*
* Copyright 2011-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.util;
import static org.springframework.util.ObjectUtils.*;
import java.lang.reflect.Type;
import java.lang.reflect.TypeVariable;
import java.util.List;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
/**
* Special {@link TypeDiscoverer} to determine the actual type for a {@link TypeVariable}. Will consider the context the
* {@link TypeVariable} is being used in.
*
* @author Oliver Gierke
* @author Alessandro Nistico
*/
class TypeVariableTypeInformation<T> extends ParentTypeAwareTypeInformation<T> {
private final TypeVariable<?> variable;
private final Lazy<List<TypeInformation<?>>> parameters;
/**
* Creates a new {@link TypeVariableTypeInformation} for the given {@link TypeVariable} owning {@link Type} and parent
* {@link TypeDiscoverer}.
*
* @param variable must not be {@literal null}
* @param owningType must not be {@literal null}
* @param parent
*/
public TypeVariableTypeInformation(TypeVariable<?> variable, TypeDiscoverer<?> parent) {
super(variable, parent);
Assert.notNull(variable, "TypeVariable must not be null");
this.variable = variable;
this.parameters = Lazy.of(() -> {
return createInfo(getTypeVariableMap().getOrDefault(variable, Object.class)).getTypeArguments();
});
}
/*
* (non-Javadoc)
* @see org.springframework.data.util.TypeDiscoverer#getTypeArguments()
*/
@Override
public List<TypeInformation<?>> getTypeArguments() {
return parameters.get();
}
@Override
public boolean equals(@Nullable Object obj) {
if (obj == this) {
return true;
}
if (!(obj instanceof TypeVariableTypeInformation<?> that)) {
return false;
}
return getType().equals(that.getType());
}
@Override
public int hashCode() {
int result = 17;
result += 31 * nullSafeHashCode(getType());
return result;
}
@Override
public String toString() {
return variable.getName();
}
}

View File

@@ -146,7 +146,7 @@ public class JsonProjectingMethodInterceptorFactory implements MethodInterceptor
public Object invoke(MethodInvocation invocation) throws Throwable {
Method method = invocation.getMethod();
TypeInformation<Object> returnType = ClassTypeInformation.fromReturnTypeOf(method);
TypeInformation<?> returnType = ClassTypeInformation.fromReturnTypeOf(method);
ResolvableType type = ResolvableType.forMethodReturnType(method);
boolean isCollectionResult = Collection.class.isAssignableFrom(type.getRawClass());
type = isCollectionResult ? type : ResolvableType.forClassWithGenerics(List.class, type);