From 332fadae55ef88ce98bda8e091a598c51cab8f20 Mon Sep 17 00:00:00 2001 From: "James R. Perkins" Date: Mon, 3 Feb 2025 12:16:52 -0800 Subject: [PATCH] [599] Add better support for resolution of wildcard types. Signed-off-by: James R. Perkins --- .../yasson/internal/ReflectionUtils.java | 6 +- .../DeserializationModelCreator.java | 6 +- .../defaultmapping/generics/GenericsTest.java | 41 ++++++++++++- .../generics/model/CollectionContainer.java | 57 +++++++++++++++++ .../generics/model/CollectionElement.java | 48 +++++++++++++++ .../generics/model/ConstructorContainer.java | 57 +++++++++++++++++ .../model/StaticCreatorContainer.java | 61 +++++++++++++++++++ 7 files changed, 272 insertions(+), 4 deletions(-) create mode 100644 src/test/java/org/eclipse/yasson/defaultmapping/generics/model/CollectionContainer.java create mode 100644 src/test/java/org/eclipse/yasson/defaultmapping/generics/model/CollectionElement.java create mode 100644 src/test/java/org/eclipse/yasson/defaultmapping/generics/model/ConstructorContainer.java create mode 100644 src/test/java/org/eclipse/yasson/defaultmapping/generics/model/StaticCreatorContainer.java diff --git a/src/main/java/org/eclipse/yasson/internal/ReflectionUtils.java b/src/main/java/org/eclipse/yasson/internal/ReflectionUtils.java index 66ccac94..49c5e7a3 100644 --- a/src/main/java/org/eclipse/yasson/internal/ReflectionUtils.java +++ b/src/main/java/org/eclipse/yasson/internal/ReflectionUtils.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2015, 2023 Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2025 Oracle and/or its affiliates. All rights reserved. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at @@ -190,6 +190,10 @@ public static Type resolveItemVariableType(List chain, TypeVariable typ if (tmp != null) { returnType = tmp; } + // If the type is a WildcardType we need to resolve the most specific type + if (returnType instanceof WildcardType) { + return resolveMostSpecificBound(chain, (WildcardType) returnType, warn); + } if (!(returnType instanceof TypeVariable)) { break; } diff --git a/src/main/java/org/eclipse/yasson/internal/deserializer/DeserializationModelCreator.java b/src/main/java/org/eclipse/yasson/internal/deserializer/DeserializationModelCreator.java index 0ac3f980..433a9147 100644 --- a/src/main/java/org/eclipse/yasson/internal/deserializer/DeserializationModelCreator.java +++ b/src/main/java/org/eclipse/yasson/internal/deserializer/DeserializationModelCreator.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2021, 2023 Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2025 Oracle and/or its affiliates. All rights reserved. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at @@ -34,6 +34,8 @@ import java.util.function.Function; import java.util.stream.Collectors; +import jakarta.json.bind.Jsonb; +import jakarta.json.bind.JsonbBuilder; import jakarta.json.bind.JsonbException; import jakarta.json.bind.config.BinaryDataStrategy; import jakarta.json.bind.config.PropertyNamingStrategy; @@ -233,7 +235,7 @@ private ModelDeserializer createObjectDeserializer(LinkedList if (creatorModel.getCustomization().isRequired()) { defaultCreatorValues.put(parameterName, new RequiredCreatorParameter(parameterName)); } else { - Class rawParamType = ReflectionUtils.getRawType(creatorModel.getType()); + Class rawParamType = ReflectionUtils.getOptionalRawType(creatorModel.getType()).orElse(Object.class); defaultCreatorValues.put(parameterName, DEFAULT_CREATOR_VALUES.getOrDefault(rawParamType, NULL_PROVIDER)); } } diff --git a/src/test/java/org/eclipse/yasson/defaultmapping/generics/GenericsTest.java b/src/test/java/org/eclipse/yasson/defaultmapping/generics/GenericsTest.java index a1bf7f2b..8057a803 100644 --- a/src/test/java/org/eclipse/yasson/defaultmapping/generics/GenericsTest.java +++ b/src/test/java/org/eclipse/yasson/defaultmapping/generics/GenericsTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2015, 2023 Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2025 Oracle and/or its affiliates. All rights reserved. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at @@ -13,6 +13,7 @@ package org.eclipse.yasson.defaultmapping.generics; import java.lang.reflect.Type; +import java.lang.reflect.WildcardType; import java.math.BigDecimal; import java.text.ParseException; import java.text.SimpleDateFormat; @@ -36,8 +37,11 @@ import org.eclipse.yasson.defaultmapping.generics.model.AnotherGenericTestClass; import org.eclipse.yasson.defaultmapping.generics.model.BoundedGenericClass; import org.eclipse.yasson.defaultmapping.generics.model.Circle; +import org.eclipse.yasson.defaultmapping.generics.model.CollectionContainer; +import org.eclipse.yasson.defaultmapping.generics.model.CollectionElement; import org.eclipse.yasson.defaultmapping.generics.model.CollectionWrapper; import org.eclipse.yasson.defaultmapping.generics.model.ColoredCircle; +import org.eclipse.yasson.defaultmapping.generics.model.ConstructorContainer; import org.eclipse.yasson.defaultmapping.generics.model.CyclicSubClass; import org.eclipse.yasson.defaultmapping.generics.model.FinalGenericWrapper; import org.eclipse.yasson.defaultmapping.generics.model.FinalMember; @@ -49,6 +53,7 @@ import org.eclipse.yasson.defaultmapping.generics.model.MyCyclicGenericClass; import org.eclipse.yasson.defaultmapping.generics.model.PropagatedGenericClass; import org.eclipse.yasson.defaultmapping.generics.model.Shape; +import org.eclipse.yasson.defaultmapping.generics.model.StaticCreatorContainer; import org.eclipse.yasson.defaultmapping.generics.model.WildCardClass; import org.eclipse.yasson.defaultmapping.generics.model.WildcardMultipleBoundsClass; import org.eclipse.yasson.serializers.model.Box; @@ -474,6 +479,40 @@ public void lowerBoundTypeVariableInCollectionAttribute() throws Exception { assertEquals(6, fromJson.get(0).field1); } + + @Test + public void genericConstructorCreator() { + final String expectedJson = "{\"value\":\"Test\"}"; + final ConstructorContainer container = new ConstructorContainer<>("Test"); + + assertEquals(expectedJson, defaultJsonb.toJson(container)); + assertEquals(container, defaultJsonb.fromJson(expectedJson, ConstructorContainer.class)); + } + + @Test + public void genericStaticCreator() { + final String expectedJson = "{\"value\":\"static\"}"; + final StaticCreatorContainer container = StaticCreatorContainer.create("static"); + + assertEquals(expectedJson, defaultJsonb.toJson(container)); + assertEquals(container, defaultJsonb.fromJson(expectedJson, StaticCreatorContainer.class)); + } + + @Test + public void wildcardCollectionContainer() { + final String expectedJson = "{\"collection\":{\"collection\":[{\"wrapped\":\"wrappedElement\"}]}}"; + final CollectionContainer collectionContainer = new CollectionContainer(); + final CollectionWrapper> collectionWrapper = new CollectionWrapper<>(); + final CollectionElement wildcardType = new CollectionElement<>(); + wildcardType.setWrapped("wrappedElement"); + final Collection> list = List.of(wildcardType); + collectionWrapper.setCollection(list); + collectionContainer.setCollection(collectionWrapper); + + assertEquals(expectedJson, defaultJsonb.toJson(collectionContainer)); + final CollectionContainer result = defaultJsonb.fromJson(expectedJson, CollectionContainer.class); + assertEquals(collectionContainer, result); + } public interface FunctionalInterface { T getValue(); diff --git a/src/test/java/org/eclipse/yasson/defaultmapping/generics/model/CollectionContainer.java b/src/test/java/org/eclipse/yasson/defaultmapping/generics/model/CollectionContainer.java new file mode 100644 index 00000000..4ef94d0c --- /dev/null +++ b/src/test/java/org/eclipse/yasson/defaultmapping/generics/model/CollectionContainer.java @@ -0,0 +1,57 @@ +/* + * Copyright (c) 2025 Red Hat, Inc. and/or its affiliates. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, + * or the Eclipse Distribution License v. 1.0 which is available at + * http://www.eclipse.org/org/documents/edl-v10.php. + * + * SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause + */ + +package org.eclipse.yasson.defaultmapping.generics.model; + +import java.util.Collection; +import java.util.Objects; + +/** + * @author James R. Perkins + */ +public class CollectionContainer { + + private CollectionWrapper> collection; + + public CollectionWrapper> getCollection() { + return collection; + } + + public void setCollection(final CollectionWrapper> collection) { + this.collection = collection; + } + + @Override + public boolean equals(final Object obj) { + if (obj == this) { + return true; + } + if (!(obj instanceof CollectionContainer)) { + return false; + } + final CollectionContainer other = (CollectionContainer) obj; + final Collection> thisCollection = collection.getCollection(); + final Collection> otherCollection = other.collection.getCollection(); + if (thisCollection == null && otherCollection == null) { + return true; + } + if (thisCollection == null || otherCollection == null) { + return false; + } + return thisCollection.containsAll(otherCollection) && otherCollection.containsAll(thisCollection); + } + + @Override + public int hashCode() { + return Objects.hash(collection); + } +} diff --git a/src/test/java/org/eclipse/yasson/defaultmapping/generics/model/CollectionElement.java b/src/test/java/org/eclipse/yasson/defaultmapping/generics/model/CollectionElement.java new file mode 100644 index 00000000..2791dfc7 --- /dev/null +++ b/src/test/java/org/eclipse/yasson/defaultmapping/generics/model/CollectionElement.java @@ -0,0 +1,48 @@ +/* + * Copyright (c) 2025 Red Hat, Inc. and/or its affiliates. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, + * or the Eclipse Distribution License v. 1.0 which is available at + * http://www.eclipse.org/org/documents/edl-v10.php. + * + * SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause + */ + +package org.eclipse.yasson.defaultmapping.generics.model; + +import java.util.Objects; + +/** + * @author James R. Perkins + */ +public class CollectionElement { + + private T wrapped; + + public T getWrapped() { + return wrapped; + } + + public void setWrapped(T wrapped) { + this.wrapped = wrapped; + } + + @Override + public int hashCode() { + return Objects.hash(wrapped); + } + + @Override + public boolean equals(final Object obj) { + if (this == obj) { + return true; + } + if (!(obj instanceof CollectionElement)) { + return false; + } + final CollectionElement other = (CollectionElement) obj; + return Objects.equals(wrapped, other.wrapped); + } +} diff --git a/src/test/java/org/eclipse/yasson/defaultmapping/generics/model/ConstructorContainer.java b/src/test/java/org/eclipse/yasson/defaultmapping/generics/model/ConstructorContainer.java new file mode 100644 index 00000000..d2da4a2b --- /dev/null +++ b/src/test/java/org/eclipse/yasson/defaultmapping/generics/model/ConstructorContainer.java @@ -0,0 +1,57 @@ +/* + * Copyright (c) 2025 Red Hat, Inc. and/or its affiliates. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, + * or the Eclipse Distribution License v. 1.0 which is available at + * http://www.eclipse.org/org/documents/edl-v10.php. + * + * SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause + */ + +package org.eclipse.yasson.defaultmapping.generics.model; + +import java.util.Objects; + +import jakarta.json.bind.annotation.JsonbCreator; +import jakarta.json.bind.annotation.JsonbProperty; + +/** + * @author James R. Perkins + */ +public class ConstructorContainer { + + private final T value; + + @JsonbCreator + public ConstructorContainer(@JsonbProperty("value") final T value) { + this.value = value; + } + + public T getValue() { + return value; + } + + @Override + public String toString() { + return "ConstructorContainer[value=" + value + "]"; + } + + @Override + public int hashCode() { + return Objects.hash(value); + } + + @Override + public boolean equals(final Object obj) { + if (this == obj) { + return true; + } + if (!(obj instanceof ConstructorContainer)) { + return false; + } + final ConstructorContainer other = (ConstructorContainer) obj; + return Objects.equals(value, other.value); + } +} diff --git a/src/test/java/org/eclipse/yasson/defaultmapping/generics/model/StaticCreatorContainer.java b/src/test/java/org/eclipse/yasson/defaultmapping/generics/model/StaticCreatorContainer.java new file mode 100644 index 00000000..c090c5ff --- /dev/null +++ b/src/test/java/org/eclipse/yasson/defaultmapping/generics/model/StaticCreatorContainer.java @@ -0,0 +1,61 @@ +/* + * Copyright (c) 2025 Red Hat, Inc. and/or its affiliates. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, + * or the Eclipse Distribution License v. 1.0 which is available at + * http://www.eclipse.org/org/documents/edl-v10.php. + * + * SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause + */ + +package org.eclipse.yasson.defaultmapping.generics.model; + +import java.util.Objects; + +import jakarta.json.bind.annotation.JsonbCreator; +import jakarta.json.bind.annotation.JsonbProperty; + +/** + * @author James R. Perkins + */ +public class StaticCreatorContainer { + private final T value; + + private StaticCreatorContainer(T value) { + this.value = value; + } + + @JsonbCreator + public static StaticCreatorContainer create(@JsonbProperty("value") final T value) { + return new StaticCreatorContainer<>(value); + } + + public T getValue() { + return value; + } + + @Override + public String toString() { + return "StaticCreatorContainer[value=" + value + "]"; + } + + @Override + public int hashCode() { + return Objects.hash(value); + } + + @Override + public boolean equals(final Object obj) { + if (this == obj) { + return true; + } + if (!(obj instanceof StaticCreatorContainer)) { + return false; + } + final StaticCreatorContainer other = (StaticCreatorContainer) obj; + return Objects.equals(value, other.value); + } + +}