blob: a945a57ce4683f4e8465a7270ca28ba575ae0686 [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 static com.google.inject.internal.Objects.nonNull;
import java.lang.reflect.GenericArrayType;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
/**
* 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>Assumes that type {@code T} implements {@link Object#equals} and
* {@link Object#hashCode()} as value (as opposed to identity) comparison.
*
* @author crazybob@google.com (Bob Lee)
*/
public abstract 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>) getRawType(type);
this.hashCode = hashCode(type);
}
/**
* Unsafe. Constructs a type literal manually.
*/
@SuppressWarnings("unchecked")
TypeLiteral(Type type) {
this.rawType = (Class<? super T>) getRawType(nonNull(type, "type"));
this.type = type;
this.hashCode = hashCode(type);
}
/**
* Gets type from super class's type parameter.
*/
static Type getSuperclassTypeParameter(Class<?> subclass) {
Type superclass = subclass.getGenericSuperclass();
if (superclass instanceof Class) {
throw new RuntimeException("Missing type parameter.");
}
return ((ParameterizedType) superclass).getActualTypeArguments()[0];
}
/**
* Gets type literal from super class's type parameter.
*/
static TypeLiteral<?> fromSuperclassTypeParameter(Class<?> subclass) {
return new SimpleTypeLiteral<Object>(getSuperclassTypeParameter(subclass));
}
@SuppressWarnings({ "unchecked" })
private static Class<?> getRawType(Type type) {
if (type instanceof Class<?>) {
// type is a normal class.
return (Class<?>) type;
}
else {
if (type instanceof ParameterizedType) {
ParameterizedType parameterizedType = (ParameterizedType) type;
// I'm not exactly sure why getRawType() returns Type instead of Class.
// Neal isn't either but suspects some pathological case related
// to nested classes exists.
Type rawType = parameterizedType.getRawType();
if (!(rawType instanceof Class<?>)) {
throw unexpectedType(rawType, Class.class);
}
return (Class<?>) rawType;
}
if (type instanceof GenericArrayType) {
// TODO: Is this sufficient?
return Object[].class;
}
// type is a parameterized type.
throw unexpectedType(type, ParameterizedType.class);
}
}
/**
* Gets the raw type.
*/
Class<? super T> getRawType() {
return rawType;
}
/**
* Gets underlying {@code Type} instance.
*/
public Type getType() {
return type;
}
/**
* Gets the type of this type's provider.
*/
@SuppressWarnings("unchecked")
TypeLiteral<Provider<T>> providerType() {
final Type[] typeParameters = new Type[] { getType() };
// This cast is safe and wouldn't generate a warning if Type had a type
// parameter.
return (TypeLiteral<Provider<T>>) get(new ParameterizedType() {
public Type[] getActualTypeArguments() {
return typeParameters;
}
public Type getRawType() {
return Provider.class;
}
public Type getOwnerType() {
return null;
}
});
}
public int hashCode() {
return this.hashCode;
}
public boolean equals(Object o) {
if (o == this) {
return true;
}
if (!(o instanceof TypeLiteral<?>)) {
return false;
}
TypeLiteral<?> other = (TypeLiteral<?>) o;
return equals(type, other.type);
}
public String toString() {
return type instanceof Class<?>
? ((Class<?>) type).getName()
: type.toString();
}
static AssertionError unexpectedType(Type type, Class<?> expected) {
return new AssertionError(
"Unexpected type. Expected: " + expected.getName()
+ ", got: " + type.getClass().getName()
+ ", for type literal: " + type.toString() + ".");
}
/**
* Gets type literal for the given {@code Type} instance.
*/
public static TypeLiteral<?> get(Type type) {
return new SimpleTypeLiteral<Object>(type);
}
/**
* Gets type literal for the given {@code Class} instance.
*/
public static <T> TypeLiteral<T> get(Class<T> type) {
return new SimpleTypeLiteral<T>(type);
}
private static class SimpleTypeLiteral<T> extends TypeLiteral<T> {
public SimpleTypeLiteral(Type type) {
super(type);
}
}
static int hashCode(Type type) {
if (type instanceof ParameterizedType) {
ParameterizedType p = (ParameterizedType) type;
int h = p.getRawType().hashCode();
for (Type argument : p.getActualTypeArguments()) {
h = h * 31 + hashCode(argument);
}
return h;
}
if (type instanceof Class) {
// Class specifies hashCode().
return type.hashCode();
}
if (type instanceof GenericArrayType) {
return hashCode(((GenericArrayType) type).getGenericComponentType()) * 31;
}
// This isn't a type we support. Could be a generic array type, wildcard
// type, etc.
return type.hashCode();
}
static boolean equals(Type a, Type b) {
if (a instanceof Class) {
// Class already specifies equals().
return a.equals(b);
}
if (a instanceof ParameterizedType) {
if (!(b instanceof ParameterizedType)) {
return false;
}
ParameterizedType pa = (ParameterizedType) a;
ParameterizedType pb = (ParameterizedType) b;
if (!pa.getRawType().equals(pb.getRawType())) {
return false;
}
Type[] aa = pa.getActualTypeArguments();
Type[] ba = pb.getActualTypeArguments();
if (aa.length != ba.length) {
return false;
}
for (int i = 0; i < aa.length; i++) {
if (!equals(aa[i], ba[i])) {
return false;
}
}
return true;
}
if (a instanceof GenericArrayType) {
if (!(b instanceof GenericArrayType)) {
return false;
}
return equals(
((GenericArrayType) a).getGenericComponentType(),
((GenericArrayType) b).getGenericComponentType()
);
}
// This isn't a type we support. Could be a generic array type, wildcard
// type, etc.
return false;
}
}