/** * 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.gson.internal; import static com.google.gson.internal.$Gson$Preconditions.checkArgument; import static java.util.Objects.requireNonNull; import java.io.Serializable; import java.lang.reflect.Array; import java.lang.reflect.GenericArrayType; import java.lang.reflect.GenericDeclaration; import java.lang.reflect.Modifier; 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.Map; import java.util.NoSuchElementException; import java.util.Properties; import java.util.Objects; /** * Static methods for working with types. * * @author Bob Lee * @author Jesse Wilson */ public final class $Gson$Types { static final Type[] EMPTY_TYPE_ARRAY = new Type[] {}; private $Gson$Types() { throw new UnsupportedOperationException(); } /** * Returns a new parameterized type, applying {@code typeArguments} to * {@code rawType} and enclosed by {@code ownerType}. * * @return a {@link java.io.Serializable serializable} parameterized type. */ public static ParameterizedType newParameterizedTypeWithOwner( Type ownerType, Type rawType, Type... typeArguments) { return new ParameterizedTypeImpl(ownerType, rawType, typeArguments); } /** * Returns an array type whose elements are all instances of * {@code componentType}. * * @return a {@link java.io.Serializable serializable} generic array type. */ public static GenericArrayType arrayOf(Type componentType) { return new GenericArrayTypeImpl(componentType); } /** * Returns a type that represents an unknown type that extends {@code bound}. * For example, if {@code bound} is {@code CharSequence.class}, this returns * {@code ? extends CharSequence}. If {@code bound} is {@code Object.class}, * this returns {@code ?}, which is shorthand for {@code ? extends Object}. */ public static WildcardType subtypeOf(Type bound) { Type[] upperBounds; if (bound instanceof WildcardType) { upperBounds = ((WildcardType) bound).getUpperBounds(); } else { upperBounds = new Type[] { bound }; } return new WildcardTypeImpl(upperBounds, EMPTY_TYPE_ARRAY); } /** * Returns a type that represents an unknown supertype of {@code bound}. For * example, if {@code bound} is {@code String.class}, this returns {@code ? * super String}. */ public static WildcardType supertypeOf(Type bound) { Type[] lowerBounds; if (bound instanceof WildcardType) { lowerBounds = ((WildcardType) bound).getLowerBounds(); } else { lowerBounds = new Type[] { bound }; } return new WildcardTypeImpl(new Type[] { Object.class }, lowerBounds); } /** * 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}. */ public static Type canonicalize(Type type) { if (type instanceof Class) { Class c = (Class) type; return c.isArray() ? new GenericArrayTypeImpl(canonicalize(c.getComponentType())) : c; } else if (type instanceof ParameterizedType) { ParameterizedType p = (ParameterizedType) type; return new ParameterizedTypeImpl(p.getOwnerType(), p.getRawType(), p.getActualTypeArguments()); } else if (type instanceof GenericArrayType) { GenericArrayType g = (GenericArrayType) type; return new GenericArrayTypeImpl(g.getGenericComponentType()); } else if (type instanceof WildcardType) { WildcardType w = (WildcardType) type; return new WildcardTypeImpl(w.getUpperBounds(), w.getLowerBounds()); } else { // type is either serializable as-is or unsupported return type; } } public 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(); checkArgument(rawType instanceof Class); return (Class) rawType; } else if (type instanceof GenericArrayType) { Type componentType = ((GenericArrayType)type).getGenericComponentType(); return Array.newInstance(getRawType(componentType), 0).getClass(); } else if (type instanceof TypeVariable) { // we could use the variable's bounds, but that won't work if there are multiple. // having a raw type that's more general than necessary is okay return Object.class; } else if (type instanceof WildcardType) { Type[] bounds = ((WildcardType) type).getUpperBounds(); // Currently the JLS only permits one bound for wildcards so using first bound is safe assert bounds.length == 1; return getRawType(bounds[0]); } else { String className = type == null ? "null" : type.getClass().getName(); throw new IllegalArgumentException("Expected a Class, ParameterizedType, or " + "GenericArrayType, but <" + type + "> is of type " + className); } } private static boolean equal(Object a, Object b) { return Objects.equals(a, b); } /** * Returns true if {@code a} and {@code b} are equal. */ public static boolean equals(Type a, Type b) { if (a == b) { // also handles (a == null && b == null) return true; } else if (a instanceof Class) { // Class already specifies equals(). return a.equals(b); } else if (a instanceof ParameterizedType) { if (!(b instanceof ParameterizedType)) { return false; } // TODO: save a .clone() call ParameterizedType pa = (ParameterizedType) a; ParameterizedType pb = (ParameterizedType) b; return equal(pa.getOwnerType(), pb.getOwnerType()) && pa.getRawType().equals(pb.getRawType()) && Arrays.equals(pa.getActualTypeArguments(), pb.getActualTypeArguments()); } else if (a instanceof GenericArrayType) { if (!(b instanceof GenericArrayType)) { return false; } GenericArrayType ga = (GenericArrayType) a; GenericArrayType gb = (GenericArrayType) b; return equals(ga.getGenericComponentType(), gb.getGenericComponentType()); } else if (a instanceof WildcardType) { if (!(b instanceof WildcardType)) { return false; } WildcardType wa = (WildcardType) a; WildcardType wb = (WildcardType) b; return Arrays.equals(wa.getUpperBounds(), wb.getUpperBounds()) && Arrays.equals(wa.getLowerBounds(), wb.getLowerBounds()); } else if (a instanceof TypeVariable) { if (!(b instanceof TypeVariable)) { return false; } TypeVariable va = (TypeVariable) a; TypeVariable vb = (TypeVariable) b; return va.getGenericDeclaration() == vb.getGenericDeclaration() && va.getName().equals(vb.getName()); } else { // This isn't a type we support. Could be a generic array type, wildcard type, etc. return false; } } public static String typeToString(Type type) { return type instanceof Class ? ((Class) type).getName() : type.toString(); } /** * Returns the generic supertype for {@code supertype}. For example, given a class {@code * 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}. */ private static Type getGenericSupertype(Type context, Class rawType, Class supertype) { if (supertype == rawType) { return context; } // we skip searching through interfaces if unknown is an interface if (supertype.isInterface()) { Class[] interfaces = rawType.getInterfaces(); for (int i = 0, length = interfaces.length; i < length; i++) { if (interfaces[i] == supertype) { return rawType.getGenericInterfaces()[i]; } else if (supertype.isAssignableFrom(interfaces[i])) { return getGenericSupertype(rawType.getGenericInterfaces()[i], interfaces[i], supertype); } } } // check our supertypes if (!rawType.isInterface()) { while (rawType != Object.class) { Class rawSupertype = rawType.getSuperclass(); if (rawSupertype == supertype) { return rawType.getGenericSuperclass(); } else if (supertype.isAssignableFrom(rawSupertype)) { return getGenericSupertype(rawType.getGenericSuperclass(), rawSupertype, supertype); } rawType = rawSupertype; } } // we can't resolve this further return supertype; } /** * 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. */ private static Type getSupertype(Type context, Class contextRawType, Class supertype) { if (context instanceof WildcardType) { // wildcards are useless for resolving supertypes. As the upper bound has the same raw type, use it instead Type[] bounds = ((WildcardType)context).getUpperBounds(); // Currently the JLS only permits one bound for wildcards so using first bound is safe assert bounds.length == 1; context = bounds[0]; } checkArgument(supertype.isAssignableFrom(contextRawType)); return resolve(context, contextRawType, $Gson$Types.getGenericSupertype(context, contextRawType, supertype)); } /** * Returns the component type of this array type. * @throws ClassCastException if this type is not an array. */ public 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. */ public static Type getCollectionElementType(Type context, Class contextRawType) { Type collectionType = getSupertype(context, contextRawType, Collection.class); if (collectionType instanceof ParameterizedType) { return ((ParameterizedType) collectionType).getActualTypeArguments()[0]; } return Object.class; } /** * Returns a two element array containing this map's key and value types in * positions 0 and 1 respectively. */ public 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); // TODO: strip wildcards? if (mapType instanceof ParameterizedType) { ParameterizedType mapParameterizedType = (ParameterizedType) mapType; return mapParameterizedType.getActualTypeArguments(); } return new Type[] { Object.class, Object.class }; } public static Type resolve(Type context, Class contextRawType, Type toResolve) { return resolve(context, contextRawType, toResolve, new HashMap, Type>()); } private static Type resolve(Type context, Class contextRawType, Type toResolve, Map, Type> visitedTypeVariables) { // this implementation is made a little more complicated in an attempt to avoid object-creation TypeVariable resolving = null; while (true) { if (toResolve instanceof TypeVariable) { TypeVariable typeVariable = (TypeVariable) toResolve; Type previouslyResolved = visitedTypeVariables.get(typeVariable); if (previouslyResolved != null) { // cannot reduce due to infinite recursion return (previouslyResolved == Void.TYPE) ? toResolve : previouslyResolved; } // Insert a placeholder to mark the fact that we are in the process of resolving this type visitedTypeVariables.put(typeVariable, Void.TYPE); if (resolving == null) { resolving = typeVariable; } toResolve = resolveTypeVariable(context, contextRawType, typeVariable); if (toResolve == typeVariable) { break; } } else if (toResolve instanceof Class && ((Class) toResolve).isArray()) { Class original = (Class) toResolve; Type componentType = original.getComponentType(); Type newComponentType = resolve(context, contextRawType, componentType, visitedTypeVariables); toResolve = equal(componentType, newComponentType) ? original : arrayOf(newComponentType); break; } else if (toResolve instanceof GenericArrayType) { GenericArrayType original = (GenericArrayType) toResolve; Type componentType = original.getGenericComponentType(); Type newComponentType = resolve(context, contextRawType, componentType, visitedTypeVariables); toResolve = equal(componentType, newComponentType) ? original : arrayOf(newComponentType); break; } else if (toResolve instanceof ParameterizedType) { ParameterizedType original = (ParameterizedType) toResolve; Type ownerType = original.getOwnerType(); Type newOwnerType = resolve(context, contextRawType, ownerType, visitedTypeVariables); boolean changed = !equal(newOwnerType, ownerType); Type[] args = original.getActualTypeArguments(); for (int t = 0, length = args.length; t < length; t++) { Type resolvedTypeArgument = resolve(context, contextRawType, args[t], visitedTypeVariables); if (!equal(resolvedTypeArgument, args[t])) { if (!changed) { args = args.clone(); changed = true; } args[t] = resolvedTypeArgument; } } toResolve = changed ? newParameterizedTypeWithOwner(newOwnerType, original.getRawType(), args) : original; break; } 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], visitedTypeVariables); if (lowerBound != originalLowerBound[0]) { toResolve = supertypeOf(lowerBound); break; } } else if (originalUpperBound.length == 1) { Type upperBound = resolve(context, contextRawType, originalUpperBound[0], visitedTypeVariables); if (upperBound != originalUpperBound[0]) { toResolve = subtypeOf(upperBound); break; } } toResolve = original; break; } else { break; } } // ensure that any in-process resolution gets updated with the final result if (resolving != null) { visitedTypeVariables.put(resolving, toResolve); } return toResolve; } private static Type resolveTypeVariable(Type context, Class contextRawType, TypeVariable unknown) { Class declaredByRaw = declaringClassOf(unknown); // we can't reduce this further if (declaredByRaw == null) { return unknown; } Type declaredBy = getGenericSupertype(context, contextRawType, declaredByRaw); if (declaredBy instanceof ParameterizedType) { int index = indexOf(declaredByRaw.getTypeParameters(), unknown); return ((ParameterizedType) declaredBy).getActualTypeArguments()[index]; } return unknown; } private static int indexOf(Object[] array, Object toFind) { for (int i = 0, length = array.length; i < length; i++) { if (toFind.equals(array[i])) { return i; } } throw new NoSuchElementException(); } /** * Returns the declaring class of {@code typeVariable}, or {@code null} if it was not declared by * a class. */ private static Class declaringClassOf(TypeVariable typeVariable) { GenericDeclaration genericDeclaration = typeVariable.getGenericDeclaration(); return genericDeclaration instanceof Class ? (Class) genericDeclaration : null; } static void checkNotPrimitive(Type type) { checkArgument(!(type instanceof Class) || !((Class) type).isPrimitive()); } private static final class ParameterizedTypeImpl implements ParameterizedType, Serializable { private final Type ownerType; private final Type rawType; private final Type[] typeArguments; public ParameterizedTypeImpl(Type ownerType, Type rawType, Type... typeArguments) { requireNonNull(rawType); // require an owner type if the raw type needs it if (rawType instanceof Class) { Class rawTypeAsClass = (Class) rawType; boolean isStaticOrTopLevelClass = Modifier.isStatic(rawTypeAsClass.getModifiers()) || rawTypeAsClass.getEnclosingClass() == null; checkArgument(ownerType != null || isStaticOrTopLevelClass); } this.ownerType = ownerType == null ? null : canonicalize(ownerType); this.rawType = canonicalize(rawType); this.typeArguments = typeArguments.clone(); for (int t = 0, length = this.typeArguments.length; t < length; t++) { requireNonNull(this.typeArguments[t]); checkNotPrimitive(this.typeArguments[t]); this.typeArguments[t] = canonicalize(this.typeArguments[t]); } } @Override public Type[] getActualTypeArguments() { return typeArguments.clone(); } @Override public Type getRawType() { return rawType; } @Override public Type getOwnerType() { return ownerType; } @Override public boolean equals(Object other) { return other instanceof ParameterizedType && $Gson$Types.equals(this, (ParameterizedType) other); } private static int hashCodeOrZero(Object o) { return o != null ? o.hashCode() : 0; } @Override public int hashCode() { return Arrays.hashCode(typeArguments) ^ rawType.hashCode() ^ hashCodeOrZero(ownerType); } @Override public String toString() { int length = typeArguments.length; if (length == 0) { return typeToString(rawType); } StringBuilder stringBuilder = new StringBuilder(30 * (length + 1)); stringBuilder.append(typeToString(rawType)).append("<").append(typeToString(typeArguments[0])); for (int i = 1; i < length; i++) { stringBuilder.append(", ").append(typeToString(typeArguments[i])); } return stringBuilder.append(">").toString(); } private static final long serialVersionUID = 0; } private static final class GenericArrayTypeImpl implements GenericArrayType, Serializable { private final Type componentType; public GenericArrayTypeImpl(Type componentType) { requireNonNull(componentType); this.componentType = canonicalize(componentType); } @Override public Type getGenericComponentType() { return componentType; } @Override public boolean equals(Object o) { return o instanceof GenericArrayType && $Gson$Types.equals(this, (GenericArrayType) o); } @Override public int hashCode() { return componentType.hashCode(); } @Override public String toString() { return typeToString(componentType) + "[]"; } private static final long serialVersionUID = 0; } /** * The WildcardType interface supports multiple upper bounds and multiple * lower bounds. We only support what the target Java version supports - at most one * bound, see also https://bugs.openjdk.java.net/browse/JDK-8250660. If a lower bound * is set, the upper bound must be Object.class. */ private static final class WildcardTypeImpl implements WildcardType, Serializable { private final Type upperBound; private final Type lowerBound; public WildcardTypeImpl(Type[] upperBounds, Type[] lowerBounds) { checkArgument(lowerBounds.length <= 1); checkArgument(upperBounds.length == 1); if (lowerBounds.length == 1) { requireNonNull(lowerBounds[0]); checkNotPrimitive(lowerBounds[0]); checkArgument(upperBounds[0] == Object.class); this.lowerBound = canonicalize(lowerBounds[0]); this.upperBound = Object.class; } else { requireNonNull(upperBounds[0]); checkNotPrimitive(upperBounds[0]); this.lowerBound = null; this.upperBound = canonicalize(upperBounds[0]); } } @Override public Type[] getUpperBounds() { return new Type[] { upperBound }; } @Override public Type[] getLowerBounds() { return lowerBound != null ? new Type[] { lowerBound } : EMPTY_TYPE_ARRAY; } @Override public boolean equals(Object other) { return other instanceof WildcardType && $Gson$Types.equals(this, (WildcardType) other); } @Override public int hashCode() { // this equals Arrays.hashCode(getLowerBounds()) ^ Arrays.hashCode(getUpperBounds()); return (lowerBound != null ? 31 + lowerBound.hashCode() : 1) ^ (31 + upperBound.hashCode()); } @Override public String toString() { if (lowerBound != null) { return "? super " + typeToString(lowerBound); } else if (upperBound == Object.class) { return "?"; } else { return "? extends " + typeToString(upperBound); } } private static final long serialVersionUID = 0; } }