fix custom deserializer not working with generic types (#477)

support for generic type and type variable deserialization with custom deserializer

Signed-off-by: Alessandro Moscatelli <alessandro.moscatelli@visiontech.cloud>
diff --git a/src/main/java/org/eclipse/yasson/internal/ComponentMatcher.java b/src/main/java/org/eclipse/yasson/internal/ComponentMatcher.java
index 280a1e5..91c563b 100644
--- a/src/main/java/org/eclipse/yasson/internal/ComponentMatcher.java
+++ b/src/main/java/org/eclipse/yasson/internal/ComponentMatcher.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2016, 2020 Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2016, 2021 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
@@ -203,10 +203,10 @@
             }
         }
         
-        if (runtimeType instanceof Class) {
-            Class<?> runtimeClass = (Class<?>) runtimeType;
+        Optional<Class<?>> runtimeClass = ReflectionUtils.getOptionalRawType(runtimeType);
+        if (runtimeClass.isPresent()) {
             // Check if any interfaces have a match
-            for (Class<?> ifc : runtimeClass.getInterfaces()) {
+            for (Class<?> ifc : runtimeClass.get().getInterfaces()) {
                 ComponentBindings ifcBinding = userComponents.get(ifc);
                 if (ifcBinding != null) {
                   Optional<T> match = getMatchingBinding(ifc, ifcBinding, supplier);
@@ -217,7 +217,7 @@
             }
             
             // check if the superclass has a match
-            Class<?> superClass = runtimeClass.getSuperclass();
+            Class<?> superClass = runtimeClass.get().getSuperclass();
             if (superClass != null && superClass != Object.class) {
                 Optional<T> superBinding = searchComponentBinding(superClass, supplier);
                 if (superBinding.isPresent()) {
diff --git a/src/main/java/org/eclipse/yasson/internal/ReflectionUtils.java b/src/main/java/org/eclipse/yasson/internal/ReflectionUtils.java
index 4469e96..916ec56 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, 2020 Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2015, 2021 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
@@ -62,6 +62,20 @@
             return Optional.of((Class<?>) ((ParameterizedType) type).getRawType());
         } else if (type instanceof GenericArrayType) {
             return Optional.of(((GenericArrayType) type).getClass());
+        } else if (type instanceof TypeVariable) {
+            TypeVariable<?> typeVariable = TypeVariable.class.cast(type);
+            if (Objects.nonNull(typeVariable.getBounds())) {
+                Optional<Class<?>> specializedClass = Optional.empty();
+                for (Type bound : typeVariable.getBounds()) {
+                    Optional<Class<?>> boundRawType = getOptionalRawType(bound);
+                    if (boundRawType.isPresent() && !Object.class.equals(boundRawType.get())) {
+                        if (!specializedClass.isPresent() || specializedClass.get().isAssignableFrom(boundRawType.get())) {
+                            specializedClass = Optional.of(boundRawType.get());
+                        }
+                    }
+                }
+                return specializedClass;
+            }
         }
         return Optional.empty();
     }
@@ -152,6 +166,11 @@
      */
     static Type resolveItemVariableType(RuntimeTypeInfo item, TypeVariable<?> typeVariable, boolean warn) {
         if (item == null) {
+            Optional<Class<?>> optionalRawType = getOptionalRawType(typeVariable);
+            if (optionalRawType.isPresent()) {
+                return optionalRawType.get();
+            }
+            
             //Bound not found, treat it as an Object.class
             if (warn) {
                 LOGGER.warning(Messages.getMessage(MessageKeys.GENERIC_BOUND_NOT_FOUND,
diff --git a/src/main/java/org/eclipse/yasson/internal/VariableTypeInheritanceSearch.java b/src/main/java/org/eclipse/yasson/internal/VariableTypeInheritanceSearch.java
index 422ce78..0b4e28f 100644
--- a/src/main/java/org/eclipse/yasson/internal/VariableTypeInheritanceSearch.java
+++ b/src/main/java/org/eclipse/yasson/internal/VariableTypeInheritanceSearch.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2015, 2019 Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2015, 2021 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
@@ -96,7 +96,7 @@
 
     private Type searchRuntimeTypeArgument(ParameterizedType runtimeType, TypeVariable<?> typeVar) {
         if (ReflectionUtils.getRawType(runtimeType) != typeVar.getGenericDeclaration()) {
-            return null;
+            return ReflectionUtils.getOptionalRawType(typeVar).filter(rawType -> !Object.class.equals(rawType)).orElse(null);
         }
         TypeVariable[] bounds = typeVar.getGenericDeclaration().getTypeParameters();
         for (int i = 0; i < bounds.length; i++) {
diff --git a/src/test/java/org/eclipse/yasson/serializers/SerializersTest.java b/src/test/java/org/eclipse/yasson/serializers/SerializersTest.java
index b35f184..2093e87 100644
--- a/src/test/java/org/eclipse/yasson/serializers/SerializersTest.java
+++ b/src/test/java/org/eclipse/yasson/serializers/SerializersTest.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2016, 2020 Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2016, 2021 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
@@ -21,13 +21,18 @@
 import static org.junit.jupiter.api.Assertions.fail;
 
 import java.io.StringReader;
+import java.lang.reflect.ParameterizedType;
 import java.lang.reflect.Type;
 import java.math.BigDecimal;
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.Calendar;
+import java.util.Collection;
 import java.util.Date;
 import java.util.HashMap;
+import java.util.List;
 import java.util.Map;
+import java.util.Objects;
 import java.util.SortedMap;
 import java.util.TimeZone;
 import java.util.TreeMap;
@@ -69,6 +74,8 @@
 import org.eclipse.yasson.serializers.model.SimpleContainer;
 import org.eclipse.yasson.serializers.model.StringWrapper;
 import org.eclipse.yasson.serializers.model.SupertypeSerializerPojo;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertTrue;
 import org.junit.jupiter.api.Test;
 
 /**
@@ -596,5 +603,93 @@
       assertEquals("\"two\"", jsonb.toJson(new OneTwo()));
       assertEquals("\"two\"", jsonb.toJson(new OneTwoThree()));
     }
+
+    public class GenericBean<T> {
+
+        public T value;
+
+        @Override
+        public boolean equals(Object obj) {
+            if (obj instanceof GenericBean){
+                return Objects.equals(GenericBean.class.cast(obj).value, this.value);
+            }
+            return Boolean.FALSE;
+        }
+
+    }
+
+    public class GenericBeanSerializer implements JsonbSerializer<GenericBean> {
+
+        private Boolean called = Boolean.FALSE;
+
+        @Override
+        public void serialize(GenericBean t, JsonGenerator jg, SerializationContext sc) {
+            called = Boolean.TRUE;
+            jg.writeStartObject();
+            sc.serialize("value", t.value, jg);
+            jg.writeEnd();
+        }
+    }
+
+    public class GenericBeanDeserializer implements JsonbDeserializer<GenericBean> {
+
+        private Boolean called = Boolean.FALSE;
+
+        @Override
+        public GenericBean deserialize(JsonParser parser, DeserializationContext ctx, Type rtType) {
+            called = Boolean.TRUE;
+            JsonObject json = parser.getObject();
+            GenericBean<String> bean = new GenericBean<>();
+            bean.value = json.getString("value");
+            return bean;
+        }
+    }
+
+    @Test
+    public void testCustomDeserializerWithParameterizedType() {
+
+        GenericBeanSerializer genericBeanSerializer = new GenericBeanSerializer();
+        GenericBeanDeserializer genericBeanDeserializer = new GenericBeanDeserializer();
+
+        Jsonb jsonb = JsonbBuilder.create(new JsonbConfig().withDeserializers(genericBeanDeserializer).withSerializers(genericBeanSerializer));
+
+        GenericBean<String> bean1 = new GenericBean<>();
+        bean1.value = "test1";
+        GenericBean<String> bean2 = new GenericBean<>();
+        bean2.value = "test2";
+        GenericBean<String> bean3 = new GenericBean<>();
+        bean3.value = "test3";
+
+        Collection<GenericBean<String>> asList = Arrays.asList(bean1, bean2, bean3);
+
+        String toJson = jsonb.toJson(asList);
+
+        assertEquals(toJson, "[{\"value\":\"test1\"},{\"value\":\"test2\"},{\"value\":\"test3\"}]");
+        assertTrue(genericBeanSerializer.called);
+
+        List<GenericBean<String>> fromJson = jsonb.fromJson(
+                toJson,
+                new ParameterizedType() {
+            @Override
+            public Type[] getActualTypeArguments() {
+                return new Type[]{GenericBean.class};
+            }
+
+            @Override
+            public Type getRawType() {
+                return Collection.class;
+            }
+
+            @Override
+            public Type getOwnerType() {
+                return null;
+            }
+        }
+        );
+
+        assertEquals(asList, fromJson);
+        assertTrue(genericBeanDeserializer.called);
+
+    }
     
 }