blob: 75008bd122eade90bcd3fbd8b8fe9c523cdea03d [file] [log] [blame]
/**
* Copyright (C) 2006 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;
import com.google.inject.internal.MoreTypes;
import static com.google.inject.internal.MoreTypes.canonicalize;
import com.google.inject.internal.util.ImmutableList;
import static com.google.inject.internal.util.Preconditions.checkArgument;
import static com.google.inject.internal.util.Preconditions.checkNotNull;
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.Member;
import java.lang.reflect.Method;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.lang.reflect.TypeVariable;
import java.lang.reflect.WildcardType;
import java.util.List;
/**
* Represents a generic type {@code T}. Java doesn't yet provide a way to
* represent generic types, so this class does. Forces clients to create a
* subclass of this class which enables retrieval the type information even at
* runtime.
*
* <p>For example, to create a type literal for {@code List<String>}, you can
* create an empty anonymous inner class:
*
* <p>
* {@code TypeLiteral<List<String>> list = new TypeLiteral<List<String>>() {};}
*
* <p>This syntax cannot be used to create type literals that have wildcard
* parameters, such as {@code Class<?>} or {@code List<? extends CharSequence>}.
* Such type literals must be constructed programatically, either by {@link
* Method#getGenericReturnType extracting types from members} or by using the
* {@link Types} factory class.
*
* <p>Along with modeling generic types, this class can resolve type parameters.
* For example, to figure out what type {@code keySet()} returns on a {@code
* Map<Integer, String>}, use this code:<pre> {@code
*
* TypeLiteral<Map<Integer, String>> mapType
* = new TypeLiteral<Map<Integer, String>>() {};
* TypeLiteral<?> keySetType
* = mapType.getReturnType(Map.class.getMethod("keySet"));
* System.out.println(keySetType); // prints "Set<Integer>"}</pre>
*
* @author crazybob@google.com (Bob Lee)
* @author jessewilson@google.com (Jesse Wilson)
*/
public class TypeLiteral<T> {
final Class<? super T> rawType;
final Type type;
final int hashCode;
/**
* Constructs a new type literal. Derives represented class from type
* parameter.
*
* <p>Clients create an empty anonymous subclass. Doing so embeds the type
* parameter in the anonymous class's type hierarchy so we can reconstitute it
* at runtime despite erasure.
*/
@SuppressWarnings("unchecked")
protected TypeLiteral() {
this.type = getSuperclassTypeParameter(getClass());
this.rawType = (Class<? super T>) MoreTypes.getRawType(type);
this.hashCode = type.hashCode();
}
/**
* Unsafe. Constructs a type literal manually.
*/
@SuppressWarnings("unchecked")
TypeLiteral(Type type) {
this.type = canonicalize(checkNotNull(type, "type"));
this.rawType = (Class<? super T>) MoreTypes.getRawType(this.type);
this.hashCode = this.type.hashCode();
}
/**
* Returns the type from super class's type parameter in {@link MoreTypes#canonicalize(Type)
* canonical form}.
*/
static Type getSuperclassTypeParameter(Class<?> subclass) {
Type superclass = subclass.getGenericSuperclass();
if (superclass instanceof Class) {
throw new RuntimeException("Missing type parameter.");
}
ParameterizedType parameterized = (ParameterizedType) superclass;
return canonicalize(parameterized.getActualTypeArguments()[0]);
}
/**
* Gets type literal from super class's type parameter.
*/
static TypeLiteral<?> fromSuperclassTypeParameter(Class<?> subclass) {
return new TypeLiteral<Object>(getSuperclassTypeParameter(subclass));
}
/**
* Returns the raw (non-generic) type for this type.
*
* @since 2.0
*/
public final Class<? super T> getRawType() {
return rawType;
}
/**
* Gets underlying {@code Type} instance.
*/
public final Type getType() {
return type;
}
/**
* Gets the type of this type's provider.
*/
@SuppressWarnings("unchecked")
final TypeLiteral<Provider<T>> providerType() {
// This cast is safe and wouldn't generate a warning if Type had a type
// parameter.
return (TypeLiteral<Provider<T>>) get(Types.providerOf(getType()));
}
@Override public final int hashCode() {
return this.hashCode;
}
@Override public final boolean equals(Object o) {
return o instanceof TypeLiteral<?>
&& MoreTypes.equals(type, ((TypeLiteral) o).type);
}
@Override public final String toString() {
return MoreTypes.typeToString(type);
}
/**
* Gets type literal for the given {@code Type} instance.
*/
public static TypeLiteral<?> get(Type type) {
return new TypeLiteral<Object>(type);
}
/**
* Gets type literal for the given {@code Class} instance.
*/
public static <T> TypeLiteral<T> get(Class<T> type) {
return new TypeLiteral<T>(type);
}
/** Returns an immutable list of the resolved types. */
private List<TypeLiteral<?>> resolveAll(Type[] types) {
TypeLiteral<?>[] result = new TypeLiteral<?>[types.length];
for (int t = 0; t < types.length; t++) {
result[t] = resolve(types[t]);
}
return ImmutableList.of(result);
}
/**
* Resolves known type parameters in {@code toResolve} and returns the result.
*/
TypeLiteral<?> resolve(Type toResolve) {
return TypeLiteral.get(resolveType(toResolve));
}
Type resolveType(Type toResolve) {
// this implementation is made a little more complicated in an attempt to avoid object-creation
while (true) {
if (toResolve instanceof TypeVariable) {
TypeVariable original = (TypeVariable) toResolve;
toResolve = MoreTypes.resolveTypeVariable(type, rawType, original);
if (toResolve == original) {
return toResolve;
}
} else if (toResolve instanceof GenericArrayType) {
GenericArrayType original = (GenericArrayType) toResolve;
Type componentType = original.getGenericComponentType();
Type newComponentType = resolveType(componentType);
return componentType == newComponentType
? original
: Types.arrayOf(newComponentType);
} else if (toResolve instanceof ParameterizedType) {
ParameterizedType original = (ParameterizedType) toResolve;
Type ownerType = original.getOwnerType();
Type newOwnerType = resolveType(ownerType);
boolean changed = newOwnerType != ownerType;
Type[] args = original.getActualTypeArguments();
for (int t = 0, length = args.length; t < length; t++) {
Type resolvedTypeArgument = resolveType(args[t]);
if (resolvedTypeArgument != args[t]) {
if (!changed) {
args = args.clone();
changed = true;
}
args[t] = resolvedTypeArgument;
}
}
return changed
? Types.newParameterizedTypeWithOwner(newOwnerType, original.getRawType(), args)
: original;
} else if (toResolve instanceof WildcardType) {
WildcardType original = (WildcardType) toResolve;
Type[] originalLowerBound = original.getLowerBounds();
Type[] originalUpperBound = original.getUpperBounds();
if (originalLowerBound.length == 1) {
Type lowerBound = resolveType(originalLowerBound[0]);
if (lowerBound != originalLowerBound[0]) {
return Types.supertypeOf(lowerBound);
}
} else if (originalUpperBound.length == 1) {
Type upperBound = resolveType(originalUpperBound[0]);
if (upperBound != originalUpperBound[0]) {
return Types.subtypeOf(upperBound);
}
}
return original;
} else {
return toResolve;
}
}
}
/**
* Returns the generic form of {@code supertype}. For example, if this is {@code
* ArrayList<String>}, this returns {@code Iterable<String>} given the input {@code
* Iterable.class}.
*
* @param supertype a superclass of, or interface implemented by, this.
* @since 2.0
*/
public TypeLiteral<?> getSupertype(Class<?> supertype) {
checkArgument(supertype.isAssignableFrom(rawType),
"%s is not a supertype of %s", supertype, this.type);
return resolve(MoreTypes.getGenericSupertype(type, rawType, supertype));
}
/**
* Returns the resolved generic type of {@code field}.
*
* @param field a field defined by this or any superclass.
* @since 2.0
*/
public TypeLiteral<?> getFieldType(Field field) {
checkArgument(field.getDeclaringClass().isAssignableFrom(rawType),
"%s is not defined by a supertype of %s", field, type);
return resolve(field.getGenericType());
}
/**
* Returns the resolved generic parameter types of {@code methodOrConstructor}.
*
* @param methodOrConstructor a method or constructor defined by this or any supertype.
* @since 2.0
*/
public List<TypeLiteral<?>> getParameterTypes(Member methodOrConstructor) {
Type[] genericParameterTypes;
if (methodOrConstructor instanceof Method) {
Method method = (Method) methodOrConstructor;
checkArgument(method.getDeclaringClass().isAssignableFrom(rawType),
"%s is not defined by a supertype of %s", method, type);
genericParameterTypes = method.getGenericParameterTypes();
} else if (methodOrConstructor instanceof Constructor) {
Constructor<?> constructor = (Constructor<?>) methodOrConstructor;
checkArgument(constructor.getDeclaringClass().isAssignableFrom(rawType),
"%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 the resolved generic exception types thrown by {@code constructor}.
*
* @param methodOrConstructor a method or constructor defined by this or any supertype.
* @since 2.0
*/
public List<TypeLiteral<?>> getExceptionTypes(Member methodOrConstructor) {
Type[] genericExceptionTypes;
if (methodOrConstructor instanceof Method) {
Method method = (Method) methodOrConstructor;
checkArgument(method.getDeclaringClass().isAssignableFrom(rawType),
"%s is not defined by a supertype of %s", method, type);
genericExceptionTypes = method.getGenericExceptionTypes();
} else if (methodOrConstructor instanceof Constructor) {
Constructor<?> constructor = (Constructor<?>) methodOrConstructor;
checkArgument(constructor.getDeclaringClass().isAssignableFrom(rawType),
"%s does not construct a supertype of %s", constructor, type);
genericExceptionTypes = constructor.getGenericExceptionTypes();
} else {
throw new IllegalArgumentException("Not a method or a constructor: " + methodOrConstructor);
}
return resolveAll(genericExceptionTypes);
}
/**
* Returns the resolved generic return type of {@code method}.
*
* @param method a method defined by this or any supertype.
* @since 2.0
*/
public TypeLiteral<?> getReturnType(Method method) {
checkArgument(method.getDeclaringClass().isAssignableFrom(rawType),
"%s is not defined by a supertype of %s", method, type);
return resolve(method.getGenericReturnType());
}
}