mirror of
https://github.com/fankes/moshi.git
synced 2025-10-20 00:19:21 +08:00
Hide Types.resolve.
This commit is contained in:
@@ -23,6 +23,7 @@ import com.squareup.moshi.JsonWriter
|
|||||||
import com.squareup.moshi.Moshi
|
import com.squareup.moshi.Moshi
|
||||||
import com.squareup.moshi.Types
|
import com.squareup.moshi.Types
|
||||||
import com.squareup.moshi.internal.Util
|
import com.squareup.moshi.internal.Util
|
||||||
|
import com.squareup.moshi.internal.Util.resolve
|
||||||
import java.lang.reflect.Modifier
|
import java.lang.reflect.Modifier
|
||||||
import java.lang.reflect.Type
|
import java.lang.reflect.Type
|
||||||
import java.util.AbstractMap.SimpleEntry
|
import java.util.AbstractMap.SimpleEntry
|
||||||
@@ -225,7 +226,7 @@ class KotlinJsonAdapterFactory : JsonAdapter.Factory {
|
|||||||
}
|
}
|
||||||
|
|
||||||
val name = jsonAnnotation?.name ?: property.name
|
val name = jsonAnnotation?.name ?: property.name
|
||||||
val resolvedPropertyType = Types.resolve(type, rawType, property.returnType.javaType)
|
val resolvedPropertyType = resolve(type, rawType, property.returnType.javaType)
|
||||||
val adapter = moshi.adapter<Any>(
|
val adapter = moshi.adapter<Any>(
|
||||||
resolvedPropertyType, Util.jsonAnnotations(allAnnotations.toTypedArray()))
|
resolvedPropertyType, Util.jsonAnnotations(allAnnotations.toTypedArray()))
|
||||||
|
|
||||||
|
@@ -27,6 +27,7 @@ import java.util.List;
|
|||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
import javax.annotation.Nullable;
|
import javax.annotation.Nullable;
|
||||||
|
|
||||||
|
import static com.squareup.moshi.internal.Util.canonicalize;
|
||||||
import static com.squareup.moshi.internal.Util.jsonAnnotations;
|
import static com.squareup.moshi.internal.Util.jsonAnnotations;
|
||||||
|
|
||||||
final class AdapterMethodsFactory implements JsonAdapter.Factory {
|
final class AdapterMethodsFactory implements JsonAdapter.Factory {
|
||||||
@@ -289,7 +290,7 @@ final class AdapterMethodsFactory implements JsonAdapter.Factory {
|
|||||||
|
|
||||||
AdapterMethod(Type type, Set<? extends Annotation> annotations, Object adapter,
|
AdapterMethod(Type type, Set<? extends Annotation> annotations, Object adapter,
|
||||||
Method method, int parameterCount, int adaptersOffset, boolean nullable) {
|
Method method, int parameterCount, int adaptersOffset, boolean nullable) {
|
||||||
this.type = Types.canonicalize(type);
|
this.type = canonicalize(type);
|
||||||
this.annotations = annotations;
|
this.annotations = annotations;
|
||||||
this.adapter = adapter;
|
this.adapter = adapter;
|
||||||
this.method = method;
|
this.method = method;
|
||||||
|
@@ -28,6 +28,8 @@ import java.util.Set;
|
|||||||
import java.util.TreeMap;
|
import java.util.TreeMap;
|
||||||
import javax.annotation.Nullable;
|
import javax.annotation.Nullable;
|
||||||
|
|
||||||
|
import static com.squareup.moshi.internal.Util.resolve;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Emits a regular class as a JSON object by mapping Java fields to JSON object properties.
|
* Emits a regular class as a JSON object by mapping Java fields to JSON object properties.
|
||||||
*
|
*
|
||||||
@@ -92,7 +94,7 @@ final class ClassJsonAdapter<T> extends JsonAdapter<T> {
|
|||||||
if (!includeField(platformType, field.getModifiers())) continue;
|
if (!includeField(platformType, field.getModifiers())) continue;
|
||||||
|
|
||||||
// Look up a type adapter for this type.
|
// Look up a type adapter for this type.
|
||||||
Type fieldType = Types.resolve(type, rawType, field.getGenericType());
|
Type fieldType = resolve(type, rawType, field.getGenericType());
|
||||||
Set<? extends Annotation> annotations = Util.jsonAnnotations(field);
|
Set<? extends Annotation> annotations = Util.jsonAnnotations(field);
|
||||||
JsonAdapter<Object> adapter = moshi.adapter(fieldType, annotations);
|
JsonAdapter<Object> adapter = moshi.adapter(fieldType, annotations);
|
||||||
|
|
||||||
|
@@ -30,6 +30,8 @@ import java.util.Set;
|
|||||||
import javax.annotation.CheckReturnValue;
|
import javax.annotation.CheckReturnValue;
|
||||||
import javax.annotation.Nullable;
|
import javax.annotation.Nullable;
|
||||||
|
|
||||||
|
import static com.squareup.moshi.internal.Util.canonicalize;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Coordinates binding between JSON values and Java objects.
|
* Coordinates binding between JSON values and Java objects.
|
||||||
*/
|
*/
|
||||||
@@ -93,7 +95,7 @@ public final class Moshi {
|
|||||||
throw new NullPointerException("annotations == null");
|
throw new NullPointerException("annotations == null");
|
||||||
}
|
}
|
||||||
|
|
||||||
type = Types.canonicalize(type);
|
type = canonicalize(type);
|
||||||
|
|
||||||
// If there's an equivalent adapter in the cache, we're done!
|
// If there's an equivalent adapter in the cache, we're done!
|
||||||
Object cacheKey = cacheKey(type, annotations);
|
Object cacheKey = cacheKey(type, annotations);
|
||||||
@@ -146,7 +148,7 @@ public final class Moshi {
|
|||||||
Set<? extends Annotation> annotations) {
|
Set<? extends Annotation> annotations) {
|
||||||
if (annotations == null) throw new NullPointerException("annotations == null");
|
if (annotations == null) throw new NullPointerException("annotations == null");
|
||||||
|
|
||||||
type = Types.canonicalize(type);
|
type = canonicalize(type);
|
||||||
|
|
||||||
int skipPastIndex = factories.indexOf(skipPast);
|
int skipPastIndex = factories.indexOf(skipPast);
|
||||||
if (skipPastIndex == -1) {
|
if (skipPastIndex == -1) {
|
||||||
|
@@ -15,10 +15,12 @@
|
|||||||
*/
|
*/
|
||||||
package com.squareup.moshi;
|
package com.squareup.moshi;
|
||||||
|
|
||||||
|
import com.squareup.moshi.internal.Util.GenericArrayTypeImpl;
|
||||||
|
import com.squareup.moshi.internal.Util.ParameterizedTypeImpl;
|
||||||
|
import com.squareup.moshi.internal.Util.WildcardTypeImpl;
|
||||||
import java.lang.annotation.Annotation;
|
import java.lang.annotation.Annotation;
|
||||||
import java.lang.reflect.Array;
|
import java.lang.reflect.Array;
|
||||||
import java.lang.reflect.GenericArrayType;
|
import java.lang.reflect.GenericArrayType;
|
||||||
import java.lang.reflect.GenericDeclaration;
|
|
||||||
import java.lang.reflect.InvocationHandler;
|
import java.lang.reflect.InvocationHandler;
|
||||||
import java.lang.reflect.Method;
|
import java.lang.reflect.Method;
|
||||||
import java.lang.reflect.ParameterizedType;
|
import java.lang.reflect.ParameterizedType;
|
||||||
@@ -31,16 +33,18 @@ import java.util.Collection;
|
|||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.LinkedHashSet;
|
import java.util.LinkedHashSet;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.NoSuchElementException;
|
|
||||||
import java.util.Properties;
|
import java.util.Properties;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
import javax.annotation.CheckReturnValue;
|
import javax.annotation.CheckReturnValue;
|
||||||
import javax.annotation.Nullable;
|
import javax.annotation.Nullable;
|
||||||
|
|
||||||
/** Factory methods for types. */
|
import static com.squareup.moshi.internal.Util.EMPTY_TYPE_ARRAY;
|
||||||
public final class Types {
|
import static com.squareup.moshi.internal.Util.getGenericSupertype;
|
||||||
static final Type[] EMPTY_TYPE_ARRAY = new Type[] {};
|
import static com.squareup.moshi.internal.Util.resolve;
|
||||||
|
|
||||||
|
/** Factory methods for types. */
|
||||||
|
@CheckReturnValue
|
||||||
|
public final class Types {
|
||||||
private Types() {
|
private Types() {
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -49,7 +53,7 @@ public final class Types {
|
|||||||
* Returns the subset of {@code annotations} without {@code jsonQualifier}, or null if {@code
|
* Returns the subset of {@code annotations} without {@code jsonQualifier}, or null if {@code
|
||||||
* annotations} does not contain {@code jsonQualifier}.
|
* annotations} does not contain {@code jsonQualifier}.
|
||||||
*/
|
*/
|
||||||
@CheckReturnValue public static @Nullable Set<? extends Annotation> nextAnnotations(
|
public static @Nullable Set<? extends Annotation> nextAnnotations(
|
||||||
Set<? extends Annotation> annotations,
|
Set<? extends Annotation> annotations,
|
||||||
Class<? extends Annotation> jsonQualifier) {
|
Class<? extends Annotation> jsonQualifier) {
|
||||||
if (!jsonQualifier.isAnnotationPresent(JsonQualifier.class)) {
|
if (!jsonQualifier.isAnnotationPresent(JsonQualifier.class)) {
|
||||||
@@ -72,7 +76,6 @@ public final class Types {
|
|||||||
* Returns a new parameterized type, applying {@code typeArguments} to {@code rawType}. Use this
|
* Returns a new parameterized type, applying {@code typeArguments} to {@code rawType}. Use this
|
||||||
* method if {@code rawType} is not enclosed in another type.
|
* method if {@code rawType} is not enclosed in another type.
|
||||||
*/
|
*/
|
||||||
@CheckReturnValue
|
|
||||||
public static ParameterizedType newParameterizedType(Type rawType, Type... typeArguments) {
|
public static ParameterizedType newParameterizedType(Type rawType, Type... typeArguments) {
|
||||||
return new ParameterizedTypeImpl(null, rawType, typeArguments);
|
return new ParameterizedTypeImpl(null, rawType, typeArguments);
|
||||||
}
|
}
|
||||||
@@ -81,13 +84,13 @@ public final class Types {
|
|||||||
* Returns a new parameterized type, applying {@code typeArguments} to {@code rawType}. Use this
|
* Returns a new parameterized type, applying {@code typeArguments} to {@code rawType}. Use this
|
||||||
* method if {@code rawType} is enclosed in {@code ownerType}.
|
* method if {@code rawType} is enclosed in {@code ownerType}.
|
||||||
*/
|
*/
|
||||||
@CheckReturnValue public static ParameterizedType newParameterizedTypeWithOwner(
|
public static ParameterizedType newParameterizedTypeWithOwner(
|
||||||
Type ownerType, Type rawType, Type... typeArguments) {
|
Type ownerType, Type rawType, Type... typeArguments) {
|
||||||
return new ParameterizedTypeImpl(ownerType, rawType, typeArguments);
|
return new ParameterizedTypeImpl(ownerType, rawType, typeArguments);
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Returns an array type whose elements are all instances of {@code componentType}. */
|
/** Returns an array type whose elements are all instances of {@code componentType}. */
|
||||||
@CheckReturnValue public static GenericArrayType arrayOf(Type componentType) {
|
public static GenericArrayType arrayOf(Type componentType) {
|
||||||
return new GenericArrayTypeImpl(componentType);
|
return new GenericArrayTypeImpl(componentType);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -97,7 +100,7 @@ public final class Types {
|
|||||||
* {@code bound} is {@code Object.class}, this returns {@code ?}, which is shorthand for {@code
|
* {@code bound} is {@code Object.class}, this returns {@code ?}, which is shorthand for {@code
|
||||||
* ? extends Object}.
|
* ? extends Object}.
|
||||||
*/
|
*/
|
||||||
@CheckReturnValue public static WildcardType subtypeOf(Type bound) {
|
public static WildcardType subtypeOf(Type bound) {
|
||||||
return new WildcardTypeImpl(new Type[] { bound }, EMPTY_TYPE_ARRAY);
|
return new WildcardTypeImpl(new Type[] { bound }, EMPTY_TYPE_ARRAY);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -105,41 +108,11 @@ public final class Types {
|
|||||||
* Returns a type that represents an unknown supertype of {@code bound}. For example, if {@code
|
* 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}.
|
* bound} is {@code String.class}, this returns {@code ? super String}.
|
||||||
*/
|
*/
|
||||||
@CheckReturnValue public static WildcardType supertypeOf(Type bound) {
|
public static WildcardType supertypeOf(Type bound) {
|
||||||
return new WildcardTypeImpl(new Type[] { Object.class }, new Type[] { bound });
|
return new WildcardTypeImpl(new Type[] { Object.class }, new Type[] { bound });
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
public static Class<?> getRawType(Type type) {
|
||||||
* Returns a type that is functionally equal but not necessarily equal according to {@link
|
|
||||||
* Object#equals(Object) Object.equals()}.
|
|
||||||
*/
|
|
||||||
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) {
|
|
||||||
if (type instanceof ParameterizedTypeImpl) return type;
|
|
||||||
ParameterizedType p = (ParameterizedType) type;
|
|
||||||
return new ParameterizedTypeImpl(p.getOwnerType(),
|
|
||||||
p.getRawType(), p.getActualTypeArguments());
|
|
||||||
|
|
||||||
} else if (type instanceof GenericArrayType) {
|
|
||||||
if (type instanceof GenericArrayTypeImpl) return type;
|
|
||||||
GenericArrayType g = (GenericArrayType) type;
|
|
||||||
return new GenericArrayTypeImpl(g.getGenericComponentType());
|
|
||||||
|
|
||||||
} else if (type instanceof WildcardType) {
|
|
||||||
if (type instanceof WildcardTypeImpl) return type;
|
|
||||||
WildcardType w = (WildcardType) type;
|
|
||||||
return new WildcardTypeImpl(w.getUpperBounds(), w.getLowerBounds());
|
|
||||||
|
|
||||||
} else {
|
|
||||||
return type; // This type is unsupported!
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@CheckReturnValue public static Class<?> getRawType(Type type) {
|
|
||||||
if (type instanceof Class<?>) {
|
if (type instanceof Class<?>) {
|
||||||
// type is a normal class.
|
// type is a normal class.
|
||||||
return (Class<?>) type;
|
return (Class<?>) type;
|
||||||
@@ -171,41 +144,24 @@ public final class Types {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@SuppressWarnings("unchecked")
|
/**
|
||||||
static <T extends Annotation> T createJsonQualifierImplementation(final Class<T> annotationType) {
|
* Returns the element type of this collection type.
|
||||||
if (!annotationType.isAnnotation()) {
|
* @throws IllegalArgumentException if this type is not a collection.
|
||||||
throw new IllegalArgumentException(annotationType + " must be an annotation.");
|
*/
|
||||||
|
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 (!annotationType.isAnnotationPresent(JsonQualifier.class)) {
|
if (collectionType instanceof ParameterizedType) {
|
||||||
throw new IllegalArgumentException(annotationType + " must have @JsonQualifier.");
|
return ((ParameterizedType) collectionType).getActualTypeArguments()[0];
|
||||||
}
|
}
|
||||||
if (annotationType.getDeclaredMethods().length != 0) {
|
return Object.class;
|
||||||
throw new IllegalArgumentException(annotationType + " must not declare methods.");
|
|
||||||
}
|
|
||||||
return (T) Proxy.newProxyInstance(annotationType.getClassLoader(),
|
|
||||||
new Class<?>[] { annotationType }, new InvocationHandler() {
|
|
||||||
@Override public Object invoke(Object proxy, Method method, Object[] args)
|
|
||||||
throws Throwable {
|
|
||||||
String methodName = method.getName();
|
|
||||||
switch (methodName) {
|
|
||||||
case "annotationType":
|
|
||||||
return annotationType;
|
|
||||||
case "equals":
|
|
||||||
Object o = args[0];
|
|
||||||
return annotationType.isInstance(o);
|
|
||||||
case "hashCode":
|
|
||||||
return 0;
|
|
||||||
case "toString":
|
|
||||||
return "@" + annotationType.getName() + "()";
|
|
||||||
default:
|
|
||||||
return method.invoke(proxy, args);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Returns true if {@code a} and {@code b} are equal. */
|
/** Returns true if {@code a} and {@code b} are equal. */
|
||||||
@CheckReturnValue public static boolean equals(@Nullable Type a, @Nullable Type b) {
|
public static boolean equals(@Nullable Type a, @Nullable Type b) {
|
||||||
if (a == b) {
|
if (a == b) {
|
||||||
return true; // Also handles (a == null && b == null).
|
return true; // Also handles (a == null && b == null).
|
||||||
|
|
||||||
@@ -260,51 +216,54 @@ public final class Types {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static int hashCodeOrZero(@Nullable Object o) {
|
@SuppressWarnings("unchecked")
|
||||||
return o != null ? o.hashCode() : 0;
|
static <T extends Annotation> T createJsonQualifierImplementation(final Class<T> annotationType) {
|
||||||
|
if (!annotationType.isAnnotation()) {
|
||||||
|
throw new IllegalArgumentException(annotationType + " must be an annotation.");
|
||||||
}
|
}
|
||||||
|
if (!annotationType.isAnnotationPresent(JsonQualifier.class)) {
|
||||||
static String typeToString(Type type) {
|
throw new IllegalArgumentException(annotationType + " must have @JsonQualifier.");
|
||||||
return type instanceof Class ? ((Class<?>) type).getName() : type.toString();
|
}
|
||||||
|
if (annotationType.getDeclaredMethods().length != 0) {
|
||||||
|
throw new IllegalArgumentException(annotationType + " must not declare methods.");
|
||||||
|
}
|
||||||
|
return (T) Proxy.newProxyInstance(annotationType.getClassLoader(),
|
||||||
|
new Class<?>[] { annotationType }, new InvocationHandler() {
|
||||||
|
@Override public Object invoke(Object proxy, Method method, Object[] args)
|
||||||
|
throws Throwable {
|
||||||
|
String methodName = method.getName();
|
||||||
|
switch (methodName) {
|
||||||
|
case "annotationType":
|
||||||
|
return annotationType;
|
||||||
|
case "equals":
|
||||||
|
Object o = args[0];
|
||||||
|
return annotationType.isInstance(o);
|
||||||
|
case "hashCode":
|
||||||
|
return 0;
|
||||||
|
case "toString":
|
||||||
|
return "@" + annotationType.getName() + "()";
|
||||||
|
default:
|
||||||
|
return method.invoke(proxy, args);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the generic supertype for {@code supertype}. For example, given a class {@code
|
* Returns a two element array containing this map's key and value types in positions 0 and 1
|
||||||
* IntegerSet}, the result for when supertype is {@code Set.class} is {@code Set<Integer>} and the
|
* respectively.
|
||||||
* result when the supertype is {@code Collection.class} is {@code Collection<Integer>}.
|
|
||||||
*/
|
*/
|
||||||
static Type getGenericSupertype(Type context, Class<?> rawType, Class<?> toResolve) {
|
static Type[] mapKeyAndValueTypes(Type context, Class<?> contextRawType) {
|
||||||
if (toResolve == rawType) {
|
// Work around a problem with the declaration of java.util.Properties. That class should extend
|
||||||
return context;
|
// Hashtable<String, String>, but it's declared to extend Hashtable<Object, Object>.
|
||||||
}
|
if (context == Properties.class) return new Type[] { String.class, String.class };
|
||||||
|
|
||||||
// we skip searching through interfaces if unknown is an interface
|
Type mapType = getSupertype(context, contextRawType, Map.class);
|
||||||
if (toResolve.isInterface()) {
|
if (mapType instanceof ParameterizedType) {
|
||||||
Class<?>[] interfaces = rawType.getInterfaces();
|
ParameterizedType mapParameterizedType = (ParameterizedType) mapType;
|
||||||
for (int i = 0, length = interfaces.length; i < length; i++) {
|
return mapParameterizedType.getActualTypeArguments();
|
||||||
if (interfaces[i] == toResolve) {
|
|
||||||
return rawType.getGenericInterfaces()[i];
|
|
||||||
} else if (toResolve.isAssignableFrom(interfaces[i])) {
|
|
||||||
return getGenericSupertype(rawType.getGenericInterfaces()[i], interfaces[i], toResolve);
|
|
||||||
}
|
}
|
||||||
}
|
return new Type[] { Object.class, Object.class };
|
||||||
}
|
|
||||||
|
|
||||||
// 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;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -339,126 +298,6 @@ public final class Types {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the element type of this collection type.
|
|
||||||
* @throws IllegalArgumentException if this type is not a collection.
|
|
||||||
*/
|
|
||||||
@CheckReturnValue
|
|
||||||
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.
|
|
||||||
*/
|
|
||||||
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 };
|
|
||||||
}
|
|
||||||
|
|
||||||
@CheckReturnValue // TODO(eric): Move this to internal Utils.
|
|
||||||
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
|
|
||||||
? new ParameterizedTypeImpl(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;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns true if this is a Type supported by {@link StandardJsonAdapters#FACTORY}.
|
* Returns true if this is a Type supported by {@link StandardJsonAdapters#FACTORY}.
|
||||||
*/
|
*/
|
||||||
@@ -474,178 +313,4 @@ public final class Types {
|
|||||||
|| type == String.class
|
|| type == String.class
|
||||||
|| type == Object.class;
|
|| type == Object.class;
|
||||||
}
|
}
|
||||||
|
|
||||||
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 @Nullable Class<?> declaringClassOf(TypeVariable<?> typeVariable) {
|
|
||||||
GenericDeclaration genericDeclaration = typeVariable.getGenericDeclaration();
|
|
||||||
return genericDeclaration instanceof Class ? (Class<?>) genericDeclaration : null;
|
|
||||||
}
|
|
||||||
|
|
||||||
static void checkNotPrimitive(Type type) {
|
|
||||||
if ((type instanceof Class<?>) && ((Class<?>) type).isPrimitive()) {
|
|
||||||
throw new IllegalArgumentException("Unexpected primitive " + type + ". Use the boxed type.");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static final class ParameterizedTypeImpl implements ParameterizedType {
|
|
||||||
private final @Nullable Type ownerType;
|
|
||||||
private final Type rawType;
|
|
||||||
final Type[] typeArguments;
|
|
||||||
|
|
||||||
ParameterizedTypeImpl(@Nullable Type ownerType, Type rawType, Type... typeArguments) {
|
|
||||||
// Require an owner type if the raw type needs it.
|
|
||||||
if (rawType instanceof Class<?>) {
|
|
||||||
Class<?> enclosingClass = ((Class<?>) rawType).getEnclosingClass();
|
|
||||||
if (ownerType != null) {
|
|
||||||
if (enclosingClass == null || Types.getRawType(ownerType) != enclosingClass) {
|
|
||||||
throw new IllegalArgumentException(
|
|
||||||
"unexpected owner type for " + rawType + ": " + ownerType);
|
|
||||||
}
|
|
||||||
} else if (enclosingClass != null) {
|
|
||||||
throw new IllegalArgumentException(
|
|
||||||
"unexpected owner type for " + rawType + ": null");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override public Type[] getActualTypeArguments() {
|
|
||||||
return typeArguments.clone();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override public Type getRawType() {
|
|
||||||
return rawType;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override public @Nullable 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;
|
|
||||||
|
|
||||||
GenericArrayTypeImpl(Type componentType) {
|
|
||||||
this.componentType = canonicalize(componentType);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override 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 @Nullable Type lowerBound;
|
|
||||||
|
|
||||||
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]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@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
|
|
||||||
&& 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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@@ -16,15 +16,29 @@
|
|||||||
package com.squareup.moshi.internal;
|
package com.squareup.moshi.internal;
|
||||||
|
|
||||||
import com.squareup.moshi.JsonQualifier;
|
import com.squareup.moshi.JsonQualifier;
|
||||||
|
import com.squareup.moshi.Types;
|
||||||
import java.lang.annotation.Annotation;
|
import java.lang.annotation.Annotation;
|
||||||
import java.lang.reflect.AnnotatedElement;
|
import java.lang.reflect.AnnotatedElement;
|
||||||
|
import java.lang.reflect.GenericArrayType;
|
||||||
|
import java.lang.reflect.GenericDeclaration;
|
||||||
|
import java.lang.reflect.ParameterizedType;
|
||||||
import java.lang.reflect.Type;
|
import java.lang.reflect.Type;
|
||||||
|
import java.lang.reflect.TypeVariable;
|
||||||
|
import java.lang.reflect.WildcardType;
|
||||||
|
import java.util.Arrays;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.LinkedHashSet;
|
import java.util.LinkedHashSet;
|
||||||
|
import java.util.NoSuchElementException;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
import javax.annotation.Nullable;
|
||||||
|
|
||||||
|
import static com.squareup.moshi.Types.arrayOf;
|
||||||
|
import static com.squareup.moshi.Types.subtypeOf;
|
||||||
|
import static com.squareup.moshi.Types.supertypeOf;
|
||||||
|
|
||||||
public final class Util {
|
public final class Util {
|
||||||
public static final Set<Annotation> NO_ANNOTATIONS = Collections.emptySet();
|
public static final Set<Annotation> NO_ANNOTATIONS = Collections.emptySet();
|
||||||
|
public static final Type[] EMPTY_TYPE_ARRAY = new Type[] {};
|
||||||
|
|
||||||
private Util() {
|
private Util() {
|
||||||
}
|
}
|
||||||
@@ -80,4 +94,340 @@ public final class Util {
|
|||||||
|| name.startsWith("kotlin.")
|
|| name.startsWith("kotlin.")
|
||||||
|| name.startsWith("scala.");
|
|| name.startsWith("scala.");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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) {
|
||||||
|
if (type instanceof ParameterizedTypeImpl) return type;
|
||||||
|
ParameterizedType p = (ParameterizedType) type;
|
||||||
|
return new ParameterizedTypeImpl(p.getOwnerType(),
|
||||||
|
p.getRawType(), p.getActualTypeArguments());
|
||||||
|
|
||||||
|
} else if (type instanceof GenericArrayType) {
|
||||||
|
if (type instanceof GenericArrayTypeImpl) return type;
|
||||||
|
GenericArrayType g = (GenericArrayType) type;
|
||||||
|
return new GenericArrayTypeImpl(g.getGenericComponentType());
|
||||||
|
|
||||||
|
} else if (type instanceof WildcardType) {
|
||||||
|
if (type instanceof WildcardTypeImpl) return type;
|
||||||
|
WildcardType w = (WildcardType) type;
|
||||||
|
return new WildcardTypeImpl(w.getUpperBounds(), w.getLowerBounds());
|
||||||
|
|
||||||
|
} else {
|
||||||
|
return type; // This type is unsupported!
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
||||||
|
? new ParameterizedTypeImpl(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;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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>}.
|
||||||
|
*/
|
||||||
|
public 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;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int hashCodeOrZero(@Nullable Object o) {
|
||||||
|
return o != null ? o.hashCode() : 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static String typeToString(Type type) {
|
||||||
|
return type instanceof Class ? ((Class<?>) type).getName() : type.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
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.
|
||||||
|
*/
|
||||||
|
static @Nullable Class<?> declaringClassOf(TypeVariable<?> typeVariable) {
|
||||||
|
GenericDeclaration genericDeclaration = typeVariable.getGenericDeclaration();
|
||||||
|
return genericDeclaration instanceof Class ? (Class<?>) genericDeclaration : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void checkNotPrimitive(Type type) {
|
||||||
|
if ((type instanceof Class<?>) && ((Class<?>) type).isPrimitive()) {
|
||||||
|
throw new IllegalArgumentException("Unexpected primitive " + type + ". Use the boxed type.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static final class ParameterizedTypeImpl implements ParameterizedType {
|
||||||
|
private final @Nullable Type ownerType;
|
||||||
|
private final Type rawType;
|
||||||
|
public final Type[] typeArguments;
|
||||||
|
|
||||||
|
public ParameterizedTypeImpl(@Nullable Type ownerType, Type rawType, Type... typeArguments) {
|
||||||
|
// Require an owner type if the raw type needs it.
|
||||||
|
if (rawType instanceof Class<?>) {
|
||||||
|
Class<?> enclosingClass = ((Class<?>) rawType).getEnclosingClass();
|
||||||
|
if (ownerType != null) {
|
||||||
|
if (enclosingClass == null || Types.getRawType(ownerType) != enclosingClass) {
|
||||||
|
throw new IllegalArgumentException(
|
||||||
|
"unexpected owner type for " + rawType + ": " + ownerType);
|
||||||
|
}
|
||||||
|
} else if (enclosingClass != null) {
|
||||||
|
throw new IllegalArgumentException(
|
||||||
|
"unexpected owner type for " + rawType + ": null");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override public Type[] getActualTypeArguments() {
|
||||||
|
return typeArguments.clone();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override public Type getRawType() {
|
||||||
|
return rawType;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override public @Nullable 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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static final class GenericArrayTypeImpl implements GenericArrayType {
|
||||||
|
private final Type componentType;
|
||||||
|
|
||||||
|
public GenericArrayTypeImpl(Type componentType) {
|
||||||
|
this.componentType = canonicalize(componentType);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override 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.
|
||||||
|
*/
|
||||||
|
public static final class WildcardTypeImpl implements WildcardType {
|
||||||
|
private final Type upperBound;
|
||||||
|
private final @Nullable 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]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@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
|
||||||
|
&& 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@@ -28,6 +28,7 @@ import java.util.Properties;
|
|||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
|
|
||||||
|
import static com.squareup.moshi.internal.Util.canonicalize;
|
||||||
import static java.lang.annotation.RetentionPolicy.RUNTIME;
|
import static java.lang.annotation.RetentionPolicy.RUNTIME;
|
||||||
import static org.assertj.core.api.Assertions.assertThat;
|
import static org.assertj.core.api.Assertions.assertThat;
|
||||||
import static org.junit.Assert.fail;
|
import static org.junit.Assert.fail;
|
||||||
@@ -174,7 +175,7 @@ public final class TypesTest {
|
|||||||
ParameterizedType ptype = (ParameterizedType) type;
|
ParameterizedType ptype = (ParameterizedType) type;
|
||||||
Type[] actualTypeArguments = ptype.getActualTypeArguments();
|
Type[] actualTypeArguments = ptype.getActualTypeArguments();
|
||||||
if (actualTypeArguments.length == 0) return null;
|
if (actualTypeArguments.length == 0) return null;
|
||||||
return Types.canonicalize(actualTypeArguments[0]);
|
return canonicalize(actualTypeArguments[0]);
|
||||||
}
|
}
|
||||||
|
|
||||||
Map<String, Integer> mapOfStringInteger;
|
Map<String, Integer> mapOfStringInteger;
|
||||||
|
Reference in New Issue
Block a user