/*
* Copyright (C) 2009 The Guava 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
*
* http://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.jdbi.v3.core.generic.internal;
import static java.util.Collections.emptyMap;
import static java.util.Collections.unmodifiableMap;
import static org.jdbi.v3.core.generic.internal.Preconditions.checkArgument;
import static org.jdbi.v3.core.generic.internal.Preconditions.checkNotNull;
import static org.jdbi.v3.core.generic.internal.Preconditions.checkState;
import javax.annotation.Nullable;
import java.lang.reflect.GenericArrayType;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.lang.reflect.TypeVariable;
import java.lang.reflect.WildcardType;
import java.util.Arrays;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.stream.Collectors;
import java.util.stream.Stream;
public final class TypeResolver {
private final TypeTable typeTable;
public TypeResolver() {
this.typeTable = new TypeTable();
}
private TypeResolver(TypeTable typeTable) {
this.typeTable = typeTable;
}
static TypeResolver accordingTo(Type type) {
return new TypeResolver().where(TypeMappingIntrospector.getTypeMappings(type));
}
public TypeResolver where(Type formal, Type actual) {
Map<TypeVariableKey, Type> mappings = new HashMap<>();
populateTypeMappings(mappings, checkNotNull(formal, "formal"), checkNotNull(actual, "actual"));
return where(mappings);
}
TypeResolver where(Map<TypeVariableKey, ? extends Type> mappings) {
return new TypeResolver(typeTable.where(mappings));
}
private static void populateTypeMappings(
final Map<TypeVariableKey, Type> mappings, Type from, final Type to) {
if (from.equals(to)) {
return;
}
new TypeVisitor() {
@Override
void visitTypeVariable(TypeVariable<?> typeVariable) {
mappings.put(new TypeVariableKey(typeVariable), to);
}
@Override
void visitWildcardType(WildcardType fromWildcardType) {
if (!(to instanceof WildcardType)) {
return; // okay to say <?> is anything
}
WildcardType toWildcardType = (WildcardType) to;
Type[] fromUpperBounds = fromWildcardType.getUpperBounds();
Type[] toUpperBounds = toWildcardType.getUpperBounds();
Type[] fromLowerBounds = fromWildcardType.getLowerBounds();
Type[] toLowerBounds = toWildcardType.getLowerBounds();
checkArgument(
fromUpperBounds.length == toUpperBounds.length
&& fromLowerBounds.length == toLowerBounds.length,
"Incompatible type: %s vs. %s",
fromWildcardType,
to);
for (int i = 0; i < fromUpperBounds.length; i++) {
populateTypeMappings(mappings, fromUpperBounds[i], toUpperBounds[i]);
}
for (int i = 0; i < fromLowerBounds.length; i++) {
populateTypeMappings(mappings, fromLowerBounds[i], toLowerBounds[i]);
}
}
@Override
void visitParameterizedType(ParameterizedType fromParameterizedType) {
if (to instanceof WildcardType) {
return; // Okay to say Foo<A> is <?>
}
ParameterizedType toParameterizedType = expectArgument(ParameterizedType.class, to);
if (fromParameterizedType.getOwnerType() != null
&& toParameterizedType.getOwnerType() != null) {
populateTypeMappings(
mappings, fromParameterizedType.getOwnerType(), toParameterizedType.getOwnerType());
}
checkArgument(
fromParameterizedType.getRawType().equals(toParameterizedType.getRawType()),
"Inconsistent raw type: %s vs. %s",
fromParameterizedType,
to);
Type[] fromArgs = fromParameterizedType.getActualTypeArguments();
Type[] toArgs = toParameterizedType.getActualTypeArguments();
checkArgument(
fromArgs.length == toArgs.length,
"%s not compatible with %s",
fromParameterizedType,
toParameterizedType);
for (int i = 0; i < fromArgs.length; i++) {
populateTypeMappings(mappings, fromArgs[i], toArgs[i]);
}
}
@Override
void visitGenericArrayType(GenericArrayType fromArrayType) {
if (to instanceof WildcardType) {
return; // Okay to say A[] is <?>
}
Type componentType = Types.getComponentType(to);
checkArgument(componentType != null, "%s is not an array type.", to);
populateTypeMappings(mappings, fromArrayType.getGenericComponentType(), componentType);
}
@Override
void visitClass(Class<?> fromClass) {
if (to instanceof WildcardType) {
return; // Okay to say Foo is <?>
}
// Can't map from a raw class to anything other than itself or a wildcard.
// You can't say "assuming String is Integer".
// And we don't support "assuming String is T"; user has to say "assuming T is String".
throw new IllegalArgumentException("No type mapping from " + fromClass + " to " + to);
}
}.visit(from);
}
public Type resolveType(Type type) {
checkNotNull(type, "type");
if (type instanceof TypeVariable) {
return typeTable.resolve((TypeVariable<?>) type);
} else if (type instanceof ParameterizedType) {
return resolveParameterizedType((ParameterizedType) type);
} else if (type instanceof GenericArrayType) {
return resolveGenericArrayType((GenericArrayType) type);
} else if (type instanceof WildcardType) {
return resolveWildcardType((WildcardType) type);
} else {
// if Class<?>, no resolution needed, we are done.
return type;
}
}
private Type[] resolveTypes(Type[] types) {
Type[] result = new Type[types.length];
for (int i = 0; i < types.length; i++) {
result[i] = resolveType(types[i]);
}
return result;
}
private WildcardType resolveWildcardType(WildcardType type) {
Type[] lowerBounds = type.getLowerBounds();
Type[] upperBounds = type.getUpperBounds();
return new Types.WildcardTypeImpl(resolveTypes(lowerBounds), resolveTypes(upperBounds));
}
private Type resolveGenericArrayType(GenericArrayType type) {
Type componentType = type.getGenericComponentType();
Type resolvedComponentType = resolveType(componentType);
return Types.newArrayType(resolvedComponentType);
}
private ParameterizedType resolveParameterizedType(ParameterizedType type) {
Type owner = type.getOwnerType();
Type resolvedOwner = (owner == null) ? null : resolveType(owner);
Type resolvedRawType = resolveType(type.getRawType());
Type[] args = type.getActualTypeArguments();
Type[] resolvedArgs = resolveTypes(args);
return Types.newParameterizedTypeWithOwner(
resolvedOwner, (Class<?>) resolvedRawType, resolvedArgs);
}
private static <T> T expectArgument(Class<T> type, Object arg) {
try {
return type.cast(arg);
} catch (ClassCastException e) {
throw new IllegalArgumentException(arg + " is not a " + type.getSimpleName());
}
}
/** A TypeTable maintains mapping from {@link TypeVariable} to types. */
private static class TypeTable {
private final Map<TypeVariableKey, Type> map;
TypeTable() {
this.map = emptyMap();
}
private TypeTable(Map<TypeVariableKey, Type> map) {
this.map = unmodifiableMap(new LinkedHashMap<>(map));
}
/** Returns a new {@code TypeResolver} with {@code variable} mapping to {@code type}. */
final TypeTable where(Map<TypeVariableKey, ? extends Type> mappings) {
Map<TypeVariableKey, Type> builder = new LinkedHashMap<>();
builder.putAll(map);
for (Map.Entry<TypeVariableKey, ? extends Type> mapping : mappings.entrySet()) {
TypeVariableKey variable = mapping.getKey();
Type type = mapping.getValue();
checkArgument(!variable.equalsType(type), "Type variable %s bound to itself", variable);
builder.put(variable, type);
}
return new TypeTable(builder);
}
final Type resolve(final TypeVariable<?> var) {
final TypeTable unguarded = this;
TypeTable guarded =
new TypeTable() {
@Override
public Type resolveInternal(TypeVariable<?> intermediateVar, TypeTable forDependent) {
if (intermediateVar.getGenericDeclaration().equals(var.getGenericDeclaration())) {
return intermediateVar;
}
return unguarded.resolveInternal(intermediateVar, forDependent);
}
};
return resolveInternal(var, guarded);
}
Type resolveInternal(TypeVariable<?> var, TypeTable forDependants) {
Type type = map.get(new TypeVariableKey(var));
if (type == null) {
Type[] bounds = var.getBounds();
if (bounds.length == 0) {
return var;
}
Type[] resolvedBounds = new TypeResolver(forDependants).resolveTypes(bounds);
if (Types.NativeTypeVariableEquals.NATIVE_TYPE_VARIABLE_ONLY
&& Arrays.equals(bounds, resolvedBounds)) {
return var;
}
return Types.newArtificialTypeVariable(
var.getGenericDeclaration(), var.getName(), resolvedBounds);
}
// in case the type is yet another type variable.
return new TypeResolver(forDependants).resolveType(type);
}
}
private static final class TypeMappingIntrospector extends TypeVisitor {
private static final WildcardCapturer wildcardCapturer = new WildcardCapturer();
private final Map<TypeVariableKey, Type> mappings = new HashMap<>();
static Map<TypeVariableKey, Type> getTypeMappings(Type contextType) {
TypeMappingIntrospector introspector = new TypeMappingIntrospector();
introspector.visit(wildcardCapturer.capture(contextType));
return unmodifiableMap(new LinkedHashMap<>(introspector.mappings));
}
@Override
void visitClass(Class<?> clazz) {
visit(clazz.getGenericSuperclass());
visit(clazz.getGenericInterfaces());
}
@Override
void visitParameterizedType(ParameterizedType parameterizedType) {
Class<?> rawClass = (Class<?>) parameterizedType.getRawType();
TypeVariable<?>[] vars = rawClass.getTypeParameters();
Type[] typeArgs = parameterizedType.getActualTypeArguments();
checkState(vars.length == typeArgs.length,
"Expected %s type variables, but got %s", typeArgs.length, vars.length);
for (int i = 0; i < vars.length; i++) {
map(new TypeVariableKey(vars[i]), typeArgs[i]);
}
visit(rawClass);
visit(parameterizedType.getOwnerType());
}
@Override
void visitTypeVariable(TypeVariable<?> t) {
visit(t.getBounds());
}
@Override
void visitWildcardType(WildcardType t) {
visit(t.getUpperBounds());
}
private void map(final TypeVariableKey var, final Type arg) {
if (mappings.containsKey(var)) {
return;
}
for (Type t = arg; t != null; t = mappings.get(TypeVariableKey.forLookup(t))) {
if (var.equalsType(t)) {
for (Type x = arg; x != null; x = mappings.remove(TypeVariableKey.forLookup(x))) {}
return;
}
}
mappings.put(var, arg);
}
}
private static final class WildcardCapturer {
private final AtomicInteger id = new AtomicInteger();
Type capture(Type type) {
checkNotNull(type, "type");
if (type instanceof Class) {
return type;
}
if (type instanceof TypeVariable) {
return type;
}
if (type instanceof GenericArrayType) {
GenericArrayType arrayType = (GenericArrayType) type;
return Types.newArrayType(capture(arrayType.getGenericComponentType()));
}
if (type instanceof ParameterizedType) {
ParameterizedType parameterizedType = (ParameterizedType) type;
return Types.newParameterizedTypeWithOwner(
captureNullable(parameterizedType.getOwnerType()),
(Class<?>) parameterizedType.getRawType(),
capture(parameterizedType.getActualTypeArguments()));
}
if (type instanceof WildcardType) {
WildcardType wildcardType = (WildcardType) type;
Type[] lowerBounds = wildcardType.getLowerBounds();
if (lowerBounds.length == 0) { // ? extends something changes to capture-of
Type[] upperBounds = wildcardType.getUpperBounds();
String name =
"capture#"
+ id.incrementAndGet()
+ "-of ? extends "
+ Stream.of(upperBounds).map(Object::toString).collect(Collectors.joining("&"));
return Types.newArtificialTypeVariable(
WildcardCapturer.class, name, wildcardType.getUpperBounds());
} else {
// TODO(benyu): handle ? super T somehow.
return type;
}
}
throw new AssertionError("must have been one of the known types");
}
private Type captureNullable(@Nullable Type type) {
if (type == null) {
return null;
}
return capture(type);
}
private Type[] capture(Type[] types) {
Type[] result = new Type[types.length];
for (int i = 0; i < types.length; i++) {
result[i] = capture(types[i]);
}
return result;
}
}
static final class TypeVariableKey {
private final TypeVariable<?> var;
TypeVariableKey(TypeVariable<?> var) {
this.var = checkNotNull(var, "var");
}
@Override
public int hashCode() {
return Objects.hash(var.getGenericDeclaration(), var.getName());
}
@Override
public boolean equals(Object obj) {
if (obj instanceof TypeVariableKey) {
TypeVariableKey that = (TypeVariableKey) obj;
return equalsTypeVariable(that.var);
} else {
return false;
}
}
@Override
public String toString() {
return var.toString();
}
static TypeVariableKey forLookup(Type t) {
if (t instanceof TypeVariable) {
return new TypeVariableKey((TypeVariable<?>) t);
} else {
return null;
}
}
boolean equalsType(Type type) {
if (type instanceof TypeVariable) {
return equalsTypeVariable((TypeVariable<?>) type);
} else {
return false;
}
}
private boolean equalsTypeVariable(TypeVariable<?> that) {
return var.getGenericDeclaration().equals(that.getGenericDeclaration())
&& var.getName().equals(that.getName());
}
}
}