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