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.Map.Entry;
import java.util.Set;
import org.springframework.core.GenericTypeResolver;
import org.springframework.util.Assert;
import org.springframework.util.ConcurrentReferenceHashMap;
import org.springframework.util.ConcurrentReferenceHashMap.ReferenceType;
@SuppressWarnings({ "unchecked", "rawtypes" })
public class ClassTypeInformation<S> extends TypeDiscoverer<S> {
public static final ClassTypeInformation<Collection> COLLECTION = new ClassTypeInformation(Collection.class);
public static final ClassTypeInformation<List> LIST = new ClassTypeInformation(List.class);
public static final ClassTypeInformation<Set> SET = new ClassTypeInformation(Set.class);
public static final ClassTypeInformation<Map> MAP = new ClassTypeInformation(Map.class);
public static final ClassTypeInformation<Object> OBJECT = new ClassTypeInformation(Object.class);
private static final Map<Class<?>, ClassTypeInformation<?>> CACHE = new ConcurrentReferenceHashMap<>(64,
ReferenceType.WEAK);
static {
Arrays.asList(COLLECTION, LIST, SET, MAP, OBJECT).forEach(it -> CACHE.put(it.getType(), it));
}
private final Class<S> type;
public static <S> ClassTypeInformation<S> from(Class<S> type) {
Assert.notNull(type, "Type must not be null!");
return (ClassTypeInformation<S>) CACHE.computeIfAbsent(type, ClassTypeInformation::new);
}
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());
}
ClassTypeInformation(Class<S> type) {
super(ProxyUtils.getUserClass(type), getTypeVariableMap(type));
this.type = type;
}
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 (Entry<TypeVariable, Type> entry : source.entrySet()) {
Type value = entry.getValue();
map.put(entry.getKey(), entry.getValue());
if (value instanceof Class) {
for (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;
}
@Override
public ClassTypeInformation<?> getRawTypeInformation() {
return this;
}
@Override
public boolean isAssignableFrom(TypeInformation<?> target) {
return getType().isAssignableFrom(target.getType());
}
@Override
public TypeInformation<? extends S> specialize(ClassTypeInformation<?> type) {
return (TypeInformation<? extends S>) type;
}
@Override
public String toString() {
return type.getName();
}
}