blob: 3159887df841a19427eb4d6aeb4a61d1b354bb7c [file] [log] [blame]
/**
* Copyright (C) 2008 Google Inc.
*
* 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 com.google.inject.internal;
import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.base.Preconditions.checkNotNull;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Maps;
import com.google.inject.util.Types;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.GenericArrayType;
import java.lang.reflect.GenericDeclaration;
import java.lang.reflect.Member;
import java.lang.reflect.Method;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.lang.reflect.TypeVariable;
import java.util.List;
import java.util.Map;
/**
* Applies known information to generic types to discover more precise generic
* types. For example, the Java reflection API exposes the return type of
* {@code Collection.iterator()} as {@code Iterator<E>}. This class leverages
* more precise type (such as {@code Set<String>}) to expose a more precise
* return type: {@code Iterator<String>}. To accomplish this, type resolver
* fills the generic parameter {@code E} with the known type {@code String}.
* Parameters whose types are unavailable will not be resolved.
*
* <p>This class is not threadsafe.
*
* @author jessewilson@google.com (Jesse Wilson)
*/
public final class TypeResolver {
/** the type being resolved, such as {@code List<String>} */
public final Type type;
/** maps from type variables to the types they resolve to */
private final Map<TypeVariable, Type> index = Maps.newHashMap();
/** types to resolved types */
private final Map<Type, Type> cache = Maps.newHashMap();
/** raw supertypes to the corresponding resolved generic types */
private final Map<Class<?>, Type> implementedTypes = Maps.newHashMap();
/**
* Creates a new type resolver that can resolve the members of {@code type}.
*/
public TypeResolver(Type type) {
this.type = type;
resolveRecursive(type);
}
/**
* Resolves and stores all generic information for {@code type} and all of
* its implemented interfaces and supertypes.
*/
private void resolveRecursive(Type type) {
Class<?> rawType = MoreTypes.getRawType(type);
implementedTypes.put(rawType, resolve(type));
Type supertype = rawType.getGenericSuperclass();
if (supertype != null) {
resolveRecursive(supertype);
}
for (Type genericInterface : rawType.getGenericInterfaces()) {
resolveRecursive(genericInterface);
}
}
/**
* Projects the known type information from this resolver on {@code type} and
* returns the result.
*/
private Type resolve(Type type) {
checkNotNull(type, "type");
Type alreadyResolved = cache.get(type);
if (alreadyResolved != null) {
return alreadyResolved;
} else if (type instanceof ParameterizedType) {
ParameterizedType parameterizedType = (ParameterizedType) type;
Class<?> rawType = MoreTypes.getRawType(parameterizedType);
TypeVariable<? extends Class<?>>[] typeVariables = rawType.getTypeParameters();
Type[] arguments = parameterizedType.getActualTypeArguments();
Type[] resolvedArguments = new Type[typeVariables.length];
for (int v = 0; v < typeVariables.length; v++) {
resolvedArguments[v] = resolve(arguments[v]);
index.put(typeVariables[v], resolvedArguments[v]);
}
Type resolvedRawType = resolve(rawType);
Type ownerType = parameterizedType.getOwnerType();
Type resolvedOwnerType = ownerType != null ? resolve(ownerType) : null;
Type resolved = Types.newParameterizedTypeWithOwner(resolvedOwnerType,
resolvedRawType, resolvedArguments);
cache.put(type, resolved);
return resolved;
} else if (type instanceof TypeVariable) {
TypeVariable typeVariable = (TypeVariable) type;
GenericDeclaration genericDeclaration = typeVariable.getGenericDeclaration();
Type result = typeVariable;
if (genericDeclaration instanceof Class) {
Type resolved = index.get(typeVariable);
result = resolved != null ? resolved : result;
}
cache.put(type, result);
return result;
} else if (type instanceof Class) {
cache.put(type, type);
return type;
} else if (type instanceof GenericArrayType) {
GenericArrayType arrayType = (GenericArrayType) type;
Type componentTypeResolved = resolve(arrayType.getGenericComponentType());
GenericArrayType resolved = Types.arrayOf(componentTypeResolved);
cache.put(type, resolved);
return resolved;
} else {
return type;
}
}
/**
* Returns an immutable list of the resolved types.
*/
private List<Type> resolveAll(Type[] types) {
Type[] result = new Type[types.length];
for (int t = 0; t < types.length; t++) {
result[t] = resolve(types[t]);
}
return ImmutableList.of(result);
}
/**
* Returns the type used to resolve other types. Ideally this is a fully
* specified type such as {@code List<String>}.
*/
public Type getType() {
return type;
}
/**
* Returns the raw form of the type being resolved, such as {@code List}.
*/
public Class<?> getRawType() {
return MoreTypes.getRawType(type);
}
/**
* Returns the generic equivalent of {@code supertype}. For example, if this
* is a resolver of {@code ArrayList<String>}, this method returns {@code
* Iterable<String>} given the input {@code Iterable.class}.
*
* @param supertype a superclass of, or interface implemented by, this
* resolver's type.
*/
public Type getSupertype(Class<?> supertype) {
Type type = implementedTypes.get(supertype);
checkArgument(type != null,
"%s is not a supertype of %s", supertype, this.type);
return type;
}
/**
* Returns the resolved generic type of {@code field}.
*
* @param field a field defined by this resolver's type or any of its
* superclasses.
*/
public Type getFieldType(Field field) {
checkArgument(implementedTypes.containsKey(field.getDeclaringClass()),
"%s is not defined by a supertype of %s", field, type);
return resolve(field.getGenericType());
}
/**
* Returns a list of the resolved generic exception types of {@code
* constructor}.
*
* @param constructor a constructor defined by this resolver's type or any of
* its superclasses.
*/
public List<Type> getExceptionTypes(Constructor constructor) {
checkArgument(implementedTypes.containsKey(constructor.getDeclaringClass()),
"%s does not construct a supertype of %s", constructor, type);
return resolveAll(constructor.getGenericExceptionTypes());
}
/**
* Returns a list of the resolved generic parameter types of {@code methodOrConstructor}.
*
* @param methodOrConstructor a method or constructor defined by this resolver's type, its
* superclasses or implemented interfaces.
*/
public List<Type> getParameterTypes(Member methodOrConstructor) {
Type[] genericParameterTypes;
if (methodOrConstructor instanceof Method) {
Method method = (Method) methodOrConstructor;
checkArgument(implementedTypes.containsKey(method.getDeclaringClass()),
"%s is not defined by a supertype of %s", method, type);
genericParameterTypes = method.getGenericParameterTypes();
} else if (methodOrConstructor instanceof Constructor) {
Constructor constructor = (Constructor) methodOrConstructor;
checkArgument(implementedTypes.containsKey(constructor.getDeclaringClass()),
"%s does not construct a supertype of %s", constructor, type);
genericParameterTypes = constructor.getGenericParameterTypes();
} else {
throw new IllegalArgumentException("Not a method or a constructor: " + methodOrConstructor);
}
return resolveAll(genericParameterTypes);
}
/**
* Returns a list of the resolved generic exception types of {@code method}.
*
* @param method a method defined by this resolver's type, its superclasses
* or implemented interfaces.
*/
public List<Type> getExceptionTypes(Method method) {
checkArgument(implementedTypes.containsKey(method.getDeclaringClass()),
"%s is not defined by a supertype of %s", method, type);
return resolveAll(method.getGenericExceptionTypes());
}
/**
* Returns the resolved generic return type of {@code method}.
*
* @param method a method defined by this resolver's type, its superclasses
* or implemented interfaces.
*/
public Type getReturnType(Method method) {
checkArgument(implementedTypes.containsKey(method.getDeclaringClass()),
"%s is not defined by a supertype of %s", method, type);
return resolve(method.getGenericReturnType());
}
@Override public boolean equals(Object obj) {
return obj instanceof TypeResolver
&& getType().equals(((TypeResolver) obj).getType());
}
@Override public int hashCode() {
return 79 * type.hashCode();
}
@Override public String toString() {
return "Resolver:" + type;
}
}