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;
+ }
+ }
}