diff --git a/gson/src/main/java/com/google/gson/DefaultTypeAdapters.java b/gson/src/main/java/com/google/gson/DefaultTypeAdapters.java index 77e3e7c4..4e920f63 100644 --- a/gson/src/main/java/com/google/gson/DefaultTypeAdapters.java +++ b/gson/src/main/java/com/google/gson/DefaultTypeAdapters.java @@ -518,7 +518,8 @@ final class DefaultTypeAdapters { JsonArray array = new JsonArray(); Type childGenericType = null; if (typeOfSrc instanceof ParameterizedType) { - childGenericType = TypeToken.get(typeOfSrc).getCollectionElementType(); + Class rawTypeOfSrc = Types.getRawType(typeOfSrc); + childGenericType = Types.getCollectionElementType(typeOfSrc, rawTypeOfSrc); } for (Object child : src) { if (child == null) { @@ -541,7 +542,7 @@ final class DefaultTypeAdapters { // Use ObjectConstructor to create instance instead of hard-coding a specific type. // This handles cases where users are using their own subclass of Collection. Collection collection = constructCollectionType(typeOfT, context); - Type childType = TypeToken.get(typeOfT).getCollectionElementType(); + Type childType = Types.getCollectionElementType(typeOfT, Types.getRawType(typeOfT)); for (JsonElement childElement : json.getAsJsonArray()) { if (childElement == null || childElement.isJsonNull()) { collection.add(null); @@ -579,7 +580,8 @@ final class DefaultTypeAdapters { JsonObject map = new JsonObject(); Type childGenericType = null; if (typeOfSrc instanceof ParameterizedType) { - childGenericType = TypeToken.get(typeOfSrc).getMapKeyAndValueTypes()[1]; + Class rawTypeOfSrc = Types.getRawType(typeOfSrc); + childGenericType = Types.getMapKeyAndValueTypes(typeOfSrc, rawTypeOfSrc)[1]; } for (Map.Entry entry : (Set) src.entrySet()) { @@ -603,7 +605,7 @@ final class DefaultTypeAdapters { // Use ObjectConstructor to create instance instead of hard-coding a specific type. // This handles cases where users are using their own subclass of Map. Map map = constructMapType(typeOfT, context); - Type[] keyAndValueTypes = TypeToken.get(typeOfT).getMapKeyAndValueTypes(); + Type[] keyAndValueTypes = Types.getMapKeyAndValueTypes(typeOfT, Types.getRawType(typeOfT)); for (Map.Entry entry : json.getAsJsonObject().entrySet()) { Object key = context.deserialize(new JsonPrimitive(entry.getKey()), keyAndValueTypes[0]); Object value = context.deserialize(entry.getValue(), keyAndValueTypes[1]); diff --git a/gson/src/main/java/com/google/gson/JsonArrayDeserializationVisitor.java b/gson/src/main/java/com/google/gson/JsonArrayDeserializationVisitor.java index 3f50d944..e1b1a258 100644 --- a/gson/src/main/java/com/google/gson/JsonArrayDeserializationVisitor.java +++ b/gson/src/main/java/com/google/gson/JsonArrayDeserializationVisitor.java @@ -16,7 +16,6 @@ package com.google.gson; -import com.google.gson.reflect.TypeToken; import java.lang.reflect.Array; import java.lang.reflect.Type; @@ -39,21 +38,18 @@ final class JsonArrayDeserializationVisitor extends JsonDeserializationVisito @Override @SuppressWarnings("unchecked") protected T constructTarget() { - - TypeToken typeToken = TypeToken.get(targetType); - if (!json.isJsonArray()) { throw new JsonParseException("Expecting array found: " + json); } JsonArray jsonArray = json.getAsJsonArray(); - if (typeToken.isArray()) { + if (Types.isArray(targetType)) { // We know that we are getting back an array of the required type, so // this typecasting is safe. - return (T) objectConstructor.constructArray(typeToken.getArrayComponentType(), + return (T) objectConstructor.constructArray(Types.getArrayComponentType(targetType), jsonArray.size()); } // is a collection - return (T) objectConstructor.construct(typeToken.getRawType()); + return (T) objectConstructor.construct(Types.getRawType(targetType)); } public void visitArray(Object array, Type arrayType) { @@ -61,7 +57,6 @@ final class JsonArrayDeserializationVisitor extends JsonDeserializationVisito throw new JsonParseException("Expecting array found: " + json); } JsonArray jsonArray = json.getAsJsonArray(); - TypeToken typeToken = TypeToken.get(arrayType); for (int i = 0; i < jsonArray.size(); i++) { JsonElement jsonChild = jsonArray.get(i); Object child; @@ -69,11 +64,12 @@ final class JsonArrayDeserializationVisitor extends JsonDeserializationVisito if (jsonChild == null || jsonChild.isJsonNull()) { child = null; } else if (jsonChild instanceof JsonObject) { - child = visitChildAsObject(typeToken.getArrayComponentType(), jsonChild); + child = visitChildAsObject(Types.getArrayComponentType(arrayType), jsonChild); } else if (jsonChild instanceof JsonArray) { - child = visitChildAsArray(typeToken.getArrayComponentType(), jsonChild.getAsJsonArray()); + child = visitChildAsArray(Types.getArrayComponentType(arrayType), + jsonChild.getAsJsonArray()); } else if (jsonChild instanceof JsonPrimitive) { - child = visitChildAsObject(typeToken.getArrayComponentType(), + child = visitChildAsObject(Types.getArrayComponentType(arrayType), jsonChild.getAsJsonPrimitive()); } else { throw new IllegalStateException(); diff --git a/gson/src/main/java/com/google/gson/JsonObjectDeserializationVisitor.java b/gson/src/main/java/com/google/gson/JsonObjectDeserializationVisitor.java index 25ba88e9..7b267ec9 100644 --- a/gson/src/main/java/com/google/gson/JsonObjectDeserializationVisitor.java +++ b/gson/src/main/java/com/google/gson/JsonObjectDeserializationVisitor.java @@ -16,7 +16,6 @@ package com.google.gson; -import com.google.gson.reflect.TypeToken; import java.lang.reflect.Type; /** @@ -100,7 +99,7 @@ final class JsonObjectDeserializationVisitor extends JsonDeserializationVisit throw new JsonParseException("Expecting object found: " + json); } JsonElement child = json.getAsJsonObject().get(fName); - boolean isPrimitive = TypeToken.get(declaredTypeOfField).isPrimitive(); + boolean isPrimitive = Primitives.isPrimitive(declaredTypeOfField); if (child == null) { // Child will be null if the field wasn't present in Json return true; } else if (child.isJsonNull()) { diff --git a/gson/src/main/java/com/google/gson/JsonSerializationVisitor.java b/gson/src/main/java/com/google/gson/JsonSerializationVisitor.java index f24e3429..0892c576 100644 --- a/gson/src/main/java/com/google/gson/JsonSerializationVisitor.java +++ b/gson/src/main/java/com/google/gson/JsonSerializationVisitor.java @@ -72,7 +72,7 @@ final class JsonSerializationVisitor implements ObjectNavigator.Visitor { public void visitArray(Object array, Type arrayType) { assignToRoot(new JsonArray()); int length = Array.getLength(array); - Type componentType = TypeToken.get(arrayType).getArrayComponentType(); + Type componentType = Types.getArrayComponentType(arrayType); for (int i = 0; i < length; ++i) { Object child = Array.get(array, i); // we should not get more specific component type yet since it is possible diff --git a/gson/src/main/java/com/google/gson/MappedObjectConstructor.java b/gson/src/main/java/com/google/gson/MappedObjectConstructor.java index 0eb57b4c..44cd6cab 100644 --- a/gson/src/main/java/com/google/gson/MappedObjectConstructor.java +++ b/gson/src/main/java/com/google/gson/MappedObjectConstructor.java @@ -55,7 +55,7 @@ final class MappedObjectConstructor implements ObjectConstructor { } public Object constructArray(Type type, int length) { - return Array.newInstance(TypeToken.get(type).getRawType(), length); + return Array.newInstance(Types.getRawType(type), length); } private T constructWithNoArgConstructor(Type typeOfT) { @@ -80,7 +80,7 @@ final class MappedObjectConstructor implements ObjectConstructor { @SuppressWarnings({"unchecked", "cast"}) private Constructor getNoArgsConstructor(Type typeOfT) { - Class clazz = TypeToken.get(typeOfT).getRawType(); + Class clazz = Types.getRawType(typeOfT); Constructor[] declaredConstructors = (Constructor[]) clazz.getDeclaredConstructors(); AccessibleObject.setAccessible(declaredConstructors, true); for (Constructor constructor : declaredConstructors) { diff --git a/gson/src/main/java/com/google/gson/ObjectNavigator.java b/gson/src/main/java/com/google/gson/ObjectNavigator.java index 19c0e180..9fe62f3f 100644 --- a/gson/src/main/java/com/google/gson/ObjectNavigator.java +++ b/gson/src/main/java/com/google/gson/ObjectNavigator.java @@ -19,7 +19,11 @@ package com.google.gson; import com.google.gson.reflect.TypeToken; import java.lang.reflect.AccessibleObject; import java.lang.reflect.Field; +import java.lang.reflect.ParameterizedType; import java.lang.reflect.Type; +import java.util.Collection; +import java.util.Map; +import java.util.Properties; /** * Provides ability to apply a visitor to an object and all of its fields @@ -100,8 +104,7 @@ final class ObjectNavigator { * does not get visited. */ public void accept(Visitor visitor) { - TypeToken objTypeInfo = TypeToken.get(objTypePair.type); - if (exclusionStrategy.shouldSkipClass(objTypeInfo.getRawType())) { + if (exclusionStrategy.shouldSkipClass(Types.getRawType(objTypePair.type))) { return; } boolean visitedWithCustomHandler = visitor.visitUsingCustomHandler(objTypePair); @@ -114,18 +117,17 @@ final class ObjectNavigator { objTypePair.setObject(objectToVisit); visitor.start(objTypePair); try { - if (objTypeInfo.isArray()) { + if (Types.isArray(objTypePair.type)) { visitor.visitArray(objectToVisit, objTypePair.type); - } else if (objTypeInfo.getType() == Object.class - && isPrimitiveOrString(objectToVisit)) { + } else if (objTypePair.type == Object.class && isPrimitiveOrString(objectToVisit)) { // TODO(Joel): this is only used for deserialization of "primitives" // we should rethink this!!! visitor.visitPrimitive(objectToVisit); - objectToVisit = visitor.getTarget(); + visitor.getTarget(); } else { visitor.startVisitingObject(objectToVisit); ObjectTypePair currObjTypePair = objTypePair.toMoreSpecificType(); - Class topLevelClass = TypeToken.get(currObjTypePair.type).getRawType(); + Class topLevelClass = Types.getRawType(currObjTypePair.type); for (Class curr = topLevelClass; curr != null && !curr.equals(Object.class); curr = curr.getSuperclass()) { if (!curr.isSynthetic()) { @@ -154,12 +156,11 @@ final class ObjectNavigator { || exclusionStrategy.shouldSkipClass(fieldAttributes.getDeclaredClass())) { continue; // skip } - TypeToken fieldTypeToken = getTypeInfoForField(f, objTypePair.type); - Type declaredTypeOfField = fieldTypeToken.getType(); + Type declaredTypeOfField = getTypeInfoForField(f, objTypePair.type); boolean visitedWithCustomHandler = - visitor.visitFieldUsingCustomHandler(fieldAttributes, declaredTypeOfField, obj); + visitor.visitFieldUsingCustomHandler(fieldAttributes, declaredTypeOfField, obj); if (!visitedWithCustomHandler) { - if (fieldTypeToken.isArray()) { + if (Types.isArray(declaredTypeOfField)) { visitor.visitArrayField(fieldAttributes, declaredTypeOfField, obj); } else { visitor.visitObjectField(fieldAttributes, declaredTypeOfField, obj); @@ -177,12 +178,12 @@ final class ObjectNavigator { * @param typeDefiningF the type that contains the field {@code f} * @return the type information for the field */ - public static TypeToken getTypeInfoForField(Field f, Type typeDefiningF) { - TypeToken typeToken = TypeToken.get(typeDefiningF); - if (!f.getDeclaringClass().isAssignableFrom(typeToken.getRawType())) { + public static Type getTypeInfoForField(Field f, Type typeDefiningF) { + Class rawType = Types.getRawType(typeDefiningF); + if (!f.getDeclaringClass().isAssignableFrom(rawType)) { // this field is unrelated to the type; the user probably omitted type information - return TypeToken.get(f.getGenericType()); + return f.getGenericType(); } - return typeToken.getFieldType(f); + return Types.resolve(typeDefiningF, rawType, f.getGenericType()); } } diff --git a/gson/src/main/java/com/google/gson/ParameterizedTypeHandlerMap.java b/gson/src/main/java/com/google/gson/ParameterizedTypeHandlerMap.java index 06c95e9a..c5936a4f 100644 --- a/gson/src/main/java/com/google/gson/ParameterizedTypeHandlerMap.java +++ b/gson/src/main/java/com/google/gson/ParameterizedTypeHandlerMap.java @@ -122,7 +122,7 @@ final class ParameterizedTypeHandlerMap { public synchronized T getHandlerFor(Type type) { T handler = map.get(type); if (handler == null) { - Class rawClass = TypeToken.get(type).getRawType(); + Class rawClass = Types.getRawType(type); if (rawClass != type) { handler = getHandlerFor(rawClass); } @@ -196,6 +196,6 @@ final class ParameterizedTypeHandlerMap { } private String typeToString(Type type) { - return TypeToken.get(type).getRawType().getSimpleName(); + return Types.getRawType(type).getSimpleName(); } } diff --git a/gson/src/main/java/com/google/gson/Primitives.java b/gson/src/main/java/com/google/gson/Primitives.java index 13629e0e..66f065f5 100644 --- a/gson/src/main/java/com/google/gson/Primitives.java +++ b/gson/src/main/java/com/google/gson/Primitives.java @@ -16,6 +16,7 @@ package com.google.gson; +import java.lang.reflect.Type; import java.util.Collections; import java.util.HashMap; import java.util.Map; @@ -61,6 +62,13 @@ final class Primitives { backward.put(value, key); } + /** + * Returns true if this type is a primitive. + */ + public static boolean isPrimitive(Type type) { + return PRIMITIVE_TO_WRAPPER_TYPE.containsKey(type); + } + /** * Returns {@code true} if {@code type} is one of the nine * primitive-wrapper types, such as {@link Integer}. diff --git a/gson/src/main/java/com/google/gson/reflect/Types.java b/gson/src/main/java/com/google/gson/Types.java similarity index 70% rename from gson/src/main/java/com/google/gson/reflect/Types.java rename to gson/src/main/java/com/google/gson/Types.java index 272f4dac..f0380418 100644 --- a/gson/src/main/java/com/google/gson/reflect/Types.java +++ b/gson/src/main/java/com/google/gson/Types.java @@ -14,10 +14,8 @@ * limitations under the License. */ -package com.google.gson.reflect; +package com.google.gson; -import static com.google.gson.reflect.TypeToken.checkArgument; -import static com.google.gson.reflect.TypeToken.checkNotNull; import java.io.Serializable; import java.lang.reflect.Array; import java.lang.reflect.GenericArrayType; @@ -27,10 +25,10 @@ import java.lang.reflect.Type; import java.lang.reflect.TypeVariable; import java.lang.reflect.WildcardType; import java.util.Arrays; -import java.util.List; +import java.util.Collection; import java.util.Map; import java.util.NoSuchElementException; -import java.util.Set; +import java.util.Properties; /** * Static methods for working with types. @@ -43,16 +41,6 @@ public final class Types { private Types() {} - /** - * Returns a new parameterized type, applying {@code typeArguments} to - * {@code rawType}. The returned type does not have an owner type. - * - * @return a {@link java.io.Serializable serializable} parameterized type. - */ - public static ParameterizedType newParameterizedType(Type rawType, Type... typeArguments) { - return newParameterizedTypeWithOwner(null, rawType, typeArguments); - } - /** * Returns a new parameterized type, applying {@code typeArguments} to * {@code rawType} and enclosed by {@code ownerType}. @@ -93,42 +81,12 @@ public final class Types { return new WildcardTypeImpl(new Type[] { Object.class }, new Type[] { bound }); } - /** - * Returns a type modelling a {@link List} whose elements are of type - * {@code elementType}. - * - * @return a {@link java.io.Serializable serializable} parameterized type. - */ - public static ParameterizedType listOf(Type elementType) { - return newParameterizedType(List.class, elementType); - } - - /** - * Returns a type modelling a {@link Set} whose elements are of type - * {@code elementType}. - * - * @return a {@link java.io.Serializable serializable} parameterized type. - */ - public static ParameterizedType setOf(Type elementType) { - return newParameterizedType(Set.class, elementType); - } - - /** - * Returns a type modelling a {@link Map} whose keys are of type - * {@code keyType} and whose values are of type {@code valueType}. - * - * @return a {@link java.io.Serializable serializable} parameterized type. - */ - public static ParameterizedType mapOf(Type keyType, Type valueType) { - return newParameterizedType(Map.class, keyType, valueType); - } - /** * Returns a type that is functionally equal but not necessarily equal * according to {@link Object#equals(Object) Object.equals()}. The returned * type is {@link java.io.Serializable}. */ - static Type canonicalize(Type type) { + public static Type canonicalize(Type type) { if (type instanceof Class) { Class c = (Class) type; return c.isArray() ? new GenericArrayTypeImpl(canonicalize(c.getComponentType())) : c; @@ -152,7 +110,7 @@ public final class Types { } } - static Class getRawType(Type type) { + public static Class getRawType(Type type) { if (type instanceof Class) { // type is a normal class. return (Class) type; @@ -180,8 +138,9 @@ public final class Types { return getRawType(((WildcardType) type).getUpperBounds()[0]); } else { + String className = type == null ? "null" : type.getClass().getName(); throw new IllegalArgumentException("Expected a Class, ParameterizedType, or " - + "GenericArrayType, but <" + type + "> is of type " + type.getClass().getName()); + + "GenericArrayType, but <" + type + "> is of type " + className); } } @@ -192,7 +151,7 @@ public final class Types { /** * Returns true if {@code a} and {@code b} are equal. */ - static boolean equals(Type a, Type b) { + public static boolean equals(Type a, Type b) { if (a == b) { // also handles (a == null && b == null) return true; @@ -251,7 +210,7 @@ public final class Types { return o != null ? o.hashCode() : 0; } - static String typeToString(Type type) { + public static String typeToString(Type type) { return type instanceof Class ? ((Class) type).getName() : type.toString(); } @@ -260,9 +219,9 @@ public final class Types { * IntegerSet}, the result for when supertype is {@code Set.class} is {@code Set} and the * result when the supertype is {@code Collection.class} is {@code Collection}. */ - static Type getGenericSupertype(Type type, Class rawType, Class toResolve) { + static Type getGenericSupertype(Type context, Class rawType, Class toResolve) { if (toResolve == rawType) { - return type; + return context; } // we skip searching through interfaces if unknown is an interface @@ -294,7 +253,138 @@ public final class Types { return toResolve; } - static Type resolveTypeVariable(Type type, Class rawType, TypeVariable unknown) { + /** + * Returns the generic form of {@code supertype}. For example, if this is {@code + * ArrayList}, this returns {@code Iterable} given the input {@code + * Iterable.class}. + * + * @param supertype a superclass of, or interface implemented by, this. + */ + static Type getSupertype(Type context, Class contextRawType, Class supertype) { + checkArgument(supertype.isAssignableFrom(contextRawType)); + return resolve(context, contextRawType, + Types.getGenericSupertype(context, contextRawType, supertype)); + } + + /** + * Returns true if this type is an array. + */ + static boolean isArray(Type type) { + return type instanceof GenericArrayType + || (type instanceof Class && ((Class) type).isArray()); + } + + /** + * Returns the component type of this array type. + * @throws ClassCastException if this type is not an array. + */ + static Type getArrayComponentType(Type array) { + return array instanceof GenericArrayType + ? ((GenericArrayType) array).getGenericComponentType() + : ((Class) array).getComponentType(); + } + + /** + * Returns the element type of this collection type. + * @throws IllegalArgumentException if this type is not a collection. + */ + static Type getCollectionElementType(Type context, Class contextRawType) { + Type collectionType = getSupertype(context, contextRawType, Collection.class); + return ((ParameterizedType) collectionType).getActualTypeArguments()[0]; + } + + /** + * Returns a two element array containing this map's key and value types in + * positions 0 and 1 respectively. + */ + static Type[] getMapKeyAndValueTypes(Type context, Class contextRawType) { + /* + * Work around a problem with the declaration of java.util.Properties. That + * class should extend Hashtable, but it's declared to + * extend Hashtable. + */ + if (context == Properties.class) { + return new Type[] { String.class, String.class }; // TODO: test subclasses of Properties! + } + + Type mapType = getSupertype(context, contextRawType, Map.class); + ParameterizedType mapParameterizedType = (ParameterizedType) mapType; + return mapParameterizedType.getActualTypeArguments(); + } + + static Type resolve(Type context, Class contextRawType, Type toResolve) { + // this implementation is made a little more complicated in an attempt to avoid object-creation + while (true) { + if (toResolve instanceof TypeVariable) { + TypeVariable typeVariable = (TypeVariable) toResolve; + toResolve = resolveTypeVariable(context, contextRawType, typeVariable); + if (toResolve == typeVariable) { + return toResolve; + } + + } else if (toResolve instanceof Class && ((Class) toResolve).isArray()) { + Class original = (Class) toResolve; + Type componentType = original.getComponentType(); + Type newComponentType = resolve(context, contextRawType, componentType); + return componentType == newComponentType + ? original + : arrayOf(newComponentType); + + } else if (toResolve instanceof GenericArrayType) { + GenericArrayType original = (GenericArrayType) toResolve; + Type componentType = original.getGenericComponentType(); + Type newComponentType = resolve(context, contextRawType, componentType); + return componentType == newComponentType + ? original + : arrayOf(newComponentType); + + } else if (toResolve instanceof ParameterizedType) { + ParameterizedType original = (ParameterizedType) toResolve; + Type ownerType = original.getOwnerType(); + Type newOwnerType = resolve(context, contextRawType, ownerType); + boolean changed = newOwnerType != ownerType; + + Type[] args = original.getActualTypeArguments(); + for (int t = 0, length = args.length; t < length; t++) { + Type resolvedTypeArgument = resolve(context, contextRawType, args[t]); + if (resolvedTypeArgument != args[t]) { + if (!changed) { + args = args.clone(); + changed = true; + } + args[t] = resolvedTypeArgument; + } + } + + return changed + ? 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 = resolve(context, contextRawType, originalLowerBound[0]); + if (lowerBound != originalLowerBound[0]) { + return supertypeOf(lowerBound); + } + } else if (originalUpperBound.length == 1) { + Type upperBound = resolve(context, contextRawType, originalUpperBound[0]); + if (upperBound != originalUpperBound[0]) { + return subtypeOf(upperBound); + } + } + return original; + + } else { + return toResolve; + } + } + } + + static Type resolveTypeVariable(Type context, Class contextRawType, TypeVariable unknown) { Class declaredByRaw = declaringClassOf(unknown); // we can't reduce this further @@ -302,7 +392,7 @@ public final class Types { return unknown; } - Type declaredBy = getGenericSupertype(type, rawType, declaredByRaw); + Type declaredBy = getGenericSupertype(context, contextRawType, declaredByRaw); if (declaredBy instanceof ParameterizedType) { int index = indexOf(declaredByRaw.getTypeParameters(), unknown); return ((ParameterizedType) declaredBy).getActualTypeArguments()[index]; @@ -485,4 +575,14 @@ public final class Types { private static final long serialVersionUID = 0; } + + private static void checkNotNull(Object obj) { + checkArgument(obj != null); + } + + private static void checkArgument(boolean condition) { + if (!condition) { + throw new IllegalArgumentException("condition failed: " + condition); + } + } } diff --git a/gson/src/main/java/com/google/gson/reflect/TypeToken.java b/gson/src/main/java/com/google/gson/reflect/TypeToken.java index 6ae3d490..555ccd0d 100644 --- a/gson/src/main/java/com/google/gson/reflect/TypeToken.java +++ b/gson/src/main/java/com/google/gson/reflect/TypeToken.java @@ -16,22 +16,14 @@ package com.google.gson.reflect; -import java.lang.reflect.Constructor; -import java.lang.reflect.Field; +import com.google.gson.Types; 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.Arrays; -import java.util.Collection; import java.util.HashMap; -import java.util.Hashtable; -import java.util.List; import java.util.Map; -import java.util.Properties; /** * Represents a generic type {@code T}. Java doesn't yet provide a way to @@ -110,13 +102,6 @@ public class TypeToken { return Types.canonicalize(parameterized.getActualTypeArguments()[0]); } - /** - * Gets type literal from super class's type parameter. - */ - static TypeToken fromSuperclassTypeParameter(Class subclass) { - return new TypeToken(getSuperclassTypeParameter(subclass)); - } - /** * Returns the raw (non-generic) type for this type. */ @@ -133,17 +118,24 @@ public class TypeToken { /** * Check if this type is assignable from the given class object. + * + * @deprecated this implementation may be inconsistent with javac for types + * with wildcards. */ + @Deprecated + @SuppressWarnings("deprecation") public boolean isAssignableFrom(Class cls) { return isAssignableFrom((Type) cls); } /** * Check if this type is assignable from the given Type. + * + * @deprecated this implementation may be inconsistent with javac for types + * with wildcards. */ + @Deprecated public boolean isAssignableFrom(Type from) { - // TODO: resolve from first, then do something lightweight? - if (from == null) { return false; } @@ -168,7 +160,12 @@ public class TypeToken { /** * Check if this type is assignable from the given type token. + * + * @deprecated this implementation may be inconsistent with javac for types + * with wildcards. */ + @Deprecated + @SuppressWarnings("deprecation") public boolean isAssignableFrom(TypeToken token) { return isAssignableFrom(token.getType()); } @@ -248,11 +245,7 @@ public class TypeToken { // Interfaces didn't work, try the superclass. Type sType = clazz.getGenericSuperclass(); - if (isAssignableFrom(sType, to, new HashMap(typeVarMap))) { - return true; - } - - return false; + return isAssignableFrom(sType, to, new HashMap(typeVarMap)); } /** @@ -293,15 +286,11 @@ public class TypeToken { * Checks if two types are the same or are equivalent under a variable mapping * given in the type map that was provided. */ - private static boolean matches(Type from, Type to, - Map typeMap) { - if (to.equals(from)) return true; + private static boolean matches(Type from, Type to, Map typeMap) { + return to.equals(from) + || (from instanceof TypeVariable + && to.equals(typeMap.get(((TypeVariable) from).getName()))); - if (from instanceof TypeVariable) { - return to.equals(typeMap.get(((TypeVariable)from).getName())); - } - - return false; } @Override public final int hashCode() { @@ -331,171 +320,6 @@ public class TypeToken { return new TypeToken(type); } - - /** Returns an immutable list of the resolved types. */ - private List> resolveAll(Type[] types) { - TypeToken[] result = new TypeToken[types.length]; - for (int t = 0; t < types.length; t++) { - result[t] = resolve(types[t]); - } - return Arrays.asList(result); - } - - /** - * Resolves known type parameters in {@code toResolve} and returns the result. - */ - TypeToken resolve(Type toResolve) { - return TypeToken.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 = Types.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}, this returns {@code Iterable} given the input {@code - * Iterable.class}. - * - * @param supertype a superclass of, or interface implemented by, this. - */ - public TypeToken getSupertype(Class supertype) { - checkArgument(supertype.isAssignableFrom(rawType)); - return resolve(Types.getGenericSupertype(type, rawType, supertype)); - } - - /** - * Returns the resolved generic type of {@code field}. - * - * @param field a field defined by this or any superclass. - */ - public TypeToken getFieldType(Field field) { - if (!field.getDeclaringClass().isAssignableFrom(rawType)) { - throw new IllegalArgumentException(rawType.getName() + " does not declare field " + field); - } - 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. - */ - public List> getParameterTypes(Member methodOrConstructor) { - Type[] genericParameterTypes; - - if (methodOrConstructor instanceof Method) { - Method method = (Method) methodOrConstructor; - checkArgument(method.getDeclaringClass().isAssignableFrom(rawType)); - genericParameterTypes = method.getGenericParameterTypes(); - - } else if (methodOrConstructor instanceof Constructor) { - Constructor constructor = (Constructor) methodOrConstructor; - checkArgument(constructor.getDeclaringClass().isAssignableFrom(rawType)); - 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. - */ - public List> getExceptionTypes(Member methodOrConstructor) { - Type[] genericExceptionTypes; - - if (methodOrConstructor instanceof Method) { - Method method = (Method) methodOrConstructor; - checkArgument(method.getDeclaringClass().isAssignableFrom(rawType)); - genericExceptionTypes = method.getGenericExceptionTypes(); - - } else if (methodOrConstructor instanceof Constructor) { - Constructor constructor = (Constructor) methodOrConstructor; - checkArgument(constructor.getDeclaringClass().isAssignableFrom(rawType)); - 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. - */ - public TypeToken getReturnType(Method method) { - checkArgument(method.getDeclaringClass().isAssignableFrom(rawType)); - return resolve(method.getGenericReturnType()); - } - static void checkNotNull(Object obj) { checkArgument(obj != null); } @@ -505,64 +329,4 @@ public class TypeToken { throw new IllegalArgumentException("condition failed: " + condition); } } - - // TODO: these methods are required by GSON but don't need to be public. Remove? - - /** - * Returns true if this type is an array. - */ - public boolean isArray() { - return type instanceof GenericArrayType; - } - - /** - * Returns true if this type is a primitive. - */ - public boolean isPrimitive() { - return type == boolean.class - || type == byte.class - || type == char.class - || type == double.class - || type == float.class - || type == int.class - || type == long.class - || type == short.class - || type == void.class; - } - - /** - * Returns the component type of this array type. - * @throws ClassCastException if this type is not an array. - */ - public Type getArrayComponentType() { - return ((GenericArrayType) type).getGenericComponentType(); - } - - /** - * Returns the element type of this collection type. - * @throws IllegalArgumentException if this type is not a collection. - */ - public Type getCollectionElementType() { - TypeToken collectionType = getSupertype(Collection.class); - return ((ParameterizedType) collectionType.getType()).getActualTypeArguments()[0]; - } - - /** - * Returns a two element array containing this map's key and value types in - * positions 0 and 1 respectively. - */ - public Type[] getMapKeyAndValueTypes() { - /* - * Work around a problem with the declaration of java.util.Properties. That - * class should extend Hashtable, but it's declared to - * extend Hashtable. - */ - if (type == Properties.class) { - return new Type[] { String.class, String.class }; // TODO: test subclasses of Properties! - } - - TypeToken mapTypeToken = TypeToken.get(type).getSupertype(Map.class); - ParameterizedType mapParameterizedType = (ParameterizedType) mapTypeToken.getType(); - return mapParameterizedType.getActualTypeArguments(); - } } diff --git a/gson/src/test/java/com/google/gson/ParamterizedTypeFixtures.java b/gson/src/test/java/com/google/gson/ParamterizedTypeFixtures.java index 116ad199..4dc07d40 100644 --- a/gson/src/test/java/com/google/gson/ParamterizedTypeFixtures.java +++ b/gson/src/test/java/com/google/gson/ParamterizedTypeFixtures.java @@ -16,7 +16,6 @@ package com.google.gson; -import com.google.gson.reflect.TypeToken; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.lang.reflect.ParameterizedType; @@ -151,12 +150,12 @@ public class ParamterizedTypeFixtures { public MyParameterizedType deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException { Type genericClass = ((ParameterizedType) typeOfT).getActualTypeArguments()[0]; - TypeToken typeToken = TypeToken.get(genericClass); - String className = typeToken.getRawType().getSimpleName(); + Class rawType = Types.getRawType(genericClass); + String className = rawType.getSimpleName(); T value = (T) json.getAsJsonObject().get(className).getAsObject(); - if (typeToken.isPrimitive()) { + if (Primitives.isPrimitive(genericClass)) { PrimitiveTypeAdapter typeAdapter = new PrimitiveTypeAdapter(); - value = (T) typeAdapter.adaptType(value, typeToken.getRawType()); + value = (T) typeAdapter.adaptType(value, rawType); } return new MyParameterizedType(value); } diff --git a/gson/src/test/java/com/google/gson/reflect/TypeTokenTest.java b/gson/src/test/java/com/google/gson/reflect/TypeTokenTest.java new file mode 100644 index 00000000..a76d5612 --- /dev/null +++ b/gson/src/test/java/com/google/gson/reflect/TypeTokenTest.java @@ -0,0 +1,81 @@ +/* + * Copyright (C) 2010 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.gson.reflect; + +import java.lang.reflect.Type; +import java.util.ArrayList; +import java.util.List; +import java.util.RandomAccess; +import java.util.Set; +import junit.framework.TestCase; + +/** + * @author Jesse Wilson + */ +@SuppressWarnings({"UnusedDeclaration", "deprecation"}) +public final class TypeTokenTest extends TestCase { + + List listOfInteger = null; + List listOfNumber = null; + List listOfString = null; + List listOfUnknown = null; + List> listOfSetOfString = null; + List> listOfSetOfUnknown = null; + + public void testIsAssignableFromRawTypes() { + assertTrue(TypeToken.get(Object.class).isAssignableFrom(String.class)); + assertFalse(TypeToken.get(String.class).isAssignableFrom(Object.class)); + assertTrue(TypeToken.get(RandomAccess.class).isAssignableFrom(ArrayList.class)); + assertFalse(TypeToken.get(ArrayList.class).isAssignableFrom(RandomAccess.class)); + } + + public void testIsAssignableFromWithTypeParameters() throws Exception { + Type a = getClass().getDeclaredField("listOfInteger").getGenericType(); + Type b = getClass().getDeclaredField("listOfNumber").getGenericType(); + assertTrue(TypeToken.get(a).isAssignableFrom(a)); + assertTrue(TypeToken.get(b).isAssignableFrom(b)); + + // listOfInteger = listOfNumber; // doesn't compile; must be false + assertFalse(TypeToken.get(a).isAssignableFrom(b)); + // listOfNumber = listOfInteger; // doesn't compile; must be false + assertFalse(TypeToken.get(b).isAssignableFrom(a)); + } + + public void testIsAssignableFromWithBasicWildcards() throws Exception { + Type a = getClass().getDeclaredField("listOfString").getGenericType(); + Type b = getClass().getDeclaredField("listOfUnknown").getGenericType(); + assertTrue(TypeToken.get(a).isAssignableFrom(a)); + assertTrue(TypeToken.get(b).isAssignableFrom(b)); + + // listOfString = listOfUnknown // doesn't compile; must be false + assertFalse(TypeToken.get(a).isAssignableFrom(b)); + listOfUnknown = listOfString; // compiles; must be true + assertTrue(TypeToken.get(b).isAssignableFrom(a)); + } + + public void testIsAssignableFromWithNestedWildcards() throws Exception { + Type a = getClass().getDeclaredField("listOfSetOfString").getGenericType(); + Type b = getClass().getDeclaredField("listOfSetOfUnknown").getGenericType(); + assertTrue(TypeToken.get(a).isAssignableFrom(a)); + assertTrue(TypeToken.get(b).isAssignableFrom(b)); + + // listOfSetOfString = listOfSetOfUnknown; // doesn't compile; must be false + assertFalse(TypeToken.get(a).isAssignableFrom(b)); + // listOfSetOfUnknown = listOfSetOfString; // doesn't compile; must be false + assertFalse(TypeToken.get(b).isAssignableFrom(a)); + } +}