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