You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@johnzon.apache.org by st...@apache.org on 2017/11/02 13:23:02 UTC

johnzon git commit: JOHNZON-143 add @JohnzonDeduplicateObjects support

Repository: johnzon
Updated Branches:
  refs/heads/master 52f0aab8e -> 2ca9baf80


JOHNZON-143 add @JohnzonDeduplicateObjects support

This allows to enable deduplicateObjects via an annotation on the root object.
See JOHNZON-135 for the underlying functionality.


Project: http://git-wip-us.apache.org/repos/asf/johnzon/repo
Commit: http://git-wip-us.apache.org/repos/asf/johnzon/commit/2ca9baf8
Tree: http://git-wip-us.apache.org/repos/asf/johnzon/tree/2ca9baf8
Diff: http://git-wip-us.apache.org/repos/asf/johnzon/diff/2ca9baf8

Branch: refs/heads/master
Commit: 2ca9baf80a47c698a32dbbd294d27bc2ded5a3cd
Parents: 52f0aab
Author: Mark Struberg <st...@apache.org>
Authored: Thu Nov 2 14:22:00 2017 +0100
Committer: Mark Struberg <st...@apache.org>
Committed: Thu Nov 2 14:22:00 2017 +0100

----------------------------------------------------------------------
 .../mapper/JohnzonDeduplicateObjects.java       | 39 +++++++++
 .../java/org/apache/johnzon/mapper/Mapper.java  | 24 ++++--
 .../johnzon/mapper/MappingGeneratorImpl.java    | 20 +++--
 .../johnzon/mapper/MappingParserImpl.java       | 35 +++++---
 .../org/apache/johnzon/mapper/Mappings.java     | 15 ++++
 .../johnzon/mapper/CircularObjectsTest.java     | 84 ++++++++++++++++++++
 6 files changed, 193 insertions(+), 24 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/johnzon/blob/2ca9baf8/johnzon-mapper/src/main/java/org/apache/johnzon/mapper/JohnzonDeduplicateObjects.java
----------------------------------------------------------------------
diff --git a/johnzon-mapper/src/main/java/org/apache/johnzon/mapper/JohnzonDeduplicateObjects.java b/johnzon-mapper/src/main/java/org/apache/johnzon/mapper/JohnzonDeduplicateObjects.java
new file mode 100644
index 0000000..cf95596
--- /dev/null
+++ b/johnzon-mapper/src/main/java/org/apache/johnzon/mapper/JohnzonDeduplicateObjects.java
@@ -0,0 +1,39 @@
+/*
+ * 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.mapper;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * Mark an Object to leverage object deduplication without having
+ * to explicitly enable it in the Mapper or JsonB Builder.
+ *
+ * The feature only gets activated if this annotation is available
+ * on the root object to be serialised/deserialised.
+ *
+ * @see MapperBuilder#setDeduplicateObjects(boolean)
+ */
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ElementType.TYPE})
+public @interface JohnzonDeduplicateObjects {
+    boolean value() default true;
+}

http://git-wip-us.apache.org/repos/asf/johnzon/blob/2ca9baf8/johnzon-mapper/src/main/java/org/apache/johnzon/mapper/Mapper.java
----------------------------------------------------------------------
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 7d411c3..29bf3fd 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
@@ -86,7 +86,7 @@ public class Mapper implements Closeable {
 
     public <T> void writeArray(final Collection<T> object, final Writer stream) {
         JsonGenerator generator = generatorFactory.createGenerator(stream(stream));
-        writeObject(object, generator, null, new JsonPointerTracker(null, "/"));
+        writeObject(object, generator, null, config.isDeduplicateObjects() ? new JsonPointerTracker(null, "/") : null);
     }
 
     public <T> void writeIterable(final Iterable<T> object, final OutputStream stream) {
@@ -126,16 +126,30 @@ public class Mapper implements Closeable {
         }
 
         final JsonGenerator generator = generatorFactory.createGenerator(stream(stream));
-        writeObject(object, generator, null, config.isDeduplicateObjects() ? new JsonPointerTracker(null, "/") : null);
+
+        writeObject(object, generator, null, isDeduplicateObjects(object.getClass()) ? new JsonPointerTracker(null, "/") : null);
+    }
+
+    private boolean isDeduplicateObjects(Class<?> rootType) {
+        Boolean dedup = null;
+        Mappings.ClassMapping classMapping = mappings.findOrCreateClassMapping(rootType);
+        if (classMapping != null) {
+            dedup = classMapping.isDeduplicateObjects();
+        }
+        if (dedup == null) {
+            dedup = config.isDeduplicateObjects();
+        }
+
+        return dedup;
     }
 
     public void writeObject(final Object object, final OutputStream stream) {
         final JsonGenerator generator = generatorFactory.createGenerator(stream(stream), config.getEncoding());
-        writeObject(object, generator, null, config.isDeduplicateObjects() ? new JsonPointerTracker(null, "/") : null);
+        writeObject(object, generator, null, isDeduplicateObjects(object.getClass()) ? new JsonPointerTracker(null, "/") : null);
     }
 
     private void writeObject(final Object object, final JsonGenerator generator, final Collection<String> ignored, JsonPointerTracker jsonPointer) {
-        MappingGeneratorImpl mappingGenerator = new MappingGeneratorImpl(config, generator, mappings);
+        MappingGeneratorImpl mappingGenerator = new MappingGeneratorImpl(config, generator, mappings, jsonPointer != null);
 
         Throwable originalException = null;
         try {
@@ -234,7 +248,7 @@ public class Mapper implements Closeable {
 
 
     private <T> T mapObject(final Type clazz, final JsonReader reader) {
-        return new MappingParserImpl(config, mappings, reader).readObject(clazz);
+        return new MappingParserImpl(config, mappings, reader, clazz).readObject(clazz);
     }
 
 

http://git-wip-us.apache.org/repos/asf/johnzon/blob/2ca9baf8/johnzon-mapper/src/main/java/org/apache/johnzon/mapper/MappingGeneratorImpl.java
----------------------------------------------------------------------
diff --git a/johnzon-mapper/src/main/java/org/apache/johnzon/mapper/MappingGeneratorImpl.java b/johnzon-mapper/src/main/java/org/apache/johnzon/mapper/MappingGeneratorImpl.java
index 7ecf2b3..1ed6a3d 100644
--- a/johnzon-mapper/src/main/java/org/apache/johnzon/mapper/MappingGeneratorImpl.java
+++ b/johnzon-mapper/src/main/java/org/apache/johnzon/mapper/MappingGeneratorImpl.java
@@ -37,15 +37,19 @@ public class MappingGeneratorImpl implements MappingGenerator {
     private final MapperConfig config;
     private final JsonGenerator generator;
     private final Mappings mappings;
+    
+    private final Boolean isDeduplicateObjects;
     private Map<Object, String> jsonPointers;
 
 
-    MappingGeneratorImpl(MapperConfig config, JsonGenerator jsonGenerator, final Mappings mappings) {
+    MappingGeneratorImpl(MapperConfig config, JsonGenerator jsonGenerator, final Mappings mappings, Boolean isDeduplicateObjects) {
         this.config = config;
         this.generator = jsonGenerator;
         this.mappings = mappings;
 
-        this.jsonPointers = config.isDeduplicateObjects() ?  new HashMap<>() : Collections.emptyMap();
+        this.isDeduplicateObjects = isDeduplicateObjects;
+
+        this.jsonPointers = isDeduplicateObjects ?  new HashMap<>() : Collections.emptyMap();
     }
 
     @Override
@@ -60,7 +64,7 @@ public class MappingGeneratorImpl implements MappingGenerator {
         } else if (object instanceof JsonValue) {
             generator.write((JsonValue) object);
         } else {
-            doWriteObject(object, generator, false, null, config.isDeduplicateObjects() ? new JsonPointerTracker(null, "/") : null);
+            doWriteObject(object, generator, false, null, isDeduplicateObjects ? new JsonPointerTracker(null, "/") : null);
         }
         return this;
     }
@@ -294,7 +298,7 @@ public class MappingGeneratorImpl implements MappingGenerator {
                         val,
                         getter.objectConverter,
                         getter.ignoreNested,
-                        config.isDeduplicateObjects() ? new JsonPointerTracker(jsonPointer, getterEntry.getKey()) : null);
+                        isDeduplicateObjects ? new JsonPointerTracker(jsonPointer, getterEntry.getKey()) : null);
             }
         }
 
@@ -344,7 +348,7 @@ public class MappingGeneratorImpl implements MappingGenerator {
                 if (valJsonPointer != null) {
                     writePrimitives(valJsonPointer);
                 } else {
-                    writeItem(itemConverter != null ? itemConverter.from(o) : o, ignoredProperties, config.isDeduplicateObjects() ? new JsonPointerTracker(jsonPointer, i) : null);
+                    writeItem(itemConverter != null ? itemConverter.from(o) : o, ignoredProperties, isDeduplicateObjects ? new JsonPointerTracker(jsonPointer, i) : null);
                 }
             }
             generator.writeEnd();
@@ -369,7 +373,7 @@ public class MappingGeneratorImpl implements MappingGenerator {
                         generator.writeEnd();
                     } else {
                         writeItem(itemConverter != null ? itemConverter.from(o) : o, ignoredProperties,
-                                config.isDeduplicateObjects() ? new JsonPointerTracker(jsonPointer, i) : null);
+                                isDeduplicateObjects ? new JsonPointerTracker(jsonPointer, i) : null);
                     }
                 }
                 i++;
@@ -428,7 +432,7 @@ public class MappingGeneratorImpl implements MappingGenerator {
                         if (t == null) {
                             generator.writeNull();
                         } else {
-                            writeItem(t, ignoredProperties, config.isDeduplicateObjects() ? new JsonPointerTracker(jsonPointer, i) : null);
+                            writeItem(t, ignoredProperties, isDeduplicateObjects ? new JsonPointerTracker(jsonPointer, i) : null);
                         }
                     }
                     generator.writeEnd();
@@ -460,7 +464,7 @@ public class MappingGeneratorImpl implements MappingGenerator {
                     if (t == null) {
                         generator.writeNull();
                     } else {
-                        writeItem(t, ignoredProperties, config.isDeduplicateObjects() ? new JsonPointerTracker(jsonPointer, i) : null);
+                        writeItem(t, ignoredProperties, isDeduplicateObjects ? new JsonPointerTracker(jsonPointer, i) : null);
                     }
                 }
                 i++;

http://git-wip-us.apache.org/repos/asf/johnzon/blob/2ca9baf8/johnzon-mapper/src/main/java/org/apache/johnzon/mapper/MappingParserImpl.java
----------------------------------------------------------------------
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 41731d4..ce604bd 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
@@ -34,6 +34,7 @@ import javax.json.JsonString;
 import javax.json.JsonStructure;
 import javax.json.JsonValue;
 import javax.xml.bind.DatatypeConverter;
+
 import java.lang.reflect.Array;
 import java.lang.reflect.InvocationTargetException;
 import java.lang.reflect.Method;
@@ -88,6 +89,7 @@ public class MappingParserImpl implements MappingParser {
 
     private final MapperConfig config;
     private final Mappings mappings;
+    private final boolean isDeduplicateObjects;
 
     private final JsonReader jsonReader;
 
@@ -99,7 +101,7 @@ public class MappingParserImpl implements MappingParser {
     private Map<String, Object> jsonPointers;
 
 
-    public MappingParserImpl(MapperConfig config, Mappings mappings, JsonReader jsonReader) {
+    public MappingParserImpl(MapperConfig config, Mappings mappings, JsonReader jsonReader, Type rootType) {
         this.config = config;
         this.mappings = mappings;
 
@@ -107,7 +109,18 @@ public class MappingParserImpl implements MappingParser {
 
         reverseAdaptersRegistry = new ConcurrentHashMap<>(config.getAdapters().size());
 
-        if (config.isDeduplicateObjects()) {
+
+        Boolean dedup = null;
+        Mappings.ClassMapping classMapping = mappings.findOrCreateClassMapping(rootType);
+        if (classMapping != null) {
+            dedup = classMapping.isDeduplicateObjects();
+        }
+        if (dedup == null) {
+            dedup = config.isDeduplicateObjects();
+        }
+        this.isDeduplicateObjects = dedup;
+
+        if (isDeduplicateObjects) {
             jsonPointers = new HashMap<>();
         } else {
             jsonPointers = Collections.emptyMap();
@@ -138,7 +151,7 @@ public class MappingParserImpl implements MappingParser {
             return (T) jsonValue;
         }
         if (JsonObject.class.isInstance(jsonValue)) {
-            return (T) buildObject(targetType, JsonObject.class.cast(jsonValue), applyObjectConverter, config.isDeduplicateObjects() ? new JsonPointerTracker(null, "/") : null);
+            return (T) buildObject(targetType, JsonObject.class.cast(jsonValue), applyObjectConverter, isDeduplicateObjects ? new JsonPointerTracker(null, "/") : null);
         }
         if (JsonString.class.isInstance(jsonValue) && (targetType == String.class || targetType == Object.class)) {
             return (T) JsonString.class.cast(jsonValue).getString();
@@ -168,7 +181,7 @@ public class MappingParserImpl implements MappingParser {
             if (Class.class.isInstance(targetType) && ((Class) targetType).isArray()) {
                 final Class componentType = ((Class) targetType).getComponentType();
                 return (T) buildArrayWithComponentType(jsonArray, componentType, config.findAdapter(componentType),
-                        config.isDeduplicateObjects() ? new JsonPointerTracker(null, "/") : null);
+                        isDeduplicateObjects ? new JsonPointerTracker(null, "/") : null);
             }
             if (ParameterizedType.class.isInstance(targetType)) {
 
@@ -180,11 +193,11 @@ public class MappingParserImpl implements MappingParser {
 
                 final Type arg = pt.getActualTypeArguments()[0];
                 return (T) mapCollection(mapping, jsonArray, Class.class.isInstance(arg) ? config.findAdapter(Class.class.cast(arg)) : null,
-                        null, config.isDeduplicateObjects() ? new JsonPointerTracker(null, "/") : null);
+                        null, isDeduplicateObjects ? new JsonPointerTracker(null, "/") : null);
             }
             if (Object.class == targetType) {
                 return (T) new ArrayList(asList(Object[].class.cast(buildArrayWithComponentType(jsonArray, Object.class, null,
-                        config.isDeduplicateObjects() ? new JsonPointerTracker(null, "/") : null))));
+                        isDeduplicateObjects ? new JsonPointerTracker(null, "/") : null))));
             }
         }
         if (JsonValue.NULL.equals(jsonValue)) {
@@ -305,7 +318,7 @@ public class MappingParserImpl implements MappingParser {
             t = classMapping.factory.create(createParameters(classMapping, object, jsonPointer));
         }
         // store the new object under it's jsonPointer in case it gets referenced later
-        if (config.isDeduplicateObjects()) {
+        if (isDeduplicateObjects) {
             jsonPointers.put(jsonPointer.toString(), t);
         }
 
@@ -348,7 +361,7 @@ public class MappingParserImpl implements MappingParser {
                 if (!classMapping.setters.containsKey(key)) {
                     try {
                         classMapping.anySetter.invoke(t, key, toValue(null, entry.getValue(), null, null, Object.class, null,
-                                config.isDeduplicateObjects() ? new JsonPointerTracker(jsonPointer, entry.getKey()) : null));
+                                isDeduplicateObjects ? new JsonPointerTracker(jsonPointer, entry.getKey()) : null));
                     } catch (final IllegalAccessException e) {
                         throw new IllegalStateException(e);
                     } catch (final InvocationTargetException e) {
@@ -602,7 +615,7 @@ public class MappingParserImpl implements MappingParser {
         int i = 0;
         for (final JsonValue value : jsonArray) {
             Array.set(array, i, toObject(null, value, componentType, itemConverter,
-                    config.isDeduplicateObjects() ? new JsonPointerTracker(jsonPointer, i) : null));
+                    isDeduplicateObjects ? new JsonPointerTracker(jsonPointer, i) : null));
             i++;
         }
         return array;
@@ -635,7 +648,7 @@ public class MappingParserImpl implements MappingParser {
             collection.add(JsonValue.NULL.equals(value)
                     ? null
                     : toValue(null, value, null, itemConverter, mapping.arg, objectConverter,
-                    config.isDeduplicateObjects() ? new JsonPointerTracker(jsonPointer, i) : null));
+                    isDeduplicateObjects ? new JsonPointerTracker(jsonPointer, i) : null));
             i++;
         }
 
@@ -667,7 +680,7 @@ public class MappingParserImpl implements MappingParser {
                     mapping.factory.getParameterItemConverter()[i],
                     mapping.factory.getParameterTypes()[i],
                     mapping.factory.getObjectConverter()[i],
-                    config.isDeduplicateObjects() ? new JsonPointerTracker(jsonPointer, paramName) : null); //X TODO ObjectConverter in @JOhnzonConverter with Constructors!
+                    isDeduplicateObjects ? new JsonPointerTracker(jsonPointer, paramName) : null); //X TODO ObjectConverter in @JOhnzonConverter with Constructors!
         }
 
         return objects;

http://git-wip-us.apache.org/repos/asf/johnzon/blob/2ca9baf8/johnzon-mapper/src/main/java/org/apache/johnzon/mapper/Mappings.java
----------------------------------------------------------------------
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 51ae62c..c8d63fe 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
@@ -65,6 +65,9 @@ public class Mappings {
         public final Getter anyGetter;
         public final Method anySetter;
 
+        private Boolean deduplicateObjects;
+        private boolean deduplicationEvaluated = false;
+
         protected ClassMapping(final Class<?> clazz, final AccessMode.Factory factory,
                                final Map<String, Getter> getters, final Map<String, Setter> setters,
                                final Adapter<?, ?> adapter,
@@ -80,6 +83,18 @@ public class Mappings {
             this.anyGetter = anyGetter;
             this.anySetter = anySetter;
         }
+
+        public Boolean isDeduplicateObjects() {
+            if (!deduplicationEvaluated) {
+                JohnzonDeduplicateObjects jdo = ((Class<JohnzonDeduplicateObjects>) clazz).getAnnotation(JohnzonDeduplicateObjects.class);
+                if (jdo != null){
+                    deduplicateObjects = jdo.value();
+                }
+                deduplicationEvaluated = true;
+            }
+            return deduplicateObjects;
+        }
+
     }
 
     public static class CollectionMapping {

http://git-wip-us.apache.org/repos/asf/johnzon/blob/2ca9baf8/johnzon-mapper/src/test/java/org/apache/johnzon/mapper/CircularObjectsTest.java
----------------------------------------------------------------------
diff --git a/johnzon-mapper/src/test/java/org/apache/johnzon/mapper/CircularObjectsTest.java b/johnzon-mapper/src/test/java/org/apache/johnzon/mapper/CircularObjectsTest.java
index c473c6d..b4c8af6 100644
--- a/johnzon-mapper/src/test/java/org/apache/johnzon/mapper/CircularObjectsTest.java
+++ b/johnzon-mapper/src/test/java/org/apache/johnzon/mapper/CircularObjectsTest.java
@@ -63,6 +63,34 @@ public class CircularObjectsTest {
     }
 
     @Test
+    public void testSimpleCyclicPersonAnnotatedDedup() {
+        DeduplicatedPerson john = new DeduplicatedPerson("John");
+        DeduplicatedPerson marry = new DeduplicatedPerson("Marry");
+
+        john.setMarriedTo(marry);
+        marry.setMarriedTo(john);
+
+        Mapper mapper = new MapperBuilder().setAccessModeName("field").build();
+        String ser = mapper.writeObjectAsString(john);
+
+        assertNotNull(ser);
+        assertTrue(ser.contains("\"name\":\"John\""));
+        assertTrue(ser.contains("\"marriedTo\":\"/\""));
+        assertTrue(ser.contains("\"name\":\"Marry\""));
+
+        // and now de-serialise it back
+        DeduplicatedPerson john2 = mapper.readObject(ser, DeduplicatedPerson.class);
+        assertNotNull(john2);
+        assertEquals("John", john2.getName());
+
+        DeduplicatedPerson marry2 = john2.getMarriedTo();
+        assertNotNull(marry2);
+        assertEquals("Marry", marry2.getName());
+
+        assertEquals(john2, marry2.getMarriedTo());
+    }
+
+    @Test
     public void testComplexCyclicPerson() {
         Person karl = new Person("Karl");
         Person andrea = new Person("Andrea");
@@ -211,4 +239,60 @@ public class CircularObjectsTest {
         }
     }
 
+    @JohnzonDeduplicateObjects
+    public static class DeduplicatedPerson {
+        private String name;
+        private DeduplicatedPerson marriedTo;
+        private DeduplicatedPerson mother;
+        private DeduplicatedPerson father;
+        private List<DeduplicatedPerson> kids = new ArrayList<>();
+
+        public DeduplicatedPerson() {
+        }
+
+        public DeduplicatedPerson(String name) {
+            this.name = name;
+        }
+
+        public String getName() {
+            return name;
+        }
+
+        public void setName(String name) {
+            this.name = name;
+        }
+
+        public DeduplicatedPerson getMarriedTo() {
+            return marriedTo;
+        }
+
+        public void setMarriedTo(DeduplicatedPerson marriedTo) {
+            this.marriedTo = marriedTo;
+        }
+
+        public DeduplicatedPerson getMother() {
+            return mother;
+        }
+
+        public void setMother(DeduplicatedPerson mother) {
+            this.mother = mother;
+        }
+
+        public DeduplicatedPerson getFather() {
+            return father;
+        }
+
+        public void setFather(DeduplicatedPerson father) {
+            this.father = father;
+        }
+
+        public List<DeduplicatedPerson> getKids() {
+            return kids;
+        }
+
+        public void setKids(List<DeduplicatedPerson> kids) {
+            this.kids = kids;
+        }
+    }
+
 }