mirror of
https://github.com/fankes/moshi.git
synced 2025-10-18 23:49:21 +08:00
Import Gson's type resolver.
This commit is contained in:
105
moshi/src/main/java/com/squareup/moshi/TypeLiteral.java
Normal file
105
moshi/src/main/java/com/squareup/moshi/TypeLiteral.java
Normal file
@@ -0,0 +1,105 @@
|
||||
/*
|
||||
* 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.squareup.moshi;
|
||||
|
||||
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: <pre> {@code
|
||||
*
|
||||
* TypeToken<List<String>> list = new TypeToken<List<String>>() {};
|
||||
* }</pre>
|
||||
*
|
||||
* <p>This syntax cannot be used to create type literals that have wildcard parameters, such as
|
||||
* {@code Class<?>} or {@code List<? extends CharSequence>}.
|
||||
*
|
||||
* @author Bob Lee
|
||||
* @author Sven Mawson
|
||||
* @author 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>) Types.getRawType(type);
|
||||
this.hashCode = type.hashCode();
|
||||
}
|
||||
|
||||
/** Unsafe. Constructs a type literal manually. */
|
||||
@SuppressWarnings("unchecked")
|
||||
TypeLiteral(Type type) {
|
||||
if (type == null) throw new NullPointerException("type == null");
|
||||
this.type = Types.canonicalize(type);
|
||||
this.rawType = (Class<? super T>) Types.getRawType(this.type);
|
||||
this.hashCode = this.type.hashCode();
|
||||
}
|
||||
|
||||
/** Returns the type from super class's type parameter in canonical for}. */
|
||||
static Type getSuperclassTypeParameter(Class<?> subclass) {
|
||||
Type superclass = subclass.getGenericSuperclass();
|
||||
if (superclass instanceof Class) throw new IllegalArgumentException("Missing type parameter.");
|
||||
ParameterizedType parameterized = (ParameterizedType) superclass;
|
||||
return Types.canonicalize(parameterized.getActualTypeArguments()[0]);
|
||||
}
|
||||
|
||||
/** Returns the raw (non-generic) type for this type literal. */
|
||||
public final Class<? super T> getRawType() {
|
||||
return rawType;
|
||||
}
|
||||
|
||||
public final Type getType() {
|
||||
return type;
|
||||
}
|
||||
|
||||
@Override public final int hashCode() {
|
||||
return this.hashCode;
|
||||
}
|
||||
|
||||
@Override public final boolean equals(Object o) {
|
||||
return o instanceof TypeLiteral<?>
|
||||
&& Types.equals(type, ((TypeLiteral<?>) o).type);
|
||||
}
|
||||
|
||||
@Override public final String toString() {
|
||||
return Types.typeToString(type);
|
||||
}
|
||||
|
||||
/** Returns a type literal for {@code type}. */
|
||||
public static TypeLiteral<?> get(Type type) {
|
||||
return new TypeLiteral<Object>(type);
|
||||
}
|
||||
|
||||
/** Returns a type literal for {@code type}. */
|
||||
public static <T> TypeLiteral<T> get(Class<T> type) {
|
||||
return new TypeLiteral<T>(type);
|
||||
}
|
||||
}
|
542
moshi/src/main/java/com/squareup/moshi/Types.java
Normal file
542
moshi/src/main/java/com/squareup/moshi/Types.java
Normal file
@@ -0,0 +1,542 @@
|
||||
/**
|
||||
* 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.squareup.moshi;
|
||||
|
||||
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.Map;
|
||||
import java.util.NoSuchElementException;
|
||||
import java.util.Properties;
|
||||
|
||||
/**
|
||||
* Static methods for working with types.
|
||||
*
|
||||
* @author Bob Lee
|
||||
* @author Jesse Wilson
|
||||
*/
|
||||
final class Types {
|
||||
static final Type[] EMPTY_TYPE_ARRAY = new Type[] {};
|
||||
|
||||
private Types() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a new parameterized type, applying {@code typeArguments} to {@code rawType} and
|
||||
* enclosed by {@code ownerType}.
|
||||
*/
|
||||
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}. */
|
||||
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) {
|
||||
return new WildcardTypeImpl(new Type[] { bound }, 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) {
|
||||
return new WildcardTypeImpl(new Type[] { Object.class }, new Type[] { bound });
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a type that is functionally equal but not necessarily equal according to {@link
|
||||
* Object#equals(Object) Object.equals()}.
|
||||
*/
|
||||
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 {
|
||||
return type; // This type is unsupported!
|
||||
}
|
||||
}
|
||||
|
||||
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();
|
||||
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) {
|
||||
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 " + className);
|
||||
}
|
||||
}
|
||||
|
||||
static boolean equal(Object a, Object b) {
|
||||
return a == b || (a != null && a.equals(b));
|
||||
}
|
||||
|
||||
/** Returns true if {@code a} and {@code b} are equal. */
|
||||
public static boolean equals(Type a, Type b) {
|
||||
if (a == b) {
|
||||
return true; // Also handles (a == null && b == null).
|
||||
|
||||
} else if (a instanceof Class) {
|
||||
return a.equals(b); // Class already specifies equals().
|
||||
|
||||
} else if (a instanceof ParameterizedType) {
|
||||
if (!(b instanceof ParameterizedType)) return false;
|
||||
ParameterizedType pa = (ParameterizedType) a;
|
||||
ParameterizedType pb = (ParameterizedType) b;
|
||||
Type[] aTypeArguments = pa instanceof ParameterizedTypeImpl
|
||||
? ((ParameterizedTypeImpl) pa).typeArguments
|
||||
: pa.getActualTypeArguments();
|
||||
Type[] bTypeArguments = pb instanceof ParameterizedTypeImpl
|
||||
? ((ParameterizedTypeImpl) pb).typeArguments
|
||||
: pb.getActualTypeArguments();
|
||||
return equal(pa.getOwnerType(), pb.getOwnerType())
|
||||
&& pa.getRawType().equals(pb.getRawType())
|
||||
&& Arrays.equals(aTypeArguments, bTypeArguments);
|
||||
|
||||
} 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 supported type. Could be a generic array type, wildcard type, etc.
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private static int hashCodeOrZero(Object o) {
|
||||
return o != null ? o.hashCode() : 0;
|
||||
}
|
||||
|
||||
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<Integer>} and the
|
||||
* result when the supertype is {@code Collection.class} is {@code Collection<Integer>}.
|
||||
*/
|
||||
static Type getGenericSupertype(Type context, Class<?> rawType, Class<?> toResolve) {
|
||||
if (toResolve == rawType) {
|
||||
return context;
|
||||
}
|
||||
|
||||
// we skip searching through interfaces if unknown is an interface
|
||||
if (toResolve.isInterface()) {
|
||||
Class<?>[] interfaces = rawType.getInterfaces();
|
||||
for (int i = 0, length = interfaces.length; i < length; i++) {
|
||||
if (interfaces[i] == toResolve) {
|
||||
return rawType.getGenericInterfaces()[i];
|
||||
} else if (toResolve.isAssignableFrom(interfaces[i])) {
|
||||
return getGenericSupertype(rawType.getGenericInterfaces()[i], interfaces[i], toResolve);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// check our supertypes
|
||||
if (!rawType.isInterface()) {
|
||||
while (rawType != Object.class) {
|
||||
Class<?> rawSupertype = rawType.getSuperclass();
|
||||
if (rawSupertype == toResolve) {
|
||||
return rawType.getGenericSuperclass();
|
||||
} else if (toResolve.isAssignableFrom(rawSupertype)) {
|
||||
return getGenericSupertype(rawType.getGenericSuperclass(), rawSupertype, toResolve);
|
||||
}
|
||||
rawType = rawSupertype;
|
||||
}
|
||||
}
|
||||
|
||||
// we can't resolve this further
|
||||
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.
|
||||
*/
|
||||
static Type getSupertype(Type context, Class<?> contextRawType, Class<?> supertype) {
|
||||
if (!supertype.isAssignableFrom(contextRawType)) throw new IllegalArgumentException();
|
||||
return resolve(context, contextRawType,
|
||||
getGenericSupertype(context, contextRawType, supertype));
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the component type of this array type.
|
||||
* @throws ClassCastException if this type is not an array.
|
||||
*/
|
||||
public static Type arrayComponentType(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 collectionElementType(Type context, Class<?> contextRawType) {
|
||||
Type collectionType = getSupertype(context, contextRawType, Collection.class);
|
||||
|
||||
if (collectionType instanceof WildcardType) {
|
||||
collectionType = ((WildcardType) collectionType).getUpperBounds()[0];
|
||||
}
|
||||
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[] mapKeyAndValueTypes(Type context, Class<?> contextRawType) {
|
||||
// Work around a problem with the declaration of java.util.Properties. That class should extend
|
||||
// Hashtable<String, String>, but it's declared to extend Hashtable<Object, Object>.
|
||||
if (context == Properties.class) return new Type[] { String.class, String.class };
|
||||
|
||||
Type mapType = getSupertype(context, contextRawType, Map.class);
|
||||
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) {
|
||||
// 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.
|
||||
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; i < array.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;
|
||||
}
|
||||
|
||||
private static void checkNotPrimitive(Type type) {
|
||||
if ((type instanceof Class<?>) && ((Class<?>) type).isPrimitive()) {
|
||||
throw new IllegalArgumentException();
|
||||
}
|
||||
}
|
||||
|
||||
private static final class ParameterizedTypeImpl implements ParameterizedType {
|
||||
private final Type ownerType;
|
||||
private final Type rawType;
|
||||
private final Type[] typeArguments;
|
||||
|
||||
public ParameterizedTypeImpl(Type ownerType, Type rawType, Type... typeArguments) {
|
||||
// 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;
|
||||
if (ownerType == null && !isStaticOrTopLevelClass) throw new IllegalArgumentException();
|
||||
}
|
||||
|
||||
this.ownerType = ownerType == null ? null : canonicalize(ownerType);
|
||||
this.rawType = canonicalize(rawType);
|
||||
this.typeArguments = typeArguments.clone();
|
||||
for (int t = 0; t < this.typeArguments.length; t++) {
|
||||
if (this.typeArguments[t] == null) throw new NullPointerException();
|
||||
checkNotPrimitive(this.typeArguments[t]);
|
||||
this.typeArguments[t] = canonicalize(this.typeArguments[t]);
|
||||
}
|
||||
}
|
||||
|
||||
public Type[] getActualTypeArguments() {
|
||||
return typeArguments.clone();
|
||||
}
|
||||
|
||||
public Type getRawType() {
|
||||
return rawType;
|
||||
}
|
||||
|
||||
public Type getOwnerType() {
|
||||
return ownerType;
|
||||
}
|
||||
|
||||
@Override public boolean equals(Object other) {
|
||||
return other instanceof ParameterizedType
|
||||
&& Types.equals(this, (ParameterizedType) other);
|
||||
}
|
||||
|
||||
@Override public int hashCode() {
|
||||
return Arrays.hashCode(typeArguments)
|
||||
^ rawType.hashCode()
|
||||
^ hashCodeOrZero(ownerType);
|
||||
}
|
||||
|
||||
@Override public String toString() {
|
||||
StringBuilder result = new StringBuilder(30 * (typeArguments.length + 1));
|
||||
result.append(typeToString(rawType));
|
||||
|
||||
if (typeArguments.length == 0) {
|
||||
return result.toString();
|
||||
}
|
||||
|
||||
result.append("<").append(typeToString(typeArguments[0]));
|
||||
for (int i = 1; i < typeArguments.length; i++) {
|
||||
result.append(", ").append(typeToString(typeArguments[i]));
|
||||
}
|
||||
return result.append(">").toString();
|
||||
}
|
||||
}
|
||||
|
||||
private static final class GenericArrayTypeImpl implements GenericArrayType {
|
||||
private final Type componentType;
|
||||
|
||||
public GenericArrayTypeImpl(Type componentType) {
|
||||
this.componentType = canonicalize(componentType);
|
||||
}
|
||||
|
||||
public Type getGenericComponentType() {
|
||||
return componentType;
|
||||
}
|
||||
|
||||
@Override public boolean equals(Object o) {
|
||||
return o instanceof GenericArrayType
|
||||
&& Types.equals(this, (GenericArrayType) o);
|
||||
}
|
||||
|
||||
@Override public int hashCode() {
|
||||
return componentType.hashCode();
|
||||
}
|
||||
|
||||
@Override public String toString() {
|
||||
return typeToString(componentType) + "[]";
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The WildcardType interface supports multiple upper bounds and multiple lower bounds. We only
|
||||
* support what the Java 6 language needs - at most one bound. If a lower bound is set, the upper
|
||||
* bound must be Object.class.
|
||||
*/
|
||||
private static final class WildcardTypeImpl implements WildcardType {
|
||||
private final Type upperBound;
|
||||
private final Type lowerBound;
|
||||
|
||||
public WildcardTypeImpl(Type[] upperBounds, Type[] lowerBounds) {
|
||||
if (lowerBounds.length > 1) throw new IllegalArgumentException();
|
||||
if (upperBounds.length != 1) throw new IllegalArgumentException();
|
||||
|
||||
if (lowerBounds.length == 1) {
|
||||
if (lowerBounds[0] == null) throw new NullPointerException();
|
||||
checkNotPrimitive(lowerBounds[0]);
|
||||
if (upperBounds[0] != Object.class) throw new IllegalArgumentException();
|
||||
this.lowerBound = canonicalize(lowerBounds[0]);
|
||||
this.upperBound = Object.class;
|
||||
|
||||
} else {
|
||||
if (upperBounds[0] == null) throw new NullPointerException();
|
||||
checkNotPrimitive(upperBounds[0]);
|
||||
this.lowerBound = null;
|
||||
this.upperBound = canonicalize(upperBounds[0]);
|
||||
}
|
||||
}
|
||||
|
||||
public Type[] getUpperBounds() {
|
||||
return new Type[] { upperBound };
|
||||
}
|
||||
|
||||
public Type[] getLowerBounds() {
|
||||
return lowerBound != null ? new Type[] { lowerBound } : EMPTY_TYPE_ARRAY;
|
||||
}
|
||||
|
||||
@Override public boolean equals(Object other) {
|
||||
return other instanceof WildcardType
|
||||
&& 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
134
moshi/src/test/java/com/squareup/moshi/TypesTest.java
Normal file
134
moshi/src/test/java/com/squareup/moshi/TypesTest.java
Normal file
@@ -0,0 +1,134 @@
|
||||
/*
|
||||
* 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.squareup.moshi;
|
||||
|
||||
import java.lang.reflect.ParameterizedType;
|
||||
import java.lang.reflect.Type;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Properties;
|
||||
import org.junit.Test;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.junit.Assert.assertNull;
|
||||
|
||||
public class TypesTest {
|
||||
@Test public void newParameterizedTypeWithoutOwner() throws Exception {
|
||||
// List<A>. List is a top-level class.
|
||||
Type type = Types.newParameterizedTypeWithOwner(null, List.class, A.class);
|
||||
assertThat(getFirstTypeArgument(type)).isEqualTo(A.class);
|
||||
|
||||
// A<B>. A is a static inner class.
|
||||
type = Types.newParameterizedTypeWithOwner(null, A.class, B.class);
|
||||
assertThat(getFirstTypeArgument(type)).isEqualTo(B.class);
|
||||
|
||||
final class D {
|
||||
}
|
||||
try {
|
||||
// D<A> is not allowed since D is not a static inner class.
|
||||
Types.newParameterizedTypeWithOwner(null, D.class, A.class);
|
||||
} catch (IllegalArgumentException expected) {
|
||||
}
|
||||
|
||||
// A<D> is allowed.
|
||||
type = Types.newParameterizedTypeWithOwner(null, A.class, D.class);
|
||||
assertThat(getFirstTypeArgument(type)).isEqualTo(D.class);
|
||||
}
|
||||
|
||||
@Test public void getFirstTypeArgument() throws Exception {
|
||||
assertNull(getFirstTypeArgument(A.class));
|
||||
|
||||
Type type = Types.newParameterizedTypeWithOwner(null, A.class, B.class, C.class);
|
||||
assertThat(getFirstTypeArgument(type)).isEqualTo(B.class);
|
||||
}
|
||||
|
||||
private static final class A {
|
||||
}
|
||||
|
||||
private static final class B {
|
||||
}
|
||||
|
||||
private static final class C {
|
||||
}
|
||||
|
||||
/**
|
||||
* Given a parameterized type {@code A<B, C>}, returns B. If the specified type is not a generic
|
||||
* type, returns null.
|
||||
*/
|
||||
public static Type getFirstTypeArgument(Type type) throws Exception {
|
||||
if (!(type instanceof ParameterizedType)) return null;
|
||||
ParameterizedType ptype = (ParameterizedType) type;
|
||||
Type[] actualTypeArguments = ptype.getActualTypeArguments();
|
||||
if (actualTypeArguments.length == 0) return null;
|
||||
return Types.canonicalize(actualTypeArguments[0]);
|
||||
}
|
||||
|
||||
Map<String, Integer> mapOfStringInteger;
|
||||
Map<String, Integer>[] arrayOfMapOfStringInteger;
|
||||
ArrayList<Map<String, Integer>> arrayListOfMapOfStringInteger;
|
||||
interface StringIntegerMap extends Map<String, Integer> {
|
||||
}
|
||||
|
||||
@Test public void typeLiteral() throws Exception {
|
||||
TypeLiteral<Map<String, Integer>> typeLiteral = new TypeLiteral<Map<String, Integer>>() {};
|
||||
TypeLiteral<?> fromReflection = TypeLiteral.get(
|
||||
TypesTest.class.getDeclaredField("mapOfStringInteger").getGenericType());
|
||||
assertThat(typeLiteral).isEqualTo(fromReflection);
|
||||
assertThat(typeLiteral.hashCode()).isEqualTo(fromReflection.hashCode());
|
||||
assertThat(typeLiteral.toString())
|
||||
.isEqualTo("java.util.Map<java.lang.String, java.lang.Integer>");
|
||||
assertThat(typeLiteral.getRawType()).isEqualTo(Map.class);
|
||||
}
|
||||
|
||||
@Test public void arrayComponentType() throws Exception {
|
||||
assertThat(Types.arrayComponentType(String[][].class)).isEqualTo(String[].class);
|
||||
assertThat(Types.arrayComponentType(String[].class)).isEqualTo(String.class);
|
||||
|
||||
Type arrayOfMapOfStringIntegerType = TypesTest.class.getDeclaredField(
|
||||
"arrayOfMapOfStringInteger").getGenericType();
|
||||
Type mapOfStringIntegerType = TypesTest.class.getDeclaredField(
|
||||
"mapOfStringInteger").getGenericType();
|
||||
assertThat(Types.arrayComponentType(arrayOfMapOfStringIntegerType))
|
||||
.isEqualTo(mapOfStringIntegerType);
|
||||
}
|
||||
|
||||
@Test public void collectionElementType() throws Exception {
|
||||
Type arrayListOfMapOfStringIntegerType = TypesTest.class.getDeclaredField(
|
||||
"arrayListOfMapOfStringInteger").getGenericType();
|
||||
Type mapOfStringIntegerType = TypesTest.class.getDeclaredField(
|
||||
"mapOfStringInteger").getGenericType();
|
||||
assertThat(Types.collectionElementType(arrayListOfMapOfStringIntegerType, List.class))
|
||||
.isEqualTo(mapOfStringIntegerType);
|
||||
}
|
||||
|
||||
@Test public void mapKeyAndValueTypes() throws Exception {
|
||||
Type mapOfStringIntegerType = TypesTest.class.getDeclaredField(
|
||||
"mapOfStringInteger").getGenericType();
|
||||
assertThat(Types.mapKeyAndValueTypes(mapOfStringIntegerType, Map.class))
|
||||
.containsExactly(String.class, Integer.class);
|
||||
}
|
||||
|
||||
@Test public void propertiesTypes() throws Exception {
|
||||
assertThat(Types.mapKeyAndValueTypes(Properties.class, Properties.class))
|
||||
.containsExactly(String.class, String.class);
|
||||
}
|
||||
|
||||
@Test public void fixedVariablesTypes() throws Exception {
|
||||
assertThat(Types.mapKeyAndValueTypes(StringIntegerMap.class, StringIntegerMap.class))
|
||||
.containsExactly(String.class, Integer.class);
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user