Merge pull request #1391 from mcumings/issue1390
Fix issue with recursive type variable protections to fix #1390
This commit is contained in:
commit
d65960b001
@ -25,7 +25,12 @@ import java.lang.reflect.ParameterizedType;
|
|||||||
import java.lang.reflect.Type;
|
import java.lang.reflect.Type;
|
||||||
import java.lang.reflect.TypeVariable;
|
import java.lang.reflect.TypeVariable;
|
||||||
import java.lang.reflect.WildcardType;
|
import java.lang.reflect.WildcardType;
|
||||||
import java.util.*;
|
import java.util.Arrays;
|
||||||
|
import java.util.Collection;
|
||||||
|
import java.util.HashMap;
|
||||||
|
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.checkArgument;
|
||||||
import static com.google.gson.internal.$Gson$Preconditions.checkNotNull;
|
import static com.google.gson.internal.$Gson$Preconditions.checkNotNull;
|
||||||
@ -334,52 +339,61 @@ public final class $Gson$Types {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public static Type resolve(Type context, Class<?> contextRawType, Type toResolve) {
|
public static Type resolve(Type context, Class<?> contextRawType, Type toResolve) {
|
||||||
return resolve(context, contextRawType, toResolve, new HashSet<TypeVariable>());
|
return resolve(context, contextRawType, toResolve, new HashMap<TypeVariable, Type>());
|
||||||
}
|
}
|
||||||
|
|
||||||
private static Type resolve(Type context, Class<?> contextRawType, Type toResolve,
|
private static Type resolve(Type context, Class<?> contextRawType, Type toResolve,
|
||||||
Collection<TypeVariable> visitedTypeVariables) {
|
Map<TypeVariable, Type> visitedTypeVariables) {
|
||||||
// this implementation is made a little more complicated in an attempt to avoid object-creation
|
// this implementation is made a little more complicated in an attempt to avoid object-creation
|
||||||
|
TypeVariable resolving = null;
|
||||||
while (true) {
|
while (true) {
|
||||||
if (toResolve instanceof TypeVariable) {
|
if (toResolve instanceof TypeVariable) {
|
||||||
TypeVariable<?> typeVariable = (TypeVariable<?>) toResolve;
|
TypeVariable<?> typeVariable = (TypeVariable<?>) toResolve;
|
||||||
if (visitedTypeVariables.contains(typeVariable)) {
|
Type previouslyResolved = visitedTypeVariables.get(typeVariable);
|
||||||
|
if (previouslyResolved != null) {
|
||||||
// cannot reduce due to infinite recursion
|
// cannot reduce due to infinite recursion
|
||||||
return toResolve;
|
return (previouslyResolved == Void.TYPE) ? toResolve : previouslyResolved;
|
||||||
} else {
|
|
||||||
visitedTypeVariables.add(typeVariable);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Insert a placeholder to mark the fact that we are in the process of resolving this type
|
||||||
|
visitedTypeVariables.put(typeVariable, Void.TYPE);
|
||||||
|
if (resolving == null) {
|
||||||
|
resolving = typeVariable;
|
||||||
|
}
|
||||||
|
|
||||||
toResolve = resolveTypeVariable(context, contextRawType, typeVariable);
|
toResolve = resolveTypeVariable(context, contextRawType, typeVariable);
|
||||||
if (toResolve == typeVariable) {
|
if (toResolve == typeVariable) {
|
||||||
return toResolve;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
} else if (toResolve instanceof Class && ((Class<?>) toResolve).isArray()) {
|
} else if (toResolve instanceof Class && ((Class<?>) toResolve).isArray()) {
|
||||||
Class<?> original = (Class<?>) toResolve;
|
Class<?> original = (Class<?>) toResolve;
|
||||||
Type componentType = original.getComponentType();
|
Type componentType = original.getComponentType();
|
||||||
Type newComponentType = resolve(context, contextRawType, componentType, visitedTypeVariables);
|
Type newComponentType = resolve(context, contextRawType, componentType, visitedTypeVariables);
|
||||||
return componentType == newComponentType
|
toResolve = equal(componentType, newComponentType)
|
||||||
? original
|
? original
|
||||||
: arrayOf(newComponentType);
|
: arrayOf(newComponentType);
|
||||||
|
break;
|
||||||
|
|
||||||
} else if (toResolve instanceof GenericArrayType) {
|
} else if (toResolve instanceof GenericArrayType) {
|
||||||
GenericArrayType original = (GenericArrayType) toResolve;
|
GenericArrayType original = (GenericArrayType) toResolve;
|
||||||
Type componentType = original.getGenericComponentType();
|
Type componentType = original.getGenericComponentType();
|
||||||
Type newComponentType = resolve(context, contextRawType, componentType, visitedTypeVariables);
|
Type newComponentType = resolve(context, contextRawType, componentType, visitedTypeVariables);
|
||||||
return componentType == newComponentType
|
toResolve = equal(componentType, newComponentType)
|
||||||
? original
|
? original
|
||||||
: arrayOf(newComponentType);
|
: arrayOf(newComponentType);
|
||||||
|
break;
|
||||||
|
|
||||||
} else if (toResolve instanceof ParameterizedType) {
|
} else if (toResolve instanceof ParameterizedType) {
|
||||||
ParameterizedType original = (ParameterizedType) toResolve;
|
ParameterizedType original = (ParameterizedType) toResolve;
|
||||||
Type ownerType = original.getOwnerType();
|
Type ownerType = original.getOwnerType();
|
||||||
Type newOwnerType = resolve(context, contextRawType, ownerType, visitedTypeVariables);
|
Type newOwnerType = resolve(context, contextRawType, ownerType, visitedTypeVariables);
|
||||||
boolean changed = newOwnerType != ownerType;
|
boolean changed = !equal(newOwnerType, ownerType);
|
||||||
|
|
||||||
Type[] args = original.getActualTypeArguments();
|
Type[] args = original.getActualTypeArguments();
|
||||||
for (int t = 0, length = args.length; t < length; t++) {
|
for (int t = 0, length = args.length; t < length; t++) {
|
||||||
Type resolvedTypeArgument = resolve(context, contextRawType, args[t], visitedTypeVariables);
|
Type resolvedTypeArgument = resolve(context, contextRawType, args[t], visitedTypeVariables);
|
||||||
if (resolvedTypeArgument != args[t]) {
|
if (!equal(resolvedTypeArgument, args[t])) {
|
||||||
if (!changed) {
|
if (!changed) {
|
||||||
args = args.clone();
|
args = args.clone();
|
||||||
changed = true;
|
changed = true;
|
||||||
@ -388,9 +402,10 @@ public final class $Gson$Types {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return changed
|
toResolve = changed
|
||||||
? newParameterizedTypeWithOwner(newOwnerType, original.getRawType(), args)
|
? newParameterizedTypeWithOwner(newOwnerType, original.getRawType(), args)
|
||||||
: original;
|
: original;
|
||||||
|
break;
|
||||||
|
|
||||||
} else if (toResolve instanceof WildcardType) {
|
} else if (toResolve instanceof WildcardType) {
|
||||||
WildcardType original = (WildcardType) toResolve;
|
WildcardType original = (WildcardType) toResolve;
|
||||||
@ -400,20 +415,28 @@ public final class $Gson$Types {
|
|||||||
if (originalLowerBound.length == 1) {
|
if (originalLowerBound.length == 1) {
|
||||||
Type lowerBound = resolve(context, contextRawType, originalLowerBound[0], visitedTypeVariables);
|
Type lowerBound = resolve(context, contextRawType, originalLowerBound[0], visitedTypeVariables);
|
||||||
if (lowerBound != originalLowerBound[0]) {
|
if (lowerBound != originalLowerBound[0]) {
|
||||||
return supertypeOf(lowerBound);
|
toResolve = supertypeOf(lowerBound);
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
} else if (originalUpperBound.length == 1) {
|
} else if (originalUpperBound.length == 1) {
|
||||||
Type upperBound = resolve(context, contextRawType, originalUpperBound[0], visitedTypeVariables);
|
Type upperBound = resolve(context, contextRawType, originalUpperBound[0], visitedTypeVariables);
|
||||||
if (upperBound != originalUpperBound[0]) {
|
if (upperBound != originalUpperBound[0]) {
|
||||||
return subtypeOf(upperBound);
|
toResolve = subtypeOf(upperBound);
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return original;
|
toResolve = original;
|
||||||
|
break;
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
return toResolve;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// ensure that any in-process resolution gets updated with the final result
|
||||||
|
if (resolving != null) {
|
||||||
|
visitedTypeVariables.put(resolving, toResolve);
|
||||||
|
}
|
||||||
|
return toResolve;
|
||||||
}
|
}
|
||||||
|
|
||||||
static Type resolveTypeVariable(Type context, Class<?> contextRawType, TypeVariable<?> unknown) {
|
static Type resolveTypeVariable(Type context, Class<?> contextRawType, TypeVariable<?> unknown) {
|
||||||
|
@ -0,0 +1,54 @@
|
|||||||
|
package com.google.gson.functional;
|
||||||
|
|
||||||
|
import com.google.gson.Gson;
|
||||||
|
import com.google.gson.GsonBuilder;
|
||||||
|
import org.junit.Before;
|
||||||
|
import org.junit.Test;
|
||||||
|
|
||||||
|
import java.util.Collection;
|
||||||
|
import java.util.Iterator;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
import static org.junit.Assert.*;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This test covers the scenario described in #1390 where a type variable needs to be used
|
||||||
|
* by a type definition multiple times. Both type variable references should resolve to the
|
||||||
|
* same underlying concrete type.
|
||||||
|
*/
|
||||||
|
public class ReusedTypeVariablesFullyResolveTest {
|
||||||
|
|
||||||
|
private Gson gson;
|
||||||
|
|
||||||
|
@Before
|
||||||
|
public void setUp() {
|
||||||
|
gson = new GsonBuilder().create();
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("ConstantConditions") // The instances were being unmarshaled as Strings instead of TestEnums
|
||||||
|
@Test
|
||||||
|
public void testGenericsPreservation() {
|
||||||
|
TestEnumSetCollection withSet = gson.fromJson("{\"collection\":[\"ONE\",\"THREE\"]}", TestEnumSetCollection.class);
|
||||||
|
Iterator<TestEnum> iterator = withSet.collection.iterator();
|
||||||
|
assertNotNull(withSet);
|
||||||
|
assertNotNull(withSet.collection);
|
||||||
|
assertEquals(2, withSet.collection.size());
|
||||||
|
TestEnum first = iterator.next();
|
||||||
|
TestEnum second = iterator.next();
|
||||||
|
|
||||||
|
assertTrue(first instanceof TestEnum);
|
||||||
|
assertTrue(second instanceof TestEnum);
|
||||||
|
}
|
||||||
|
|
||||||
|
enum TestEnum { ONE, TWO, THREE }
|
||||||
|
|
||||||
|
private static class TestEnumSetCollection extends SetCollection<TestEnum> {}
|
||||||
|
|
||||||
|
private static class SetCollection<T> extends BaseCollection<T, Set<T>> {}
|
||||||
|
|
||||||
|
private static class BaseCollection<U, C extends Collection<U>>
|
||||||
|
{
|
||||||
|
public C collection;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user