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 12:53:27 UTC

[johnzon] branch master updated: JOHNZON-261 JOHNZON-260 better optional handling

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 cd66727  JOHNZON-261 JOHNZON-260 better optional handling
cd66727 is described below

commit cd66727ab3244223a9b6c69227e74c7f7e94edfb
Author: Romain Manni-Bucau <rm...@apache.org>
AuthorDate: Fri Aug 16 14:53:22 2019 +0200

    JOHNZON-261 JOHNZON-260 better optional handling
---
 .../org/apache/johnzon/jsonb/JohnzonBuilder.java   |  1 -
 .../org/apache/johnzon/jsonb/JsonbAccessMode.java  | 98 ++++++++++++++++------
 .../jsonb/reflect/GenericArrayTypeImpl.java        | 55 ++++++++++++
 .../java/org/apache/johnzon/mapper/Mapper.java     |  2 +-
 .../apache/johnzon/mapper/MappingParserImpl.java   | 13 ++-
 .../java/org/apache/johnzon/mapper/Mappings.java   | 24 +++++-
 6 files changed, 160 insertions(+), 33 deletions(-)

diff --git a/johnzon-jsonb/src/main/java/org/apache/johnzon/jsonb/JohnzonBuilder.java b/johnzon-jsonb/src/main/java/org/apache/johnzon/jsonb/JohnzonBuilder.java
index 6f7572e..03410df 100644
--- a/johnzon-jsonb/src/main/java/org/apache/johnzon/jsonb/JohnzonBuilder.java
+++ b/johnzon-jsonb/src/main/java/org/apache/johnzon/jsonb/JohnzonBuilder.java
@@ -629,7 +629,6 @@ public class JohnzonBuilder implements JsonbBuilder {
         }));
         addDateFormatConfigConverters(converters, zoneIDUTC);
 
-
         converters.forEach((k, v) -> builder.addAdapter(k.getFrom(), k.getTo(), v));
         return converters;
     }
diff --git a/johnzon-jsonb/src/main/java/org/apache/johnzon/jsonb/JsonbAccessMode.java b/johnzon-jsonb/src/main/java/org/apache/johnzon/jsonb/JsonbAccessMode.java
index b723229..d999e35 100644
--- a/johnzon-jsonb/src/main/java/org/apache/johnzon/jsonb/JsonbAccessMode.java
+++ b/johnzon-jsonb/src/main/java/org/apache/johnzon/jsonb/JsonbAccessMode.java
@@ -29,6 +29,7 @@ import java.io.IOException;
 import java.lang.annotation.Annotation;
 import java.lang.reflect.Constructor;
 import java.lang.reflect.Field;
+import java.lang.reflect.GenericArrayType;
 import java.lang.reflect.InvocationTargetException;
 import java.lang.reflect.Method;
 import java.lang.reflect.Modifier;
@@ -95,6 +96,7 @@ import org.apache.johnzon.jsonb.converter.JsonbNumberConverter;
 import org.apache.johnzon.jsonb.converter.JsonbValueConverter;
 import org.apache.johnzon.jsonb.converter.JsonbZonedDateTimeConverter;
 import org.apache.johnzon.jsonb.order.PerHierarchyAndLexicographicalOrderFieldComparator;
+import org.apache.johnzon.jsonb.reflect.GenericArrayTypeImpl;
 import org.apache.johnzon.jsonb.serializer.JohnzonDeserializationContext;
 import org.apache.johnzon.jsonb.serializer.JohnzonSerializationContext;
 import org.apache.johnzon.jsonb.spi.JohnzonAdapterFactory;
@@ -462,20 +464,41 @@ public class JsonbAccessMode implements AccessMode, Closeable {
             // handle optionals since mapper is still only java 7
             final Type type;
             final Function<Object, Object> reader;
-            if (isOptional(finalReader)) {
-                type = ParameterizedType.class.cast(finalReader.getType()).getActualTypeArguments()[0];
+            final Type readerType = finalReader.getType();
+            if (isOptional(readerType)) {
+                type = ParameterizedType.class.cast(readerType).getActualTypeArguments()[0];
                 reader = i -> ofNullable(finalReader.read(i)).map(o -> Optional.class.cast(o).orElse(null)).orElse(null);
-            } else if (OptionalInt.class == finalReader.getType()) {
-                type = int.class;
-                reader = i -> OptionalInt.class.cast(finalReader.read(i)).orElse(0);
-            } else if (OptionalLong.class == finalReader.getType()) {
-                type = long.class;
-                reader = i -> OptionalLong.class.cast(finalReader.read(i)).orElse(0);
-            } else if (OptionalDouble.class == finalReader.getType()) {
-                type = double.class;
-                reader = i -> OptionalDouble.class.cast(finalReader.read(i)).orElse(0);
+            } else if (OptionalInt.class == readerType) {
+                type = Integer.class;
+                reader = i -> {
+                    final OptionalInt optionalInt = OptionalInt.class.cast(finalReader.read(i));
+                    return optionalInt == null || !optionalInt.isPresent() ? null : optionalInt.getAsInt();
+                };
+            } else if (OptionalLong.class == readerType) {
+                type = Long.class;
+                reader = i -> {
+                    final OptionalLong optionalLong = OptionalLong.class.cast(finalReader.read(i));
+                    return optionalLong == null || !optionalLong.isPresent() ? null : optionalLong.getAsLong();
+                };
+            } else if (OptionalDouble.class == readerType) {
+                type = Double.class;
+                reader = i -> {
+                    final OptionalDouble optionalDouble = OptionalDouble.class.cast(finalReader.read(i));
+                    return optionalDouble == null || !optionalDouble.isPresent() ? null : optionalDouble.getAsDouble();
+                };
+            } else if (isOptionalArray(finalReader)) {
+                final Type optionalUnwrappedType = findOptionalType(GenericArrayType.class.cast(readerType).getGenericComponentType());
+                type = new GenericArrayTypeImpl(optionalUnwrappedType);
+                reader = i -> {
+                    final Object[] optionals = Object[].class.cast(finalReader.read(i));
+                    return optionals == null ?
+                            null : Stream.of(optionals)
+                                .map(Optional.class::cast)
+                                .map(o -> o.orElse(null))
+                                .toArray();
+                };
             } else {
-                type = finalReader.getType();
+                type = readerType;
                 reader = finalReader::read;
             }
 
@@ -566,20 +589,34 @@ public class JsonbAccessMode implements AccessMode, Closeable {
             // handle optionals since mapper is still only java 7
             final Type type;
             final BiConsumer<Object, Object> writer;
-            if (isOptional(initialWriter)) {
-                type = ParameterizedType.class.cast(initialWriter.getType()).getActualTypeArguments()[0];
+            final Type writerType = initialWriter.getType();
+            if (isOptional(writerType)) {
+                type = findOptionalType(writerType);
                 writer = (i, val) -> finalWriter.write(i, Optional.ofNullable(val));
-            } else if (OptionalInt.class == initialWriter.getType()) {
-                type = int.class;
-                writer = (i, val) -> finalWriter.write(i, OptionalInt.of(Number.class.cast(val).intValue()));
-            } else if (OptionalLong.class == initialWriter.getType()) {
-                type = long.class;
-                writer = (i, val) -> finalWriter.write(i, OptionalLong.of(Number.class.cast(val).longValue()));
-            } else if (OptionalDouble.class == initialWriter.getType()) {
-                type = double.class;
-                writer = (i, val) -> finalWriter.write(i, OptionalDouble.of(Number.class.cast(val).doubleValue()));
+            } else if (OptionalInt.class == writerType) {
+                type = Integer.class;
+                writer = (i, value) -> finalWriter.write(i, value == null ?
+                        OptionalInt.empty() : OptionalInt.of(Number.class.cast(value).intValue()));
+            } else if (OptionalLong.class == writerType) {
+                type = Long.class;
+                writer = (i, value) -> finalWriter.write(i, value == null ?
+                        OptionalLong.empty() : OptionalLong.of(Number.class.cast(value).longValue()));
+            } else if (OptionalDouble.class == writerType) {
+                type = Double.class;
+                writer = (i, value) -> finalWriter.write(i, value == null ?
+                        OptionalDouble.empty() : OptionalDouble.of(Number.class.cast(value).doubleValue()));
+            } else if (isOptionalArray(initialWriter)) {
+                final Type optionalUnwrappedType = findOptionalType(GenericArrayType.class.cast(writerType).getGenericComponentType());
+                type = new GenericArrayTypeImpl(optionalUnwrappedType);
+                writer = (i, value) -> {
+                    if (value != null) {
+                        finalWriter.write(i, Stream.of(Object[].class.cast(value))
+                            .map(Optional::ofNullable)
+                            .toArray(Optional[]::new));
+                    }
+                };
             } else {
-                type = initialWriter.getType();
+                type = writerType;
                 writer = finalWriter::write;
             }
 
@@ -703,8 +740,17 @@ public class JsonbAccessMode implements AccessMode, Closeable {
         return cache;
     }
 
-    private boolean isOptional(final DecoratedType value) {
-        return ParameterizedType.class.isInstance(value.getType()) && Optional.class == ParameterizedType.class.cast(value.getType()).getRawType();
+    private Type findOptionalType(final Type writerType) {
+        return ParameterizedType.class.cast(writerType).getActualTypeArguments()[0];
+    }
+
+    private boolean isOptional(final Type type) {
+        return ParameterizedType.class.isInstance(type) && Optional.class == ParameterizedType.class.cast(type).getRawType();
+    }
+
+    private boolean isOptionalArray(final DecoratedType value) {
+        return GenericArrayType.class.isInstance(value.getType()) &&
+                isOptional(GenericArrayType.class.cast(value.getType()).getGenericComponentType());
     }
 
     private boolean isTransient(final DecoratedType dt, final PropertyVisibilityStrategy visibility) {
diff --git a/johnzon-jsonb/src/main/java/org/apache/johnzon/jsonb/reflect/GenericArrayTypeImpl.java b/johnzon-jsonb/src/main/java/org/apache/johnzon/jsonb/reflect/GenericArrayTypeImpl.java
new file mode 100644
index 0000000..d0d1a4d
--- /dev/null
+++ b/johnzon-jsonb/src/main/java/org/apache/johnzon/jsonb/reflect/GenericArrayTypeImpl.java
@@ -0,0 +1,55 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you 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.apache.johnzon.jsonb.reflect;
+
+import java.lang.reflect.GenericArrayType;
+import java.lang.reflect.Type;
+
+public class GenericArrayTypeImpl implements GenericArrayType {
+    private final Type componentType;
+
+    public GenericArrayTypeImpl(final Type componentType) {
+        this.componentType = componentType;
+    }
+
+    @Override
+    public Type getGenericComponentType() {
+        return componentType;
+    }
+
+    @Override
+    public int hashCode() {
+        return componentType.hashCode();
+    }
+
+    @Override
+    public boolean equals(final Object obj) {
+        if (this == obj) {
+            return true;
+        } else if (obj instanceof GenericArrayType) {
+            return ((GenericArrayType) obj).getGenericComponentType().equals(componentType);
+        }
+        return false;
+
+    }
+
+    public String toString() {
+        return componentType + "[]";
+    }
+}
diff --git a/johnzon-mapper/src/main/java/org/apache/johnzon/mapper/Mapper.java b/johnzon-mapper/src/main/java/org/apache/johnzon/mapper/Mapper.java
index 18b7398..e8ae5cc 100644
--- a/johnzon-mapper/src/main/java/org/apache/johnzon/mapper/Mapper.java
+++ b/johnzon-mapper/src/main/java/org/apache/johnzon/mapper/Mapper.java
@@ -228,7 +228,7 @@ public class Mapper implements Closeable {
     }
 
     private void writeObject(final Object object, final JsonGenerator generator, final Collection<String> ignored, JsonPointerTracker jsonPointer) {
-        MappingGeneratorImpl mappingGenerator = new MappingGeneratorImpl(config, generator, mappings, jsonPointer != null);
+        final MappingGeneratorImpl mappingGenerator = new MappingGeneratorImpl(config, generator, mappings, jsonPointer != null);
         mappingGenerator.doWriteObject(object, generator, true, ignored, jsonPointer);
     }
 
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 a182a5a..e32b650 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
@@ -36,6 +36,7 @@ import javax.json.JsonStructure;
 import javax.json.JsonValue;
 
 import java.lang.reflect.Array;
+import java.lang.reflect.GenericArrayType;
 import java.lang.reflect.InvocationTargetException;
 import java.lang.reflect.Method;
 import java.lang.reflect.Modifier;
@@ -713,6 +714,16 @@ public class MappingParserImpl implements MappingParser {
             }
         }
 
+        if (GenericArrayType.class.isInstance(type)) {
+            Type genericComponentType = GenericArrayType.class.cast(type).getGenericComponentType();
+            while (ParameterizedType.class.isInstance(genericComponentType)) {
+                genericComponentType = ParameterizedType.class.cast(genericComponentType).getRawType();
+            }
+            if (Class.class.isInstance(genericComponentType)) {
+                return buildArrayWithComponentType(jsonArray, Class.class.cast(genericComponentType), itemConverter, jsonPointer, rootType);
+            } // else: fail for now
+        }
+
         if (Object.class == type) {
             return buildArray(ANY_LIST, jsonArray, null, null, jsonPointer, rootType);
         }
@@ -861,7 +872,7 @@ public class MappingParserImpl implements MappingParser {
         }
         if (Integer.class == componentType) {
             Integer[] array = new Integer[jsonArray.size()];
-            Integer i = 0;
+            int i = 0;
             for (final JsonValue value : jsonArray) {
                 array[i] = (Integer) toObject(null, value, componentType, itemConverter,
                         isDeduplicateObjects ? new JsonPointerTracker(jsonPointer, i) : null, rootType);
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 1e7ea18..1ecbe44 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,6 +29,7 @@ import org.apache.johnzon.mapper.reflection.JohnzonParameterizedType;
 
 import java.lang.annotation.Annotation;
 import java.lang.reflect.Array;
+import java.lang.reflect.GenericArrayType;
 import java.lang.reflect.Method;
 import java.lang.reflect.ParameterizedType;
 import java.lang.reflect.Type;
@@ -488,7 +489,9 @@ public class Mappings {
             final Type param = value.getType();
             final Class<?> returnType = Class.class.isInstance(param) ? Class.class.cast(param) : null;
             final Setter setter = new Setter(
-                    value, isPrimitive(param), returnType != null && returnType.isArray(), resolve(param, rootClass),
+                    value, isPrimitive(param),
+                    (returnType != null && returnType.isArray()) || GenericArrayType.class.isInstance(value.getType()),
+                    resolve(param, rootClass),
                     findConverter(copyDate, value), value.findObjectConverterReader(),
                     writeIgnore != null ? writeIgnore.minVersion() : -1);
             setters.put(key, setter);
@@ -506,7 +509,7 @@ public class Mappings {
             final Class<?> returnType = Class.class.isInstance(value.getType()) ? Class.class.cast(value.getType()) : null;
             final ParameterizedType pt = ParameterizedType.class.isInstance(value.getType()) ? ParameterizedType.class.cast(value.getType()) : null;
             final Getter getter = new Getter(value, returnType == Object.class, isPrimitive(returnType),
-                    returnType != null && returnType.isArray(),
+                    (returnType != null && returnType.isArray()) || GenericArrayType.class.isInstance(value.getType()),
                     (pt != null && Collection.class.isAssignableFrom(Class.class.cast(pt.getRawType())))
                             || (returnType != null && Collection.class.isAssignableFrom(returnType)),
                     (pt != null && Map.class.isAssignableFrom(Class.class.cast(pt.getRawType())))
@@ -710,15 +713,28 @@ public class Mappings {
         public MapUnwrapperWriter(final Map<String, Setter> writers, final String[] paths) {
             this.writers = writers;
             this.paths = paths;
-            this.componentTypes = new HashMap<String, Class<?>>();
+            this.componentTypes = new HashMap<>();
 
             for (final Map.Entry<String, Setter> setter : writers.entrySet()) {
                 if (setter.getValue().array) {
-                    componentTypes.put(setter.getKey(), Class.class.cast(setter.getValue().paramType).getComponentType());
+                    componentTypes.put(setter.getKey(),
+                            Class.class.isInstance(setter.getValue().paramType) ?
+                                    Class.class.cast(setter.getValue().paramType).getComponentType() :
+                                    cast(GenericArrayType.class.cast(setter.getValue().paramType).getGenericComponentType()));
                 }
             }
         }
 
+        private Class<?> cast(final Type genericComponentType) {
+            if (Class.class.isInstance(genericComponentType)) {
+                return Class.class.cast(genericComponentType);
+            }
+            if (ParameterizedType.class.isInstance(genericComponentType)) {
+                return cast(ParameterizedType.class.cast(genericComponentType).getRawType());
+            }
+            throw new UnsupportedOperationException("Unsupported type: " + genericComponentType);
+        }
+
         @Override
         public void write(final Object instance, final Object value) {
             Map<String, Object> nested = null;