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:
committed by
Oliver Drotbohm
parent
8721ab4170
commit
1d6331ccb6
@@ -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());
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -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()) //
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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.
|
||||
*
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
|
||||
Reference in New Issue
Block a user