diff --git a/bson/src/main/org/bson/codecs/pojo/TypeData.java b/bson/src/main/org/bson/codecs/pojo/TypeData.java index 77511b11d5..c669a8e655 100644 --- a/bson/src/main/org/bson/codecs/pojo/TypeData.java +++ b/bson/src/main/org/bson/codecs/pojo/TypeData.java @@ -61,14 +61,8 @@ public static TypeData newInstance(final Field field) { } public static TypeData newInstance(final Type genericType, final Class clazz) { - TypeData.Builder builder = TypeData.builder(clazz); - if (genericType instanceof ParameterizedType) { - ParameterizedType pType = (ParameterizedType) genericType; - for (Type argType : pType.getActualTypeArguments()) { - getNestedTypeData(builder, argType); - } - } - return builder.build(); + // No enclosing class context: type variables have nothing to resolve against and erase to Object. + return newInstance(genericType, clazz, Collections.>emptyList(), null); } static TypeData newInstance(final Type genericParentType, final Class parentClass, @@ -110,32 +104,19 @@ private static TypeData resolveTypeArgument(final Type type, return TypeData.builder(Object.class).build(); } else if (type instanceof Class) { return TypeData.builder((Class) type).build(); + } else if (type instanceof WildcardType) { + // A wildcard cannot be the top-level type argument of an extends/implements clause (JLS §8.1.4, + // §8.1.5), but it can appear nested inside one (e.g. extends Base>) or in a + // field/method generic type. Resolve it to its upper bound so any type variable inside the bound is + // still substituted against the current context. + return resolveTypeArgument(((WildcardType) type).getUpperBounds()[0], currentClassTypeParameters, + currentClassTypeData); } else { - // WildcardType is intentionally not handled: wildcards cannot appear as type arguments - // in superclass or interface declarations (JLS §8.1.4, §8.1.5), so this method is - // never called with a WildcardType from getClassHierarchy. Falls through to Object below. + // Any other Type (e.g. GenericArrayType) is erased to Object. return TypeData.builder(Object.class).build(); } } - @SuppressWarnings({"unchecked", "rawtypes"}) - private static void getNestedTypeData(final TypeData.Builder builder, final Type type) { - if (type instanceof ParameterizedType) { - ParameterizedType pType = (ParameterizedType) type; - TypeData.Builder paramBuilder = TypeData.builder((Class) pType.getRawType()); - for (Type argType : pType.getActualTypeArguments()) { - getNestedTypeData(paramBuilder, argType); - } - builder.addTypeParameter(paramBuilder.build()); - } else if (type instanceof WildcardType) { - builder.addTypeParameter(TypeData.builder((Class) ((WildcardType) type).getUpperBounds()[0]).build()); - } else if (type instanceof TypeVariable) { - builder.addTypeParameter(TypeData.builder(Object.class).build()); - } else if (type instanceof Class) { - builder.addTypeParameter(TypeData.builder((Class) type).build()); - } - } - /** * @return the class this {@code ClassTypeData} represents */ diff --git a/bson/src/test/unit/org/bson/codecs/pojo/PojoRoundTripTest.java b/bson/src/test/unit/org/bson/codecs/pojo/PojoRoundTripTest.java index aa702b44c6..798f0781f1 100644 --- a/bson/src/test/unit/org/bson/codecs/pojo/PojoRoundTripTest.java +++ b/bson/src/test/unit/org/bson/codecs/pojo/PojoRoundTripTest.java @@ -34,6 +34,7 @@ import org.bson.codecs.pojo.entities.ForwardingInterfaceModel; import org.bson.codecs.pojo.entities.ForwardingModel; import org.bson.codecs.pojo.entities.ForwardingNestedModel; +import org.bson.codecs.pojo.entities.ForwardingWildcardModel; import org.bson.codecs.pojo.entities.GenericHolderModel; import org.bson.codecs.pojo.entities.GenericTreeModel; import org.bson.codecs.pojo.entities.InterfaceBasedModel; @@ -549,6 +550,13 @@ private static List testCases() { new ForwardingNestedModel(asList("a", "b", "c")), getPojoCodecProviderBuilder(ForwardingNestedModel.class), "{'value': ['a', 'b', 'c']}")); + data.add(new TestData("Forwarding nested wildcard chain resolves to List", + new ForwardingWildcardModel(asList(getShapeModelCircle(), getShapeModelRectangle())), + getPojoCodecProviderBuilder(ForwardingWildcardModel.class, ShapeModelAbstract.class, + ShapeModelCircle.class, ShapeModelRectangle.class), + "{'value': [{'_t': 'org.bson.codecs.pojo.entities.ShapeModelCircle', 'color': 'orange', 'radius': 4.2}, " + + "{'_t': 'org.bson.codecs.pojo.entities.ShapeModelRectangle', 'color': 'green', 'width': 22.1, " + + "'height': 105.0}]}")); return data; } diff --git a/bson/src/test/unit/org/bson/codecs/pojo/TypeDataTest.java b/bson/src/test/unit/org/bson/codecs/pojo/TypeDataTest.java index 78100a8464..e3455ea5e8 100644 --- a/bson/src/test/unit/org/bson/codecs/pojo/TypeDataTest.java +++ b/bson/src/test/unit/org/bson/codecs/pojo/TypeDataTest.java @@ -181,14 +181,29 @@ public void newInstanceWithEmptyTypeParamsReturnsRawTypeData() throws NoSuchFiel assertEquals(expected, actual); } + @Test + public void newInstanceResolvesBoundedWildcardToUpperBound() throws NoSuchFieldException { + Field boundedWildcardField = Holder.class.getDeclaredField("boundedWildcard"); + Type boundedWildcardGenericType = boundedWildcardField.getGenericType(); + + List> typeParams = asList(Holder.class.getTypeParameters()); + TypeData currentResolved = TypeData.builder(Holder.class) + .addTypeParameter(TypeData.builder(String.class).build()).build(); + + TypeData expected = TypeData.builder(List.class) + .addTypeParameter(TypeData.builder(Number.class).build()).build(); + TypeData actual = TypeData.newInstance(boundedWildcardGenericType, List.class, typeParams, currentResolved); + assertEquals(expected, actual); + } + private static class Holder { - T scalar; - List list; - List concrete; - Map nestedMap; + private List list; + private List concrete; + private Map nestedMap; + private List boundedWildcard; } private static class NonGeneric { - String s; + private String s; } } diff --git a/bson/src/test/unit/org/bson/codecs/pojo/entities/ForwardingWildcardModel.java b/bson/src/test/unit/org/bson/codecs/pojo/entities/ForwardingWildcardModel.java new file mode 100644 index 0000000000..1a68e08f86 --- /dev/null +++ b/bson/src/test/unit/org/bson/codecs/pojo/entities/ForwardingWildcardModel.java @@ -0,0 +1,29 @@ +/* + * Copyright 2008-present MongoDB, 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 org.bson.codecs.pojo.entities; + +import java.util.List; + +public class ForwardingWildcardModel extends ForwardingNestedBase> { + + public ForwardingWildcardModel() { + } + + public ForwardingWildcardModel(final List value) { + super(value); + } +}