diff --git a/moshi/src/main/java/com/squareup/moshi/TypeLiteral.java b/moshi/src/main/java/com/squareup/moshi/TypeLiteral.java
new file mode 100644
index 0000000..6172a55
--- /dev/null
+++ b/moshi/src/main/java/com/squareup/moshi/TypeLiteral.java
@@ -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.
+ *
+ *
For example, to create a type literal for {@code List}, you can create an empty
+ * anonymous inner class: {@code
+ *
+ * TypeToken> list = new TypeToken>() {};
+ * }
+ *
+ * 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 {
+ final Class super T> rawType;
+ final Type type;
+ final int hashCode;
+
+ /**
+ * Constructs a new type literal. Derives represented class from type parameter.
+ *
+ * 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(type);
+ }
+
+ /** Returns a type literal for {@code type}. */
+ public static TypeLiteral get(Class type) {
+ return new TypeLiteral(type);
+ }
+}
diff --git a/moshi/src/main/java/com/squareup/moshi/Types.java b/moshi/src/main/java/com/squareup/moshi/Types.java
new file mode 100644
index 0000000..8c4f584
--- /dev/null
+++ b/moshi/src/main/java/com/squareup/moshi/Types.java
@@ -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} and the
+ * result when the supertype is {@code Collection.class} is {@code Collection}.
+ */
+ 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}, 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) {
+ 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, but it's declared to extend Hashtable.
+ 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);
+ }
+ }
+ }
+}
diff --git a/moshi/src/test/java/com/squareup/moshi/TypesTest.java b/moshi/src/test/java/com/squareup/moshi/TypesTest.java
new file mode 100644
index 0000000..83bd221
--- /dev/null
+++ b/moshi/src/test/java/com/squareup/moshi/TypesTest.java
@@ -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. List is a top-level class.
+ Type type = Types.newParameterizedTypeWithOwner(null, List.class, A.class);
+ assertThat(getFirstTypeArgument(type)).isEqualTo(A.class);
+
+ // A. 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 is not allowed since D is not a static inner class.
+ Types.newParameterizedTypeWithOwner(null, D.class, A.class);
+ } catch (IllegalArgumentException expected) {
+ }
+
+ // A 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}, 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 mapOfStringInteger;
+ Map[] arrayOfMapOfStringInteger;
+ ArrayList> arrayListOfMapOfStringInteger;
+ interface StringIntegerMap extends Map {
+ }
+
+ @Test public void typeLiteral() throws Exception {
+ TypeLiteral> typeLiteral = new TypeLiteral>() {};
+ 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");
+ 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);
+ }
+}