You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@johnzon.apache.org by rm...@apache.org on 2019/08/16 14:12:37 UTC

[johnzon] branch master updated: JOHNZON-262 JOHNZON-263 JOHNZON-264 better support of generics for collections

This is an automated email from the ASF dual-hosted git repository.

rmannibucau pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/johnzon.git


The following commit(s) were added to refs/heads/master by this push:
     new 7c6f006  JOHNZON-262 JOHNZON-263 JOHNZON-264 better support of generics for collections
7c6f006 is described below

commit 7c6f006e566f08da4360b512518f432a7eb862a9
Author: Romain Manni-Bucau <rm...@apache.org>
AuthorDate: Fri Aug 16 16:12:30 2019 +0200

    JOHNZON-262 JOHNZON-263 JOHNZON-264 better support of generics for collections
---
 .../apache/johnzon/mapper/MappingParserImpl.java   |   7 ++
 .../java/org/apache/johnzon/mapper/Mappings.java   |  33 ++----
 .../apache/johnzon/mapper/reflection/Generics.java |  48 ++++++++-
 .../org/apache/johnzon/mapper/GenericsTest.java    | 112 +++++++++++++++++++++
 4 files changed, 171 insertions(+), 29 deletions(-)

diff --git a/johnzon-mapper/src/main/java/org/apache/johnzon/mapper/MappingParserImpl.java b/johnzon-mapper/src/main/java/org/apache/johnzon/mapper/MappingParserImpl.java
index e32b650..a2f388f 100644
--- a/johnzon-mapper/src/main/java/org/apache/johnzon/mapper/MappingParserImpl.java
+++ b/johnzon-mapper/src/main/java/org/apache/johnzon/mapper/MappingParserImpl.java
@@ -698,6 +698,13 @@ public class MappingParserImpl implements MappingParser {
                 final Class<?> componentType = clazz.getComponentType();
                 return buildArrayWithComponentType(jsonArray, componentType, itemConverter, jsonPointer, rootType);
             }
+            if (Collection.class.isAssignableFrom(clazz)) {
+                final Mappings.CollectionMapping mapping = mappings.findCollectionMapping(
+                        new JohnzonParameterizedType(clazz, Object.class), rootType);
+                if (mapping != null) {
+                    return mapCollection(mapping, jsonArray, itemConverter, objectConverter, jsonPointer, rootType);
+                }
+            }
         }
 
         if (ParameterizedType.class.isInstance(type)) {
diff --git a/johnzon-mapper/src/main/java/org/apache/johnzon/mapper/Mappings.java b/johnzon-mapper/src/main/java/org/apache/johnzon/mapper/Mappings.java
index 142fa3f..0da829f 100644
--- a/johnzon-mapper/src/main/java/org/apache/johnzon/mapper/Mappings.java
+++ b/johnzon-mapper/src/main/java/org/apache/johnzon/mapper/Mappings.java
@@ -29,7 +29,6 @@ import java.lang.reflect.GenericArrayType;
 import java.lang.reflect.Method;
 import java.lang.reflect.ParameterizedType;
 import java.lang.reflect.Type;
-import java.lang.reflect.TypeVariable;
 import java.math.BigDecimal;
 import java.math.BigInteger;
 import java.util.Collection;
@@ -328,7 +327,7 @@ public class Mappings {
             }
 
             final CollectionMapping mapping = new CollectionMapping(isPrimitive(fieldArgTypes[0]), collectionType,
-                    Generics.resolve(fieldArgTypes[0], Class.class.isInstance(root) ? Class.class.cast(root) : null));
+                    Generics.resolve(fieldArgTypes[0], root));
             collections.putIfAbsent(aType, mapping);
             return mapping;
         }
@@ -373,7 +372,7 @@ public class Mappings {
         if (classMapping == null) {
             if (ParameterizedType.class.isInstance(clazz)) {
                 final ParameterizedType pt = ParameterizedType.class.cast(clazz);
-                final ClassMapping mapping = doFindOrCreateClassMapping(pt.getRawType(), toResolvedTypes(pt));
+                final ClassMapping mapping = doFindOrCreateClassMapping(pt.getRawType(), Generics.toResolvedTypes(pt));
                 return putOrGetClassMapping(clazz, mapping);
             }
             if (!Class.class.isInstance(clazz)) {
@@ -397,26 +396,6 @@ public class Mappings {
         return classMapping;
     }
 
-    private Map<Type, Type> toResolvedTypes(final Type clazz) {
-        if (ParameterizedType.class.isInstance(clazz)) {
-            final ParameterizedType parameterizedType = ParameterizedType.class.cast(clazz);
-            if (!Class.class.isInstance(parameterizedType.getRawType())) {
-                return emptyMap(); // not yet supported
-            }
-            final Class<?> raw = Class.class.cast(parameterizedType.getRawType());
-            final Type[] arguments = parameterizedType.getActualTypeArguments();
-            if (arguments.length > 0) {
-                final TypeVariable<? extends Class<?>>[] parameters = raw.getTypeParameters();
-                final Map<Type, Type> map = new HashMap<>(parameters.length);
-                for (int i = 0; i < parameters.length && i < arguments.length; i++) {
-                    map.put(parameters[i], arguments[i]);
-                }
-                return map;
-            }
-        }
-        return emptyMap();
-    }
-
     private ClassMapping putOrGetClassMapping(final Type clazz, final ClassMapping classMapping) {
         if (classMapping == null) {
             return null;
@@ -526,7 +505,7 @@ public class Mappings {
             if (key.equals("metaClass")) {
                 return;
             }
-            final Type param = resolvedTypes.getOrDefault(value.getType(), value.getType());
+            final Type param = lookupType(value, resolvedTypes);
             final Class<?> returnType = Class.class.isInstance(param) ? Class.class.cast(param) : null;
             final Setter setter = new Setter(
                     value, isPrimitive(param),
@@ -538,6 +517,10 @@ public class Mappings {
         }
     }
 
+    private Type lookupType(final AccessMode.DecoratedType value, final Map<Type, Type> resolvedTypes) {
+        return resolvedTypes.getOrDefault(value.getType(), value.getType());
+    }
+
     private void addGetterIfNeeded(final Map<String, Getter> getters,
                                    final String key,
                                    final AccessMode.Reader value,
@@ -546,7 +529,7 @@ public class Mappings {
         final JohnzonIgnore readIgnore = value.getAnnotation(JohnzonIgnore.class);
         final JohnzonIgnoreNested ignoreNested = value.getAnnotation(JohnzonIgnoreNested.class);
         if (readIgnore == null || readIgnore.minVersion() >= 0) {
-            final Type type = resolvedTypes.getOrDefault(value.getType(), value.getType());
+            final Type type = lookupType(value, resolvedTypes);
             final Class<?> returnType = Class.class.isInstance(type) ? Class.class.cast(type) : null;
             final ParameterizedType pt = ParameterizedType.class.isInstance(type) ? ParameterizedType.class.cast(type) : null;
             final Getter getter = new Getter(value, returnType == Object.class, isPrimitive(returnType),
diff --git a/johnzon-mapper/src/main/java/org/apache/johnzon/mapper/reflection/Generics.java b/johnzon-mapper/src/main/java/org/apache/johnzon/mapper/reflection/Generics.java
index 8e06529..f79e82e 100644
--- a/johnzon-mapper/src/main/java/org/apache/johnzon/mapper/reflection/Generics.java
+++ b/johnzon-mapper/src/main/java/org/apache/johnzon/mapper/reflection/Generics.java
@@ -19,34 +19,71 @@
 package org.apache.johnzon.mapper.reflection;
 
 import static java.util.Arrays.asList;
+import static java.util.Collections.emptyMap;
 
 import java.lang.reflect.ParameterizedType;
 import java.lang.reflect.Type;
 import java.lang.reflect.TypeVariable;
+import java.lang.reflect.WildcardType;
 import java.util.ArrayList;
 import java.util.Collection;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.stream.Stream;
 
 public final class Generics {
     private Generics() {
         // no-op
     }
 
+    public static Map<Type, Type> toResolvedTypes(final Type clazz) {
+        if (ParameterizedType.class.isInstance(clazz)) {
+            final ParameterizedType parameterizedType = ParameterizedType.class.cast(clazz);
+            if (!Class.class.isInstance(parameterizedType.getRawType())) {
+                return emptyMap(); // not yet supported
+            }
+            final Class<?> raw = Class.class.cast(parameterizedType.getRawType());
+            final Type[] arguments = parameterizedType.getActualTypeArguments();
+            if (arguments.length > 0) {
+                final TypeVariable<? extends Class<?>>[] parameters = raw.getTypeParameters();
+                final Map<Type, Type> map = new HashMap<>(parameters.length);
+                for (int i = 0; i < parameters.length && i < arguments.length; i++) {
+                    map.put(parameters[i], arguments[i]);
+                }
+                return map;
+            }
+        }
+        return emptyMap();
+    }
+
     // todo: this piece of code needs to be enhanced a lot:
     // - better handling of the hierarchy
     // - wildcard support?
     // - cycle handling (Foo<Foo>)
     // - ....
-    public static Type resolve(final Type value, final Class<?> rootClass) {
+    public static Type resolve(final Type value, final Type rootClass) {
         if (TypeVariable.class.isInstance(value)) {
             return resolveTypeVariable(value, rootClass);
         }
         if (ParameterizedType.class.isInstance(value)) {
             return resolveParameterizedType(value, rootClass);
         }
+        if (WildcardType.class.isInstance(value)) {
+            return resolveWildcardType(value);
+        }
         return value;
     }
 
-    private static Type resolveParameterizedType(final Type value, final Class<?> rootClass) {
+    private static Type resolveWildcardType(final Type value) {
+        final WildcardType wildcardType = WildcardType.class.cast(value);
+        if (Stream.of(wildcardType.getUpperBounds()).anyMatch(it -> it == Object.class) &&
+                wildcardType.getLowerBounds().length == 0) {
+            return Object.class;
+        } // else todo
+        return value;
+    }
+
+    private static Type resolveParameterizedType(final Type value, final Type rootClass) {
         Collection<Type> args = null;
         final ParameterizedType parameterizedType = ParameterizedType.class.cast(value);
         int index = 0;
@@ -72,9 +109,9 @@ public final class Generics {
     }
 
     // for now the level is hardcoded to 2 with generic > concrete
-    private static Type resolveTypeVariable(final Type value, final Class<?> rootClass) {
+    private static Type resolveTypeVariable(final Type value, final Type rootClass) {
         final TypeVariable<?> tv = TypeVariable.class.cast(value);
-        Type parent = rootClass == null ? null : rootClass.getGenericSuperclass();
+        Type parent = rootClass;
         while (Class.class.isInstance(parent)) {
             parent = Class.class.cast(parent).getGenericSuperclass();
         }
@@ -92,6 +129,9 @@ public final class Generics {
                 return type;
             }
         }
+        if (Class.class.isInstance(rootClass)) {
+            return Object.class; // prefer a default over
+        }
         return value;
     }
 }
diff --git a/johnzon-mapper/src/test/java/org/apache/johnzon/mapper/GenericsTest.java b/johnzon-mapper/src/test/java/org/apache/johnzon/mapper/GenericsTest.java
index a5b0577..eaf831a 100644
--- a/johnzon-mapper/src/test/java/org/apache/johnzon/mapper/GenericsTest.java
+++ b/johnzon-mapper/src/test/java/org/apache/johnzon/mapper/GenericsTest.java
@@ -18,14 +18,79 @@
  */
 package org.apache.johnzon.mapper;
 
+import static java.util.Arrays.asList;
+import static java.util.Collections.singletonList;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNotNull;
 
+import java.lang.reflect.Type;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Queue;
+
 import org.junit.Test;
 import org.superbiz.Model;
 
 public class GenericsTest {
     @Test
+    public void multipleBounds() {
+        final LinkedList<String> list = new LinkedList<>(
+                Arrays.asList("Test 1", "Test 2"));
+        final HolderWithMultipleBounds<LinkedList<String>> wrapper = new HolderWithMultipleBounds<>();
+        wrapper.setInstance(new ArrayList<>());
+        wrapper.getInstance().add(list);
+
+        final Mapper mapper = new MapperBuilder().build();
+        final String json = mapper.writeObjectAsString(wrapper);
+        assertEquals("{\"instance\":[[\"Test 1\",\"Test 2\"]]}", json);
+
+        final Type type = new HolderWithMultipleBounds<LinkedList<String>>() {
+        }.getClass().getGenericSuperclass();
+        final HolderWithMultipleBounds deserialized = mapper
+                .readObject("{ \"instance\" : [[ \"Test 1\", \"Test 2\" ]] }", type);
+        assertEquals(wrapper.getInstance(), deserialized.getInstance());
+        mapper.close();
+    }
+
+    @Test
+    public void noVariable() {
+        final LinkedList<String> list = new LinkedList<>(
+                Arrays.asList("Test 1", "Test 2"));
+        final HolderWithMultipleBounds<LinkedList<String>> wrapper = new HolderWithMultipleBounds<>();
+        wrapper.setInstance(new ArrayList<>());
+        wrapper.getInstance().add(list);
+
+        final Mapper mapper = new MapperBuilder().build();
+        final String json = mapper.writeObjectAsString(wrapper);
+        assertEquals("{\"instance\":[[\"Test 1\",\"Test 2\"]]}", json);
+
+        final HolderWithMultipleBounds deserialized = mapper
+                .readObject("{ \"instance\" : [[ \"Test 1\", \"Test 2\" ]] }", HolderWithMultipleBounds.class);
+        assertEquals(wrapper.getInstance(), deserialized.getInstance());
+        mapper.close();
+    }
+
+    @Test
+    public void missingGeneric() {
+        final Mapper mapper = new MapperBuilder().build();
+        final ListHolder deserialized = mapper
+                .readObject("{ \"instance\" : [[ \"Test 1\", \"Test 2\" ]] }", ListHolder.class);
+        assertEquals(singletonList(asList("Test 1", "Test 2")), deserialized.getInstance());
+        mapper.close();
+    }
+
+    @Test
+    public void wildcardGeneric() {
+        final Mapper mapper = new MapperBuilder().build();
+        final WildcardListHolder deserialized = mapper
+                .readObject("{ \"instance\" : [[ \"Test 1\", \"Test 2\" ]] }", WildcardListHolder.class);
+        assertEquals(singletonList(asList("Test 1", "Test 2")), deserialized.getInstance());
+        mapper.close();
+    }
+
+    @Test
     public void typeVariableMultiLevel() {
         final String input = "{\"aalist\":[{\"detail\":\"something2\",\"name\":\"Na2\"}]," +
                 "\"childA\":{\"detail\":\"something\",\"name\":\"Na\"},\"childB\":{}}";
@@ -40,4 +105,51 @@ public class GenericsTest {
         assertEquals("something2", model.getAalist().iterator().next().detail);
         assertEquals(input, mapper.writeObjectAsString(model));
     }
+
+    public interface Holder<T> {
+        T getInstance();
+        void setInstance(T t);
+    }
+
+    public static class HolderWithMultipleBounds<T extends List & Queue> implements Holder<List<T>> {
+        protected List<T> instance;
+
+        @Override
+        public List<T> getInstance() {
+            return instance;
+        }
+
+        @Override
+        public void setInstance(final List<T> instance) {
+            this.instance = instance;
+        }
+    }
+
+    public static class ListHolder implements Holder<List> {
+        protected List instance;
+
+        @Override
+        public List getInstance() {
+            return instance;
+        }
+
+        @Override
+        public void setInstance(final List instance) {
+            this.instance = instance;
+        }
+    }
+
+    public static class WildcardListHolder implements Holder<List<?>> {
+        protected List<?> instance;
+
+        @Override
+        public List<?> getInstance() {
+            return instance;
+        }
+
+        @Override
+        public void setInstance(final List<?> instance) {
+            this.instance = instance;
+        }
+    }
 }