Validate TypeToken.getParameterized
arguments (#2166)
This commit is contained in:
parent
0b6a7bf7d9
commit
98f2bbf4c1
@ -16,6 +16,9 @@
|
||||
|
||||
package com.google.gson.internal;
|
||||
|
||||
import static com.google.gson.internal.$Gson$Preconditions.checkArgument;
|
||||
import static com.google.gson.internal.$Gson$Preconditions.checkNotNull;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.lang.reflect.Array;
|
||||
import java.lang.reflect.GenericArrayType;
|
||||
@ -32,9 +35,6 @@ import java.util.Map;
|
||||
import java.util.NoSuchElementException;
|
||||
import java.util.Properties;
|
||||
|
||||
import static com.google.gson.internal.$Gson$Preconditions.checkArgument;
|
||||
import static com.google.gson.internal.$Gson$Preconditions.checkNotNull;
|
||||
|
||||
/**
|
||||
* Static methods for working with types.
|
||||
*
|
||||
@ -486,6 +486,7 @@ public final class $Gson$Types {
|
||||
private final Type[] typeArguments;
|
||||
|
||||
public ParameterizedTypeImpl(Type ownerType, Type rawType, Type... typeArguments) {
|
||||
checkNotNull(rawType);
|
||||
// require an owner type if the raw type needs it
|
||||
if (rawType instanceof Class<?>) {
|
||||
Class<?> rawTypeAsClass = (Class<?>) rawType;
|
||||
@ -552,6 +553,7 @@ public final class $Gson$Types {
|
||||
private final Type componentType;
|
||||
|
||||
public GenericArrayTypeImpl(Type componentType) {
|
||||
checkNotNull(componentType);
|
||||
this.componentType = canonicalize(componentType);
|
||||
}
|
||||
|
||||
|
@ -16,8 +16,8 @@
|
||||
|
||||
package com.google.gson.reflect;
|
||||
|
||||
import com.google.gson.internal.$Gson$Types;
|
||||
import com.google.gson.internal.$Gson$Preconditions;
|
||||
import com.google.gson.internal.$Gson$Types;
|
||||
import java.lang.reflect.GenericArrayType;
|
||||
import java.lang.reflect.ParameterizedType;
|
||||
import java.lang.reflect.Type;
|
||||
@ -319,8 +319,46 @@ public class TypeToken<T> {
|
||||
/**
|
||||
* Gets type literal for the parameterized type represented by applying {@code typeArguments} to
|
||||
* {@code rawType}.
|
||||
*
|
||||
* @throws IllegalArgumentException
|
||||
* If {@code rawType} is not of type {@code Class}, or if the type arguments are invalid for
|
||||
* the raw type
|
||||
*/
|
||||
public static TypeToken<?> getParameterized(Type rawType, Type... typeArguments) {
|
||||
$Gson$Preconditions.checkNotNull(rawType);
|
||||
$Gson$Preconditions.checkNotNull(typeArguments);
|
||||
|
||||
// Perform basic validation here because this is the only public API where users
|
||||
// can create malformed parameterized types
|
||||
if (!(rawType instanceof Class)) {
|
||||
// See also https://bugs.openjdk.org/browse/JDK-8250659
|
||||
throw new IllegalArgumentException("rawType must be of type Class, but was " + rawType);
|
||||
}
|
||||
Class<?> rawClass = (Class<?>) rawType;
|
||||
TypeVariable<?>[] typeVariables = rawClass.getTypeParameters();
|
||||
|
||||
int expectedArgsCount = typeVariables.length;
|
||||
int actualArgsCount = typeArguments.length;
|
||||
if (actualArgsCount != expectedArgsCount) {
|
||||
throw new IllegalArgumentException(rawClass.getName() + " requires " + expectedArgsCount +
|
||||
" type arguments, but got " + actualArgsCount);
|
||||
}
|
||||
|
||||
for (int i = 0; i < expectedArgsCount; i++) {
|
||||
Type typeArgument = typeArguments[i];
|
||||
Class<?> rawTypeArgument = $Gson$Types.getRawType(typeArgument);
|
||||
TypeVariable<?> typeVariable = typeVariables[i];
|
||||
|
||||
for (Type bound : typeVariable.getBounds()) {
|
||||
Class<?> rawBound = $Gson$Types.getRawType(bound);
|
||||
|
||||
if (!rawBound.isAssignableFrom(rawTypeArgument)) {
|
||||
throw new IllegalArgumentException("Type argument " + typeArgument + " does not satisfy bounds "
|
||||
+ "for type variable " + typeVariable + " declared by " + rawType);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return new TypeToken<>($Gson$Types.newParameterizedTypeWithOwner(null, rawType, typeArguments));
|
||||
}
|
||||
|
||||
|
@ -16,6 +16,7 @@
|
||||
|
||||
package com.google.gson.reflect;
|
||||
|
||||
import java.lang.reflect.GenericArrayType;
|
||||
import java.lang.reflect.Type;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
@ -91,6 +92,12 @@ public final class TypeTokenTest extends TestCase {
|
||||
TypeToken<?> expectedListOfStringArray = new TypeToken<List<String>[]>() {};
|
||||
Type listOfString = new TypeToken<List<String>>() {}.getType();
|
||||
assertEquals(expectedListOfStringArray, TypeToken.getArray(listOfString));
|
||||
|
||||
try {
|
||||
TypeToken.getArray(null);
|
||||
fail();
|
||||
} catch (NullPointerException e) {
|
||||
}
|
||||
}
|
||||
|
||||
public void testParameterizedFactory() {
|
||||
@ -104,6 +111,97 @@ public final class TypeTokenTest extends TestCase {
|
||||
Type listOfString = TypeToken.getParameterized(List.class, String.class).getType();
|
||||
Type listOfListOfString = TypeToken.getParameterized(List.class, listOfString).getType();
|
||||
assertEquals(expectedListOfListOfListOfString, TypeToken.getParameterized(List.class, listOfListOfString));
|
||||
|
||||
TypeToken<?> expectedWithExactArg = new TypeToken<GenericWithBound<Number>>() {};
|
||||
assertEquals(expectedWithExactArg, TypeToken.getParameterized(GenericWithBound.class, Number.class));
|
||||
|
||||
TypeToken<?> expectedWithSubclassArg = new TypeToken<GenericWithBound<Integer>>() {};
|
||||
assertEquals(expectedWithSubclassArg, TypeToken.getParameterized(GenericWithBound.class, Integer.class));
|
||||
|
||||
TypeToken<?> expectedSatisfyingTwoBounds = new TypeToken<GenericWithMultiBound<ClassSatisfyingBounds>>() {};
|
||||
assertEquals(expectedSatisfyingTwoBounds, TypeToken.getParameterized(GenericWithMultiBound.class, ClassSatisfyingBounds.class));
|
||||
}
|
||||
|
||||
public void testParameterizedFactory_Invalid() {
|
||||
try {
|
||||
TypeToken.getParameterized(null, new Type[0]);
|
||||
fail();
|
||||
} catch (NullPointerException e) {
|
||||
}
|
||||
|
||||
GenericArrayType arrayType = (GenericArrayType) TypeToken.getArray(String.class).getType();
|
||||
try {
|
||||
TypeToken.getParameterized(arrayType, new Type[0]);
|
||||
fail();
|
||||
} catch (IllegalArgumentException e) {
|
||||
assertEquals("rawType must be of type Class, but was java.lang.String[]", e.getMessage());
|
||||
}
|
||||
|
||||
try {
|
||||
TypeToken.getParameterized(String.class, String.class);
|
||||
fail();
|
||||
} catch (IllegalArgumentException e) {
|
||||
assertEquals("java.lang.String requires 0 type arguments, but got 1", e.getMessage());
|
||||
}
|
||||
|
||||
try {
|
||||
TypeToken.getParameterized(List.class, new Type[0]);
|
||||
fail();
|
||||
} catch (IllegalArgumentException e) {
|
||||
assertEquals("java.util.List requires 1 type arguments, but got 0", e.getMessage());
|
||||
}
|
||||
|
||||
try {
|
||||
TypeToken.getParameterized(List.class, String.class, String.class);
|
||||
fail();
|
||||
} catch (IllegalArgumentException e) {
|
||||
assertEquals("java.util.List requires 1 type arguments, but got 2", e.getMessage());
|
||||
}
|
||||
|
||||
try {
|
||||
TypeToken.getParameterized(GenericWithBound.class, String.class);
|
||||
fail();
|
||||
} catch (IllegalArgumentException e) {
|
||||
assertEquals("Type argument class java.lang.String does not satisfy bounds "
|
||||
+ "for type variable T declared by " + GenericWithBound.class,
|
||||
e.getMessage());
|
||||
}
|
||||
|
||||
try {
|
||||
TypeToken.getParameterized(GenericWithBound.class, Object.class);
|
||||
fail();
|
||||
} catch (IllegalArgumentException e) {
|
||||
assertEquals("Type argument class java.lang.Object does not satisfy bounds "
|
||||
+ "for type variable T declared by " + GenericWithBound.class,
|
||||
e.getMessage());
|
||||
}
|
||||
|
||||
try {
|
||||
TypeToken.getParameterized(GenericWithMultiBound.class, Number.class);
|
||||
fail();
|
||||
} catch (IllegalArgumentException e) {
|
||||
assertEquals("Type argument class java.lang.Number does not satisfy bounds "
|
||||
+ "for type variable T declared by " + GenericWithMultiBound.class,
|
||||
e.getMessage());
|
||||
}
|
||||
|
||||
try {
|
||||
TypeToken.getParameterized(GenericWithMultiBound.class, CharSequence.class);
|
||||
fail();
|
||||
} catch (IllegalArgumentException e) {
|
||||
assertEquals("Type argument interface java.lang.CharSequence does not satisfy bounds "
|
||||
+ "for type variable T declared by " + GenericWithMultiBound.class,
|
||||
e.getMessage());
|
||||
}
|
||||
|
||||
try {
|
||||
TypeToken.getParameterized(GenericWithMultiBound.class, Object.class);
|
||||
fail();
|
||||
} catch (IllegalArgumentException e) {
|
||||
assertEquals("Type argument class java.lang.Object does not satisfy bounds "
|
||||
+ "for type variable T declared by " + GenericWithMultiBound.class,
|
||||
e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
private static class CustomTypeToken extends TypeToken<String> {
|
||||
@ -158,3 +256,13 @@ public final class TypeTokenTest extends TestCase {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Have to declare these classes here as top-level classes because otherwise tests for
|
||||
// TypeToken.getParameterized fail due to owner type mismatch
|
||||
class GenericWithBound<T extends Number> {
|
||||
}
|
||||
class GenericWithMultiBound<T extends Number & CharSequence> {
|
||||
}
|
||||
@SuppressWarnings("serial")
|
||||
abstract class ClassSatisfyingBounds extends Number implements CharSequence {
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user