|
16 | 16 |
|
17 | 17 | package org.bson.codecs.pojo; |
18 | 18 |
|
| 19 | +import javax.annotation.Nullable; |
19 | 20 | import java.lang.reflect.Field; |
20 | 21 | import java.lang.reflect.Method; |
21 | 22 | import java.lang.reflect.ParameterizedType; |
@@ -61,32 +62,70 @@ public static TypeData<?> newInstance(final Field field) { |
61 | 62 | } |
62 | 63 |
|
63 | 64 | public static <T> TypeData<T> newInstance(final Type genericType, final Class<T> clazz) { |
64 | | - TypeData.Builder<T> builder = TypeData.builder(clazz); |
65 | | - if (genericType instanceof ParameterizedType) { |
66 | | - ParameterizedType pType = (ParameterizedType) genericType; |
| 65 | + // No enclosing class context: type variables have nothing to resolve against and erase to Object. |
| 66 | + return newInstance(genericType, clazz, Collections.<TypeVariable<?>>emptyList(), null); |
| 67 | + } |
| 68 | + |
| 69 | + static <T> TypeData<T> newInstance(final Type genericParentType, final Class<T> parentClass, |
| 70 | + final List<TypeVariable<?>> currentClassTypeParameters, |
| 71 | + @Nullable final TypeData<?> currentClassTypeData) { |
| 72 | + TypeData.Builder<T> builder = TypeData.builder(parentClass); |
| 73 | + if (genericParentType instanceof ParameterizedType) { |
| 74 | + ParameterizedType pType = (ParameterizedType) genericParentType; |
67 | 75 | for (Type argType : pType.getActualTypeArguments()) { |
68 | | - getNestedTypeData(builder, argType); |
| 76 | + builder.addTypeParameter(resolveTypeArgument(argType, currentClassTypeParameters, currentClassTypeData)); |
69 | 77 | } |
70 | 78 | } |
71 | 79 | return builder.build(); |
72 | 80 | } |
73 | 81 |
|
74 | 82 | @SuppressWarnings({"unchecked", "rawtypes"}) |
75 | | - private static <T> void getNestedTypeData(final TypeData.Builder<T> builder, final Type type) { |
| 83 | + private static TypeData<?> resolveTypeArgument(final Type type, |
| 84 | + final List<TypeVariable<?>> currentClassTypeParameters, |
| 85 | + @Nullable final TypeData<?> currentClassTypeData) { |
76 | 86 | if (type instanceof ParameterizedType) { |
77 | 87 | ParameterizedType pType = (ParameterizedType) type; |
78 | 88 | TypeData.Builder paramBuilder = TypeData.builder((Class) pType.getRawType()); |
79 | 89 | for (Type argType : pType.getActualTypeArguments()) { |
80 | | - getNestedTypeData(paramBuilder, argType); |
| 90 | + paramBuilder.addTypeParameter(resolveTypeArgument(argType, currentClassTypeParameters, currentClassTypeData)); |
81 | 91 | } |
82 | | - builder.addTypeParameter(paramBuilder.build()); |
83 | | - } else if (type instanceof WildcardType) { |
84 | | - builder.addTypeParameter(TypeData.builder((Class) ((WildcardType) type).getUpperBounds()[0]).build()); |
| 92 | + return paramBuilder.build(); |
85 | 93 | } else if (type instanceof TypeVariable) { |
86 | | - builder.addTypeParameter(TypeData.builder(Object.class).build()); |
| 94 | + return resolveTypeVariable((TypeVariable<?>) type, currentClassTypeParameters, currentClassTypeData); |
87 | 95 | } else if (type instanceof Class) { |
88 | | - builder.addTypeParameter(TypeData.builder((Class) type).build()); |
| 96 | + return TypeData.builder((Class) type).build(); |
| 97 | + } else if (type instanceof WildcardType) { |
| 98 | + // A wildcard cannot be the top-level type argument of an extends/implements clause (JLS §8.1.4, |
| 99 | + // §8.1.5), but it can appear nested inside one (e.g. extends Base<List<? extends Number>>) or in a |
| 100 | + // field/method generic type. Resolve it to its upper bound so any type variable inside the bound is |
| 101 | + // still substituted against the current context. |
| 102 | + return resolveTypeArgument(((WildcardType) type).getUpperBounds()[0], currentClassTypeParameters, |
| 103 | + currentClassTypeData); |
| 104 | + } else { |
| 105 | + // Any other Type (e.g. GenericArrayType) is erased to Object. |
| 106 | + return TypeData.builder(Object.class).build(); |
| 107 | + } |
| 108 | + } |
| 109 | + |
| 110 | + private static TypeData<?> resolveTypeVariable(final TypeVariable<?> type, final List<TypeVariable<?>> currentClassTypeParameters, |
| 111 | + @Nullable final TypeData<?> currentClassTypeData) { |
| 112 | + if (currentClassTypeData != null) { |
| 113 | + for (int i = 0; i < currentClassTypeParameters.size(); i++) { |
| 114 | + // Given 'class B<T> extends A<T> {}': |
| 115 | + // - JLS §6.3: the scope of B's type parameter T includes the superclass clause. |
| 116 | + // - JLS §6.5.5.1: T in "extends A<T>" therefore denotes B's T, not A's. |
| 117 | + // Both reflection paths represent the same declaration, and the TypeVariable |
| 118 | + // contract states "all instances representing a type variable must be equal() to |
| 119 | + // each other". |
| 120 | + if (currentClassTypeParameters.get(i).equals(type)) { |
| 121 | + if (i < currentClassTypeData.getTypeParameters().size()) { |
| 122 | + return currentClassTypeData.getTypeParameters().get(i); |
| 123 | + } |
| 124 | + break; |
| 125 | + } |
| 126 | + } |
89 | 127 | } |
| 128 | + return TypeData.builder(Object.class).build(); |
90 | 129 | } |
91 | 130 |
|
92 | 131 | /** |
|
0 commit comments