http://git-wip-us.apache.org/repos/asf/johnzon/blob/76fe13de/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/76fe13de/johnzon-mapper/src/main/java/org/apache/johnzon/mapper/JohnzonIgnoreNested.java ---------------------------------------------------------------------- diff --git a/johnzon-mapper/src/main/java/org/apache/johnzon/mapper/JohnzonIgnoreNested.java b/johnzon-mapper/src/main/java/org/apache/johnzon/mapper/JohnzonIgnoreNested.java new file mode 100644 index 0000000..f1d16a7 --- /dev/null +++ b/johnzon-mapper/src/main/java/org/apache/johnzon/mapper/JohnzonIgnoreNested.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.Retention; +import java.lang.annotation.Target; + +import static java.lang.annotation.ElementType.ANNOTATION_TYPE; +import static java.lang.annotation.ElementType.FIELD; +import static java.lang.annotation.ElementType.METHOD; +import static java.lang.annotation.RetentionPolicy.RUNTIME; + +/** + * Only used during serialization for now. + */ +@Target({ METHOD, FIELD, ANNOTATION_TYPE }) +@Retention(RUNTIME) +public @interface JohnzonIgnoreNested { + /** + * @return the array of properties to avoid in the nested type. + */ + String[] properties() default {}; +} http://git-wip-us.apache.org/repos/asf/johnzon/blob/76fe13de/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 58aed8b..0dc8a3c 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 @@ -18,6 +18,7 @@ */ package org.apache.johnzon.mapper; +import org.apache.johnzon.mapper.internal.JsonPointerTracker; import org.apache.johnzon.mapper.reflection.JohnzonCollectionType; import javax.json.JsonException; @@ -63,7 +64,7 @@ public class Mapper implements Closeable { this.mappings = new Mappings(config); this.readerHandler = ReaderHandler.create(readerFactory); this.closeables = closeables; - this.charset = config.getEncoding() == null ? null : config.getEncoding(); + this.charset = config.getEncoding(); } @@ -84,8 +85,14 @@ 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); + final JsonGenerator generator = generatorFactory.createGenerator(stream(stream)); + + try { + boolean dedup = Boolean.TRUE.equals(config.isDeduplicateObjects()); + writeObject(object, generator, null, dedup ? new JsonPointerTracker(null, "/") : null); + } finally { + generator.close(); + } } public <T> void writeIterable(final Iterable<T> object, final OutputStream stream) { @@ -93,8 +100,13 @@ public class Mapper implements Closeable { } public <T> void writeIterable(final Iterable<T> object, final Writer stream) { - JsonGenerator generator = generatorFactory.createGenerator(stream(stream)); - writeObject(object, generator); + final JsonGenerator generator = generatorFactory.createGenerator(stream(stream)); + try { + boolean dedup = Boolean.TRUE.equals(config.isDeduplicateObjects()); + writeObject(object, generator, null, dedup ? new JsonPointerTracker(null, "/") : null); + } finally { + generator.close(); + } } public void writeObject(final Object object, final Writer stream) { @@ -125,24 +137,12 @@ public class Mapper implements Closeable { } final JsonGenerator generator = generatorFactory.createGenerator(stream(stream)); - writeObject(object, generator); - } - - public void writeObject(final Object object, final OutputStream stream) { - final JsonGenerator generator = generatorFactory.createGenerator(stream(stream), config.getEncoding()); - writeObject(object, generator); - } - - private void writeObject(final Object object, final JsonGenerator generator) { - MappingGeneratorImpl mappingGenerator = new MappingGeneratorImpl(config, generator, mappings); - RuntimeException originalException = null; try { - mappingGenerator.doWriteObject(object, generator, true); + writeObject(object, generator); } catch (RuntimeException e) { originalException = e; } finally { - try { generator.close(); } catch (JsonException e) { @@ -154,7 +154,38 @@ public class Mapper implements Closeable { } } } + } + + private void writeObject(final Object object, final JsonGenerator generator) { + writeObject(object, generator, null, + isDeduplicateObjects(object.getClass()) ? new JsonPointerTracker(null, "/") : null); + } + + private boolean isDeduplicateObjects(Class<?> rootType) { + Boolean dedup = config.isDeduplicateObjects(); + if (dedup == null) { + Mappings.ClassMapping classMapping = mappings.findOrCreateClassMapping(rootType); + if (classMapping != null) { + dedup = classMapping.isDeduplicateObjects(); + } + } + + return dedup != null ? dedup : false; + } + + public void writeObject(final Object object, final OutputStream stream) { + Charset charset = config.getEncoding(); + if (charset == null) { + writeObject(object, new OutputStreamWriter(stream)); + } else { + writeObject(object, new OutputStreamWriter(stream)); + } + } + + private void writeObject(final Object object, final JsonGenerator generator, final Collection<String> ignored, JsonPointerTracker jsonPointer) { + MappingGeneratorImpl mappingGenerator = new MappingGeneratorImpl(config, generator, mappings, jsonPointer != null); + mappingGenerator.doWriteObject(object, generator, true, ignored, jsonPointer); } public String writeArrayAsString(final Collection<?> instance) { @@ -181,15 +212,33 @@ public class Mapper implements Closeable { } public <T> T readObject(final Reader stream, final Type clazz) { - return mapObject(clazz, readerFactory.createReader(stream(stream))); + final JsonReader reader = readerFactory.createReader(stream(stream)); + try { + return mapObject(clazz, reader); + } finally { + reader.close(); + } } public <T> T readObject(final InputStream stream, final Type clazz) { - return mapObject(clazz, charset == null ? readerFactory.createReader(stream(stream)): readerFactory.createReader(stream(stream), charset)); + final JsonReader reader = charset == null ? readerFactory.createReader(stream(stream)) : readerFactory.createReader( + stream(stream), charset); + + try { + return mapObject(clazz, reader); + } finally { + reader.close(); + } } public <T> Collection<T> readCollection(final InputStream stream, final ParameterizedType genericType) { - return mapObject(genericType, charset == null ? readerFactory.createReader(stream(stream)): readerFactory.createReader(stream(stream), charset)); + final JsonReader reader = charset == null ? readerFactory.createReader(stream(stream)): readerFactory.createReader(stream(stream), charset); + + try { + return mapObject(genericType, reader); + } finally { + reader.close(); + } } public <T> T readJohnzonCollection(final InputStream stream, final JohnzonCollectionType<T> genericType) { @@ -201,27 +250,51 @@ public class Mapper implements Closeable { } public <T> Collection<T> readCollection(final Reader stream, final ParameterizedType genericType) { - return mapObject(genericType, readerFactory.createReader(stream(stream))); + final JsonReader reader = readerFactory.createReader(stream(stream)); + + try { + return mapObject(genericType, reader); + } finally { + reader.close(); + } } public <T> T[] readArray(final Reader stream, final Class<T> clazz) { final JsonReader reader = readerFactory.createReader(stream(stream)); - return (T[]) mapArray(clazz, reader); + + try { + return (T[]) mapArray(clazz, reader); + } finally { + reader.close(); + } } public <T> T readTypedArray(final InputStream stream, final Class<?> elementType, final Class<T> arrayType) { final JsonReader reader = charset == null ? readerFactory.createReader(stream(stream)): readerFactory.createReader(stream(stream), charset); - return arrayType.cast(mapArray(elementType, reader)); + try { + return arrayType.cast(mapArray(elementType, reader)); + } finally { + reader.close(); + } } public <T> T readTypedArray(final Reader stream, final Class<?> elementType, final Class<T> arrayType) { final JsonReader reader = readerFactory.createReader(stream(stream)); - return arrayType.cast(mapArray(elementType, reader)); + + try { + return arrayType.cast(mapArray(elementType, reader)); + } finally { + reader.close(); + } } public <T> T[] readArray(final InputStream stream, final Class<T> clazz) { final JsonReader reader = charset == null ? readerFactory.createReader(stream(stream)): readerFactory.createReader(stream(stream), charset); - return (T[]) mapArray(clazz, reader); + try { + return (T[]) mapArray(clazz, reader); + } finally { + reader.close(); + } } private Object mapArray(final Class<?> clazz, final JsonReader reader) { @@ -230,7 +303,11 @@ public class Mapper implements Closeable { private <T> T mapObject(final Type clazz, final JsonReader reader) { - return new MappingParserImpl(config, mappings, reader).readObject(clazz); + boolean dedup = false; + if (clazz instanceof Class) { + dedup = isDeduplicateObjects((Class) clazz); + } + return new MappingParserImpl(config, mappings, reader, dedup).readObject(clazz); } @@ -242,10 +319,6 @@ public class Mapper implements Closeable { return !config.isClose() ? noClose(stream) : stream; } - private OutputStream stream(final OutputStream stream) { - return !config.isClose() ? noClose(stream) : stream; - } - private InputStream stream(final InputStream stream) { return !config.isClose() ? noClose(stream) : stream; } http://git-wip-us.apache.org/repos/asf/johnzon/blob/76fe13de/johnzon-mapper/src/main/java/org/apache/johnzon/mapper/MapperBuilder.java ---------------------------------------------------------------------- diff --git a/johnzon-mapper/src/main/java/org/apache/johnzon/mapper/MapperBuilder.java b/johnzon-mapper/src/main/java/org/apache/johnzon/mapper/MapperBuilder.java index b3e57eb..387f3e6 100644 --- a/johnzon-mapper/src/main/java/org/apache/johnzon/mapper/MapperBuilder.java +++ b/johnzon-mapper/src/main/java/org/apache/johnzon/mapper/MapperBuilder.java @@ -107,6 +107,7 @@ public class MapperBuilder { private int maxSize = -1; private int bufferSize = -1; private String bufferStrategy; + private boolean autoAdjustStringBuffers; private Comparator<String> attributeOrder = null; private boolean supportConstructors; private boolean useGetterForCollections; @@ -129,6 +130,10 @@ public class MapperBuilder { private Map<Class<?>, ObjectConverter.Writer<?>> objectConverterWriters = new HashMap<Class<?>, ObjectConverter.Writer<?>>(); private Map<Class<?>, String[]> ignoredForFields = new HashMap<Class<?>, String[]>(); private boolean primitiveConverters; + private boolean failOnUnknownProperties; + private SerializeValueFilter serializeValueFilter; + private boolean useBigDecimalForFloats; + private Boolean deduplicateObjects = null; public Mapper build() { if (readerFactory == null || generatorFactory == null) { @@ -155,6 +160,9 @@ public class MapperBuilder { if (bufferSize > 0) { config.put(JsonParserFactoryImpl.BUFFER_LENGTH, bufferSize); } + if (autoAdjustStringBuffers) { + config.put("org.apache.johnzon.auto-adjust-buffer", true); + } if (readerFactory == null) { readerFactory = provider.createReaderFactory(config); } @@ -215,10 +223,16 @@ public class MapperBuilder { version, close, skipNull, skipEmptyArray, treatByteArrayAsBase64, treatByteArrayAsBase64URL, readAttributeBeforeWrite, - accessMode, encoding, attributeOrder, enforceQuoteString), + accessMode, encoding, attributeOrder, enforceQuoteString, failOnUnknownProperties, + serializeValueFilter, useBigDecimalForFloats, deduplicateObjects), closeables); } + public MapperBuilder setFailOnUnknownProperties(final boolean failOnUnknownProperties) { + this.failOnUnknownProperties = failOnUnknownProperties; + return this; + } + public MapperBuilder addCloseable(final Closeable closeable) { closeables.add(closeable); return this; @@ -389,4 +403,56 @@ public class MapperBuilder { this.primitiveConverters = val; return this; } + + public MapperBuilder setSerializeValueFilter(final SerializeValueFilter serializeValueFilter) { + this.serializeValueFilter = serializeValueFilter; + return this; + } + + public MapperBuilder setUseBigDecimalForFloats(final boolean useBigDecimalForFloats) { + this.useBigDecimalForFloats = useBigDecimalForFloats; + return this; + } + + public MapperBuilder setAutoAdjustStringBuffers(final boolean autoAdjustStringBuffers) { + this.autoAdjustStringBuffers = autoAdjustStringBuffers; + return this; + } + + /** + * If any non-primitive Java Object gets serialised more than just one time, + * then we write a JsonPointer to the first occurrence instead. + * + * This will effectively also avoid endless loops in data with cycles! + * + * An example: Assume you have a Person with a name 'Sarah' and her daughter, + * a Person with the name 'Clemens' both stored in a JSON array. + * Given the Java Code: + * <pre> + * Person sarah = new Person("Sarah"); + * Person clemens = new Person("Clemens"); + * clemens.setMother(sarah); + * Person[] family = new Person[]{sarah, clemens}; + * </pre> + * Transformed to JSON this will now look like the following: + * <pre> + * [{"name":"Sarah"},{"name":"Clemens","mother":"/0"}] + * </pre> + * That means instead of serialising 'mother' as full object we will + * now only store a JsonPointer to the Person 'Sarah'. + * + * When deserialised back Johnzon will automatically de-reference the JsonPointer + * back to the correct instance. + * + * Possible values: + * <ul> + * <li>{@code true}: deduplicate objects</li> + * <li>{@code false}: do <b>not</b> deduplicate objects</li> + * <li>{@code null}: dedupliate based on the {@link JohnzonDeduplicateObjects} annotation. This is the default</li> + * </ul> + */ + public MapperBuilder setDeduplicateObjects(Boolean deduplicateObjects) { + this.deduplicateObjects = deduplicateObjects; + return this; + } } http://git-wip-us.apache.org/repos/asf/johnzon/blob/76fe13de/johnzon-mapper/src/main/java/org/apache/johnzon/mapper/MapperConfig.java ---------------------------------------------------------------------- diff --git a/johnzon-mapper/src/main/java/org/apache/johnzon/mapper/MapperConfig.java b/johnzon-mapper/src/main/java/org/apache/johnzon/mapper/MapperConfig.java index efb38f6..3cbb97e 100644 --- a/johnzon-mapper/src/main/java/org/apache/johnzon/mapper/MapperConfig.java +++ b/johnzon-mapper/src/main/java/org/apache/johnzon/mapper/MapperConfig.java @@ -63,6 +63,10 @@ public /* DON'T MAKE IT HIDDEN */ class MapperConfig implements Cloneable { private final Map<Class<?>, ObjectConverter.Reader<?>> objectConverterReaders; private final Comparator<String> attributeOrder; private final boolean enforceQuoteString; + private final boolean failOnUnknown; + private final SerializeValueFilter serializeValueFilter; + private final boolean useBigDecimalForFloats; + private final Boolean deduplicateObjects; private final Map<Class<?>, ObjectConverter.Writer<?>> objectConverterWriterCache; private final Map<Class<?>, ObjectConverter.Reader<?>> objectConverterReaderCache; @@ -78,7 +82,10 @@ public /* DON'T MAKE IT HIDDEN */ class MapperConfig implements Cloneable { final boolean readAttributeBeforeWrite, final AccessMode accessMode, final Charset encoding, final Comparator<String> attributeOrder, - final boolean enforceQuoteString) { + final boolean enforceQuoteString, final boolean failOnUnknown, + final SerializeValueFilter serializeValueFilter, + final boolean useBigDecimalForFloats, + final Boolean deduplicateObjects) { //CHECKSTYLE:ON this.objectConverterWriters = objectConverterWriters; this.objectConverterReaders = objectConverterReaders; @@ -94,9 +101,24 @@ public /* DON'T MAKE IT HIDDEN */ class MapperConfig implements Cloneable { this.adapters = adapters; this.attributeOrder = attributeOrder; this.enforceQuoteString = enforceQuoteString; + this.failOnUnknown = failOnUnknown; + + this.serializeValueFilter = serializeValueFilter != null ? serializeValueFilter : new SerializeValueFilter() { + @Override + public boolean shouldIgnore(String name, Object value) { + return false; + } + }; this.objectConverterWriterCache = new HashMap<Class<?>, ObjectConverter.Writer<?>>(objectConverterWriters.size()); this.objectConverterReaderCache = new HashMap<Class<?>, ObjectConverter.Reader<?>>(objectConverterReaders.size()); + this.useBigDecimalForFloats = useBigDecimalForFloats; + this.deduplicateObjects = deduplicateObjects; + } + + + public SerializeValueFilter getSerializeValueFilter() { + return serializeValueFilter; } public Adapter findAdapter(final Type aClass) { @@ -104,6 +126,12 @@ public /* DON'T MAKE IT HIDDEN */ class MapperConfig implements Cloneable { if (converter != null) { return converter; } + /* could be an option but doesnt fit well our old converters + final Adapter<?, ?> reverseConverter = adapters.get(new AdapterKey(String.class, aClass)); + if (reverseConverter != null) { + return new ReversedAdapter<>(reverseConverter); + } + */ if (Class.class.isInstance(aClass)) { final Class<?> clazz = Class.class.cast(aClass); if (clazz.isEnum()) { @@ -220,6 +248,10 @@ public /* DON'T MAKE IT HIDDEN */ class MapperConfig implements Cloneable { return converter; } + public boolean isFailOnUnknown() { + return failOnUnknown; + } + public int getVersion() { return version; } @@ -275,4 +307,12 @@ public /* DON'T MAKE IT HIDDEN */ class MapperConfig implements Cloneable { public boolean isEnforceQuoteString() { return enforceQuoteString; } + + public boolean isUseBigDecimalForFloats() { + return useBigDecimalForFloats; + } + + public Boolean isDeduplicateObjects() { + return deduplicateObjects; + } } http://git-wip-us.apache.org/repos/asf/johnzon/blob/76fe13de/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 38b5315..827404c 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 @@ -19,6 +19,7 @@ package org.apache.johnzon.mapper; import org.apache.johnzon.mapper.internal.AdapterKey; +import org.apache.johnzon.mapper.internal.JsonPointerTracker; import javax.json.JsonValue; import javax.json.stream.JsonGenerator; @@ -28,18 +29,27 @@ import java.lang.reflect.InvocationTargetException; import java.math.BigDecimal; import java.math.BigInteger; import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; import java.util.Map; 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.isDeduplicateObjects = isDeduplicateObjects; + + this.jsonPointers = isDeduplicateObjects ? new HashMap<Object, String>() : Collections.<Object, String>emptyMap(); } @Override @@ -54,12 +64,13 @@ public class MappingGeneratorImpl implements MappingGenerator { } else if (object instanceof JsonValue) { generator.write((JsonValue) object); } else { - doWriteObject(object, generator, false); + doWriteObject(object, generator, false, null, isDeduplicateObjects ? new JsonPointerTracker(null, "/") : null); } return this; } - public void doWriteObject(Object object, JsonGenerator generator, boolean writeBody) { + public void doWriteObject(Object object, JsonGenerator generator, boolean writeBody, final Collection<String> ignoredProperties, JsonPointerTracker jsonPointer) { + try { if (object instanceof Map) { if (writeBody) { @@ -85,7 +96,7 @@ public class MappingGeneratorImpl implements MappingGenerator { } if (object instanceof Iterable) { - doWriteIterable((Iterable) object); + doWriteIterable((Iterable) object, ignoredProperties, jsonPointer); return; } @@ -97,7 +108,7 @@ public class MappingGeneratorImpl implements MappingGenerator { if (writeBody && objectConverter != null) { objectConverter.writeJson(object, this); } else { - doWriteObjectBody(object); + doWriteObjectBody(object, ignoredProperties, jsonPointer); } if (writeBody) { @@ -125,14 +136,9 @@ public class MappingGeneratorImpl implements MappingGenerator { } final Class<?> valueClass = value.getClass(); - final boolean primitive = Mappings.isPrimitive(valueClass); - final boolean clazz = mappings.getClassMapping(valueClass) != null; - final boolean array = clazz || primitive ? false : valueClass.isArray(); - final boolean collection = clazz || primitive || array ? false : Collection.class.isAssignableFrom(valueClass); - final boolean map = clazz || primitive || array || collection ? false : Map.class.isAssignableFrom(valueClass); - writeValue(valueClass, - primitive, array, collection, map, itemConverter, - key == null ? "null" : key.toString(), value, null); + writeValue(valueClass, true, + false, false, false, false, itemConverter, + key == null ? "null" : key.toString(), value, null, null, null); } return generator; } @@ -224,7 +230,13 @@ public class MappingGeneratorImpl implements MappingGenerator { } - private void doWriteObjectBody(final Object object) throws IllegalAccessException, InvocationTargetException { + private void doWriteObjectBody(final Object object, final Collection<String> ignored, JsonPointerTracker jsonPointer) + throws IllegalAccessException, InvocationTargetException { + + if (jsonPointer != null) { + jsonPointers.put(object, jsonPointer.toString()); + } + final Class<?> objectClass = object.getClass(); final Mappings.ClassMapping classMapping = mappings.findOrCreateClassMapping(objectClass); if (classMapping == null) { @@ -236,12 +248,15 @@ public class MappingGeneratorImpl implements MappingGenerator { return; } if (classMapping.adapter != null) { - doWriteObjectBody(classMapping.adapter.to(object)); + doWriteObjectBody(classMapping.adapter.to(object), ignored, jsonPointer); return; } for (final Map.Entry<String, Mappings.Getter> getterEntry : classMapping.getters.entrySet()) { final Mappings.Getter getter = getterEntry.getValue(); + if (ignored != null && ignored.contains(getterEntry.getKey())) { + continue; + } if (getter.version >= 0 && config.getVersion() >= getter.version) { continue; } @@ -263,12 +278,24 @@ public class MappingGeneratorImpl implements MappingGenerator { final Object val = getter.converter == null ? value : getter.converter.from(value); - writeValue(val.getClass(), - getter.primitive, getter.array, - getter.collection, getter.map, - getter.itemConverter, - getterEntry.getKey(), - val, getter.objectConverter); + String valJsonPointer = jsonPointers.get(val); + if (valJsonPointer != null) { + // write the JsonPointer instead + generator.write(getterEntry.getKey(), valJsonPointer); + } else { + writeValue(val.getClass(), + getter.dynamic, + getter.primitive, + getter.array, + getter.collection, + getter.map, + getter.itemConverter, + getterEntry.getKey(), + val, + getter.objectConverter, + getter.ignoreNested, + isDeduplicateObjects ? new JsonPointerTracker(jsonPointer, getterEntry.getKey()) : null); + } } // @JohnzonAny doesn't respect comparator since it is a map and not purely in the model we append it after and @@ -281,13 +308,21 @@ public class MappingGeneratorImpl implements MappingGenerator { } } - private void writeValue(final Class<?> type, + //CHECKSTYLE:OFF + private void writeValue(final Class<?> type, final boolean dynamic, final boolean primitive, final boolean array, final boolean collection, final boolean map, final Adapter itemConverter, final String key, final Object value, - final ObjectConverter.Writer objectConverter) throws InvocationTargetException, IllegalAccessException { - if (array) { + final ObjectConverter.Writer objectConverter, + final Collection<String> ignoredProperties, + final JsonPointerTracker jsonPointer) + throws InvocationTargetException, IllegalAccessException { + //CHECKSTYLE:ON + if (config.getSerializeValueFilter().shouldIgnore(key, value)) { + return; + } + if (array || (dynamic && type.isArray())) { final int length = Array.getLength(value); if (length == 0 && config.isSkipEmptyArray()) { return; @@ -306,25 +341,46 @@ public class MappingGeneratorImpl implements MappingGenerator { generator.writeStartArray(key); for (int i = 0; i < length; i++) { final Object o = Array.get(value, i); - writeItem(itemConverter != null ? itemConverter.from(o) : o); + String valJsonPointer = jsonPointers.get(o); + if (valJsonPointer != null) { + writePrimitives(valJsonPointer); + } else { + writeItem(itemConverter != null ? itemConverter.from(o) : o, ignoredProperties, isDeduplicateObjects ? new JsonPointerTracker(jsonPointer, i) : null); + } } generator.writeEnd(); - return; - } else if (collection) { + } else if (collection || (dynamic && Collection.class.isAssignableFrom(type))) { generator.writeStartArray(key); + int i = 0; for (final Object o : Collection.class.cast(value)) { - writeItem(itemConverter != null ? itemConverter.from(o) : o); + String valJsonPointer = jsonPointers.get(o); + if (valJsonPointer != null) { + // write JsonPointer instead of the original object + writePrimitives(valJsonPointer); + } else { + ObjectConverter.Writer objectConverterToUse = objectConverter; + if (o != null && objectConverterToUse == null) { + objectConverterToUse = config.findObjectConverterWriter(o.getClass()); + } + + if (objectConverterToUse != null) { + generator.writeStartObject(); + objectConverterToUse.writeJson(o, this); + generator.writeEnd(); + } else { + writeItem(itemConverter != null ? itemConverter.from(o) : o, ignoredProperties, + isDeduplicateObjects ? new JsonPointerTracker(jsonPointer, i) : null); + } + } + i++; } generator.writeEnd(); - return; - } else if (map) { + } else if (map || (dynamic && Map.class.isAssignableFrom(type))) { generator.writeStartObject(key); writeMapBody((Map<?, ?>) value, itemConverter); generator.writeEnd(); - return; - } else if (primitive) { + } else if (primitive || (dynamic && Mappings.isPrimitive(type))) { writePrimitives(key, type, value); - return; } else { final Adapter converter = config.findAdapter(type); if (converter != null) { @@ -332,10 +388,9 @@ public class MappingGeneratorImpl implements MappingGenerator { if (writePrimitives(key, adapted.getClass(), adapted)) { return; } - writeValue(String.class, true, false, false, false, null, key, adapted, null); + writeValue(String.class, true, true, false, false, false, null, key, adapted, null, ignoredProperties, jsonPointer); return; } else { - ObjectConverter.Writer objectConverterToUse = objectConverter; if (objectConverterToUse == null) { objectConverterToUse = config.findObjectConverterWriter(type); @@ -352,16 +407,18 @@ public class MappingGeneratorImpl implements MappingGenerator { return; } generator.writeStartObject(key); - doWriteObjectBody(value); + doWriteObjectBody(value, ignoredProperties, jsonPointer); generator.writeEnd(); } } - private void writeItem(final Object o) { - if (!writePrimitives(o)) { + private void writeItem(final Object o, final Collection<String> ignoredProperties, JsonPointerTracker jsonPointer) { + if (o == null) { + generator.writeNull(); + } else if (!writePrimitives(o)) { if (Collection.class.isInstance(o)) { - doWriteIterable(Collection.class.cast(o)); - } else if (o != null && o.getClass().isArray()) { + doWriteIterable(Collection.class.cast(o), ignoredProperties, jsonPointer); + } else if (o.getClass().isArray()) { final int length = Array.getLength(o); if (length > 0 || !config.isSkipEmptyArray()) { generator.writeStartArray(); @@ -370,24 +427,29 @@ public class MappingGeneratorImpl implements MappingGenerator { if (t == null) { generator.writeNull(); } else { - writeItem(t); + writeItem(t, ignoredProperties, isDeduplicateObjects ? new JsonPointerTracker(jsonPointer, i) : null); } } generator.writeEnd(); } - } else if (o == null) { - generator.writeNull(); } else { - doWriteObject(o, generator, true); + String valJsonPointer = jsonPointers.get(o); + if (valJsonPointer != null) { + // write the JsonPointer instead + generator.write(valJsonPointer); + } else { + doWriteObject(o, generator, true, ignoredProperties, jsonPointer); + } } } } - private <T> void doWriteIterable(final Iterable<T> object) { + private <T> void doWriteIterable(final Iterable<T> object, final Collection<String> ignoredProperties, JsonPointerTracker jsonPointer) { if (object == null) { generator.writeStartArray().writeEnd(); } else { generator.writeStartArray(); + int i = 0; for (final T t : object) { if (JsonValue.class.isInstance(t)) { generator.write(JsonValue.class.cast(t)); @@ -395,9 +457,10 @@ public class MappingGeneratorImpl implements MappingGenerator { if (t == null) { generator.writeNull(); } else { - writeItem(t); + writeItem(t, ignoredProperties, isDeduplicateObjects ? new JsonPointerTracker(jsonPointer, i) : null); } } + i++; } generator.writeEnd(); } http://git-wip-us.apache.org/repos/asf/johnzon/blob/76fe13de/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 e731b8f..a15fd2a 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 @@ -18,13 +18,13 @@ */ package org.apache.johnzon.mapper; -import org.apache.johnzon.core.JsonLongImpl; import org.apache.johnzon.core.JsonReaderImpl; import org.apache.johnzon.mapper.access.AccessMode; import org.apache.johnzon.mapper.converter.CharacterConverter; import org.apache.johnzon.mapper.converter.EnumConverter; import org.apache.johnzon.mapper.internal.AdapterKey; import org.apache.johnzon.mapper.internal.ConverterAdapter; +import org.apache.johnzon.mapper.internal.JsonPointerTracker; import org.apache.johnzon.mapper.reflection.JohnzonParameterizedType; import javax.json.JsonArray; @@ -35,6 +35,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; @@ -46,6 +47,7 @@ import java.math.BigInteger; import java.util.ArrayDeque; import java.util.ArrayList; import java.util.Collection; +import java.util.Collections; import java.util.Deque; import java.util.EnumMap; import java.util.EnumSet; @@ -79,7 +81,6 @@ import static javax.json.JsonValue.ValueType.TRUE; */ public class MappingParserImpl implements MappingParser { - private static final Adapter<Object, String> FALLBACK_CONVERTER = new ConverterAdapter<Object>(new FallbackConverter()); private static final JohnzonParameterizedType ANY_LIST = new JohnzonParameterizedType(List.class, Object.class); private static final CharacterConverter CHARACTER_CONVERTER = new CharacterConverter(); // this one is particular, share the logic @@ -88,18 +89,34 @@ public class MappingParserImpl implements MappingParser { private final MapperConfig config; private final Mappings mappings; + private final boolean isDeduplicateObjects; private final JsonReader jsonReader; + /** + * Used for de-referencing JsonPointers during deserialisation. + * key: JsonPointer + * value: already deserialised Object + */ + private Map<String, Object> jsonPointers; - public MappingParserImpl(MapperConfig config, Mappings mappings, JsonReader jsonReader) { + public MappingParserImpl(MapperConfig config, Mappings mappings, JsonReader jsonReader, boolean isDeduplicateObjects) { this.config = config; this.mappings = mappings; this.jsonReader = jsonReader; reverseAdaptersRegistry = new ConcurrentHashMap<Adapter<?, ?>, AdapterKey>(config.getAdapters().size()); + + + this.isDeduplicateObjects = isDeduplicateObjects; + + if (isDeduplicateObjects) { + jsonPointers = new HashMap<String, Object>(); + } else { + jsonPointers = Collections.emptyMap(); + } } @@ -130,7 +147,7 @@ public class MappingParserImpl implements MappingParser { return (T) jsonValue; } if (JsonObject.class.isInstance(jsonValue)) { - return (T) buildObject(targetType, JsonObject.class.cast(jsonValue), applyObjectConverter); + 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(); @@ -158,19 +175,25 @@ public class MappingParserImpl implements MappingParser { JsonArray jsonArray = (JsonArray) jsonValue; if (Class.class.isInstance(targetType) && ((Class) targetType).isArray()) { - return (T) buildArrayWithComponentType(jsonArray, ((Class) targetType).getComponentType(), null); + final Class componentType = ((Class) targetType).getComponentType(); + return (T) buildArrayWithComponentType(jsonArray, componentType, config.findAdapter(componentType), + isDeduplicateObjects ? new JsonPointerTracker(null, "/") : null, Object.class); } if (ParameterizedType.class.isInstance(targetType)) { - final Mappings.CollectionMapping mapping = mappings.findCollectionMapping((ParameterizedType) targetType); + final ParameterizedType pt = (ParameterizedType) targetType; + final Mappings.CollectionMapping mapping = mappings.findCollectionMapping(pt, Object.class); if (mapping == null) { throw new UnsupportedOperationException("type " + targetType + " not supported"); } - return (T) mapCollection(mapping, jsonArray, null); + final Type arg = pt.getActualTypeArguments()[0]; + return (T) mapCollection(mapping, jsonArray, Class.class.isInstance(arg) ? config.findAdapter(Class.class.cast(arg)) : null, + null, isDeduplicateObjects ? new JsonPointerTracker(null, "/") : null, Object.class); } if (Object.class == targetType) { - return (T) new ArrayList(asList(Object[].class.cast(buildArrayWithComponentType(jsonArray, Object.class, null)))); + return (T) new ArrayList(asList(Object[].class.cast(buildArrayWithComponentType(jsonArray, Object.class, null, + isDeduplicateObjects ? new JsonPointerTracker(null, "/") : null, Object.class)))); } } if (JsonValue.NULL.equals(jsonValue)) { @@ -186,13 +209,13 @@ public class MappingParserImpl implements MappingParser { } - private Object buildObject(final Type inType, final JsonObject object, final boolean applyObjectConverter) { + private Object buildObject(final Type inType, final JsonObject object, final boolean applyObjectConverter, JsonPointerTracker jsonPointer) { Type type = inType; if (inType == Object.class) { type = new JohnzonParameterizedType(Map.class, String.class, Object.class); } - if (applyObjectConverter && !(type instanceof JohnzonParameterizedType)) { + if (applyObjectConverter && !(type instanceof ParameterizedType)) { if (!(type instanceof Class)) { throw new MapperException("ObjectConverters are only supported for Classes not Types"); @@ -217,7 +240,7 @@ public class MappingParserImpl implements MappingParser { if (LinkedHashMap.class == raw) { map = new LinkedHashMap(); } else if (SortedMap.class.isAssignableFrom(raw) || NavigableMap.class == raw || TreeMap.class == raw) { - map = new TreeMap(); + map = config.getAttributeOrder() == null ? new TreeMap() : new TreeMap(config.getAttributeOrder()); } else if (ConcurrentMap.class.isAssignableFrom(raw)) { map = new ConcurrentHashMap(object.size()); } else if (EnumMap.class.isAssignableFrom(raw)) { @@ -241,22 +264,11 @@ public class MappingParserImpl implements MappingParser { for (final Map.Entry<String, JsonValue> value : object.entrySet()) { final JsonValue jsonValue = value.getValue(); if (JsonNumber.class.isInstance(jsonValue) && any) { - final JsonNumber number = JsonNumber.class.cast(jsonValue); - if (JsonLongImpl.class.isInstance(number)) { - final int integer = number.intValue(); - final long asLong = number.longValue(); - if (integer == asLong) { - map.put(value.getKey(), integer); - } else { - map.put(value.getKey(), asLong); - } - } else { - map.put(value.getKey(), !number.isIntegral() ? number.bigDecimalValue() : number.intValue()); - } + map.put(value.getKey(), toNumberValue(JsonNumber.class.cast(jsonValue))); } else if (JsonString.class.isInstance(jsonValue) && any) { map.put(value.getKey(), JsonString.class.cast(jsonValue).getString()); } else { - map.put(convertTo(keyType, value.getKey()), toObject(null, jsonValue, fieldArgTypes[1], null)); + map.put(convertTo(keyType, value.getKey()), toObject(null, jsonValue, fieldArgTypes[1], null, jsonPointer, Object.class)); } } return map; @@ -265,7 +277,7 @@ public class MappingParserImpl implements MappingParser { } else if (Map.class == type || HashMap.class == type || LinkedHashMap.class == type) { final LinkedHashMap<String, Object> map = new LinkedHashMap<String, Object>(); for (final Map.Entry<String, JsonValue> value : object.entrySet()) { - map.put(value.getKey(), toObject(null, value.getValue(), Object.class, null)); + map.put(value.getKey(), toObject(null, value.getValue(), Object.class, null, jsonPointer, Object.class)); } return map; } @@ -287,8 +299,25 @@ public class MappingParserImpl implements MappingParser { throw new MapperException(classMapping.clazz + " not instantiable"); } - final Object t = classMapping.factory.getParameterTypes().length == 0 ? - classMapping.factory.create(null) : classMapping.factory.create(createParameters(classMapping, object)); + if (config.isFailOnUnknown()) { + if (!classMapping.setters.keySet().containsAll(object.keySet())) { + throw new MapperException("(fail on unknown properties): " + new HashSet<String>(object.keySet()) {{ + removeAll(classMapping.setters.keySet()); + }}); + } + } + + Object t; + if (classMapping.factory.getParameterTypes().length == 0) { + t = classMapping.factory.create(null); + } else { + t = classMapping.factory.create(createParameters(classMapping, object, jsonPointer)); + } + // store the new object under it's jsonPointer in case it gets referenced later + if (isDeduplicateObjects) { + jsonPointers.put(jsonPointer.toString(), t); + } + for (final Map.Entry<String, Mappings.Setter> setter : classMapping.setters.entrySet()) { final JsonValue jsonValue = object.get(setter.getKey()); final Mappings.Setter value = setter.getValue(); @@ -315,7 +344,8 @@ public class MappingParserImpl implements MappingParser { } } } - final Object convertedValue = toValue(existingInstance, jsonValue, value.converter, value.itemConverter, value.paramType, value.objectConverter); + final Object convertedValue = toValue(existingInstance, jsonValue, value.converter, value.itemConverter, value.paramType, value.objectConverter, + new JsonPointerTracker(jsonPointer, setter.getKey()), inType); if (convertedValue != null) { setterMethod.write(t, convertedValue); } @@ -326,7 +356,8 @@ public class MappingParserImpl implements MappingParser { final String key = entry.getKey(); if (!classMapping.setters.containsKey(key)) { try { - classMapping.anySetter.invoke(t, key, toValue(null, entry.getValue(), null, null, Object.class, null)); + classMapping.anySetter.invoke(t, key, toValue(null, entry.getValue(), null, null, Object.class, null, + isDeduplicateObjects ? new JsonPointerTracker(jsonPointer, entry.getKey()) : null, type)); } catch (final IllegalAccessException e) { throw new IllegalStateException(e); } catch (final InvocationTargetException e) { @@ -339,7 +370,25 @@ public class MappingParserImpl implements MappingParser { return t; } - private Object convertTo(final Adapter converter, final JsonValue jsonValue) { + private Number toNumberValue(JsonNumber jsonNumber) { + if (jsonNumber.isIntegral()) { + final int intValue = jsonNumber.intValue(); + final long longValue = jsonNumber.longValue(); + if (intValue == longValue) { + return intValue; + } else { + return longValue; + } + } else { + if (config.isUseBigDecimalForFloats()) { + return jsonNumber.bigDecimalValue(); + } else { + return jsonNumber.doubleValue(); + } + } + } + + private Object convertTo(final Adapter converter, final JsonValue jsonValue, JsonPointerTracker jsonPointer) { if (jsonValue.getValueType() == JsonValue.ValueType.OBJECT) { //X TODO maybe we can put this into MapperConfig? @@ -350,7 +399,7 @@ public class MappingParserImpl implements MappingParser { final Object param; try { Type to = adapterKey.getTo(); - param = buildObject(to, JsonObject.class.cast(jsonValue), to instanceof Class); + param = buildObject(to, JsonObject.class.cast(jsonValue), to instanceof Class, jsonPointer); } catch (final Exception e) { throw new MapperException(e); } @@ -421,7 +470,8 @@ public class MappingParserImpl implements MappingParser { private Object toObject(final Object baseInstance, final JsonValue jsonValue, - final Type type, final Adapter itemConverter) { + final Type type, final Adapter itemConverter, final JsonPointerTracker jsonPointer, + final Type rootType) { if (jsonValue == null || JsonValue.NULL.equals(jsonValue)) { return null; } @@ -436,7 +486,10 @@ public class MappingParserImpl implements MappingParser { throw new MapperException("Unable to parse " + jsonValue + " to boolean"); } - if(config.isTreatByteArrayAsBase64() && jsonValue.getValueType() == JsonValue.ValueType.STRING && (type == byte[].class /*|| type == Byte[].class*/)) { + if (config.isTreatByteArrayAsBase64() && jsonValue.getValueType() == JsonValue.ValueType.STRING && (type == byte[].class /*|| type == Byte[].class*/)) { + return DatatypeConverter.parseBase64Binary(((JsonString)jsonValue).getString()); + } + if (config.isTreatByteArrayAsBase64URL() && jsonValue.getValueType() == JsonValue.ValueType.STRING && (type == byte[].class /*|| type == Byte[].class*/)) { return DatatypeConverter.parseBase64Binary(((JsonString)jsonValue).getString()); } @@ -448,11 +501,7 @@ public class MappingParserImpl implements MappingParser { return false; } if (JsonNumber.class.isInstance(jsonValue)) { - final JsonNumber jsonNumber = JsonNumber.class.cast(jsonValue); - if(jsonNumber.isIntegral()) { - return jsonNumber.intValue(); - } - return jsonNumber.doubleValue(); + return toNumberValue(JsonNumber.class.cast(jsonValue)); } } @@ -468,13 +517,14 @@ public class MappingParserImpl implements MappingParser { final Object object = buildObject( baseInstance != null ? baseInstance.getClass() : ( typedAdapter ? TypeAwareAdapter.class.cast(itemConverter).getTo() : type), - JsonObject.class.cast(jsonValue), type instanceof Class); + JsonObject.class.cast(jsonValue), type instanceof Class, + jsonPointer); return typedAdapter ? itemConverter.to(object) : object; } else if (JsonArray.class.isInstance(jsonValue)) { if (JsonArray.class == type || JsonStructure.class == type) { return jsonValue; } - return buildArray(type, JsonArray.class.cast(jsonValue), itemConverter); + return buildArray(type, JsonArray.class.cast(jsonValue), itemConverter, null, jsonPointer, rootType); } else if (JsonNumber.class.isInstance(jsonValue)) { if (JsonNumber.class == type) { return jsonValue; @@ -520,6 +570,13 @@ public class MappingParserImpl implements MappingParser { final String string = JsonString.class.cast(jsonValue).getString(); if (itemConverter == null) { + // check whether we have a jsonPointer to a previously deserialised object + if (!String.class.equals(type)) { + Object o = jsonPointers.get(string); + if (o != null) { + return o; + } + } return convertTo(Class.class.cast(type), string); } else { return itemConverter.to(string); @@ -529,40 +586,46 @@ public class MappingParserImpl implements MappingParser { throw new MapperException("Unable to parse " + jsonValue + " to " + type); } - private Object buildArray(final Type type, final JsonArray jsonArray, final Adapter itemConverter) { + private Object buildArray(final Type type, final JsonArray jsonArray, final Adapter itemConverter, + final ObjectConverter.Reader objectConverter, + final JsonPointerTracker jsonPointer, final Type rootType) { if (Class.class.isInstance(type)) { final Class clazz = Class.class.cast(type); if (clazz.isArray()) { final Class<?> componentType = clazz.getComponentType(); - return buildArrayWithComponentType(jsonArray, componentType, itemConverter); + return buildArrayWithComponentType(jsonArray, componentType, itemConverter, jsonPointer, rootType); } } if (ParameterizedType.class.isInstance(type)) { - final Mappings.CollectionMapping mapping = mappings.findCollectionMapping(ParameterizedType.class.cast(type)); + final Mappings.CollectionMapping mapping = mappings.findCollectionMapping(ParameterizedType.class.cast(type), rootType); if (mapping != null) { - return mapCollection(mapping, jsonArray, itemConverter); + return mapCollection(mapping, jsonArray, itemConverter, objectConverter, jsonPointer, rootType); } } if (Object.class == type) { - return buildArray(ANY_LIST, jsonArray, null); + return buildArray(ANY_LIST, jsonArray, null, null, jsonPointer, rootType); } throw new UnsupportedOperationException("type " + type + " not supported"); } - private Object buildArrayWithComponentType(final JsonArray jsonArray, final Class<?> componentType, final Adapter itemConverter) { + private Object buildArrayWithComponentType(final JsonArray jsonArray, final Class<?> componentType, final Adapter itemConverter, + final JsonPointerTracker jsonPointer, final Type rootType) { final Object array = Array.newInstance(componentType, jsonArray.size()); int i = 0; for (final JsonValue value : jsonArray) { - Array.set(array, i++, toObject(null, value, componentType, itemConverter)); + Array.set(array, i, toObject(null, value, componentType, itemConverter, + isDeduplicateObjects ? new JsonPointerTracker(jsonPointer, i) : null, rootType)); + i++; } return array; } private <T> Collection<T> mapCollection(final Mappings.CollectionMapping mapping, final JsonArray jsonArray, - final Adapter itemConverter) { + final Adapter itemConverter, ObjectConverter.Reader objectConverter, + final JsonPointerTracker jsonPointer, final Type rootType) { final Collection collection; if (SortedSet.class == mapping.raw || NavigableSet.class == mapping.raw || TreeSet.class == mapping.raw) { @@ -583,8 +646,13 @@ public class MappingParserImpl implements MappingParser { throw new IllegalStateException("not supported collection type: " + mapping.raw.getName()); } + int i = 0; for (final JsonValue value : jsonArray) { - collection.add(JsonValue.NULL.equals(value) ? null : toObject(null, value, mapping.arg, itemConverter)); + collection.add(JsonValue.NULL.equals(value) + ? null + : toValue(null, value, null, itemConverter, mapping.arg, objectConverter, + isDeduplicateObjects ? new JsonPointerTracker(jsonPointer, i) : null, rootType)); + i++; } if (EnumSet.class == mapping.raw) { @@ -593,7 +661,7 @@ public class MappingParserImpl implements MappingParser { } else if (collection.size() == 1) { return Collection.class.cast(EnumSet.of(Enum.class.cast(collection.iterator().next()))); } else { - final List<Enum<?>> list = List.class.cast(collection); + final List<Enum> list = List.class.cast(collection); return Collection.class.cast(EnumSet.of(list.get(0), list.subList(1, list.size()).toArray(new Enum[list.size() - 1]))); } } @@ -602,38 +670,51 @@ public class MappingParserImpl implements MappingParser { } - private Object[] createParameters(final Mappings.ClassMapping mapping, final JsonObject object) { + private Object[] createParameters(final Mappings.ClassMapping mapping, final JsonObject object, JsonPointerTracker jsonPointer) { final int length = mapping.factory.getParameterTypes().length; final Object[] objects = new Object[length]; for (int i = 0; i < length; i++) { + String paramName = mapping.factory.getParameterNames()[i]; objects[i] = toValue(null, - object.get(mapping.factory.getParameterNames()[i]), - mapping.factory.getParameterConverter()[i], - mapping.factory.getParameterItemConverter()[i], - mapping.factory.getParameterTypes()[i], - null); //X TODO ObjectConverter in @JOhnzonConverter with Constructors! + object.get(paramName), + mapping.factory.getParameterConverter()[i], + mapping.factory.getParameterItemConverter()[i], + mapping.factory.getParameterTypes()[i], + mapping.factory.getObjectConverter()[i], + isDeduplicateObjects ? new JsonPointerTracker(jsonPointer, paramName) : null, + mapping.clazz); //X TODO ObjectConverter in @JohnzonConverter with Constructors! } return objects; } private Object toValue(final Object baseInstance, final JsonValue jsonValue, final Adapter converter, - final Adapter itemConverter, final Type type, final ObjectConverter.Reader objectConverter) { + final Adapter itemConverter, final Type type, final ObjectConverter.Reader objectConverter, + final JsonPointerTracker jsonPointer, final Type rootType) { if (objectConverter != null) { if (jsonValue instanceof JsonObject) { return objectConverter.fromJson((JsonObject) jsonValue, type, this); + } else if (jsonValue instanceof JsonArray) { + return buildArray(type, (JsonArray) jsonValue, itemConverter, objectConverter, jsonPointer, rootType); } else { throw new UnsupportedOperationException("Array handling with ObjectConverter currently not implemented"); } } - return converter == null ? toObject(baseInstance, jsonValue, type, itemConverter) - : jsonValue.getValueType() == JsonValue.ValueType.STRING ? converter.to(JsonString.class.cast(jsonValue).getString()) - : convertTo(converter, jsonValue); + try { + return converter == null ? toObject(baseInstance, jsonValue, type, itemConverter, jsonPointer, rootType) + : jsonValue.getValueType() == JsonValue.ValueType.STRING ? converter.to(JsonString.class.cast(jsonValue).getString()) + : convertTo(converter, jsonValue, jsonPointer); + } catch (Exception e) { + if (e instanceof MapperException) { + throw (MapperException) e; + } + throw new MapperException(e); + } } @@ -678,8 +759,8 @@ public class MappingParserImpl implements MappingParser { } } if (converter == null) { - config.getAdapters().putIfAbsent(new AdapterKey(String.class, aClass), FALLBACK_CONVERTER); - return FALLBACK_CONVERTER.to(text); + throw new MapperException("Missing a Converter for type " + aClass + " to convert the JSON String '" + + text + "' . Please register a custom converter for it."); } return converter.to(text); } @@ -703,21 +784,6 @@ public class MappingParserImpl implements MappingParser { return null; } - - private static class FallbackConverter implements Converter<Object> { - @Override - public String toString(final Object instance) { - return instance.toString(); - } - - @Override - public Object fromString(final String text) { - throw new MapperException("Using fallback converter, " + - "this only works in write mode but not in read. Please register a custom converter to do so."); - } - } - - /** * Internal class to suppress {@link ObjectConverter} lookup if and only if * the {@link JsonValue} is the same refernece than the lookup was done before. http://git-wip-us.apache.org/repos/asf/johnzon/blob/76fe13de/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 7dd016c..08845fe 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 @@ -24,6 +24,7 @@ import org.apache.johnzon.mapper.converter.DateWithCopyConverter; import org.apache.johnzon.mapper.converter.EnumConverter; import org.apache.johnzon.mapper.internal.AdapterKey; import org.apache.johnzon.mapper.internal.ConverterAdapter; +import org.apache.johnzon.mapper.reflection.Generics; import org.apache.johnzon.mapper.reflection.JohnzonParameterizedType; import java.lang.annotation.Annotation; @@ -37,6 +38,7 @@ import java.util.Collection; import java.util.Comparator; import java.util.Date; import java.util.Deque; +import java.util.EnumSet; import java.util.HashMap; import java.util.HashSet; import java.util.LinkedHashMap; @@ -52,6 +54,7 @@ import java.util.concurrent.ConcurrentMap; import static java.util.Arrays.asList; import static org.apache.johnzon.mapper.reflection.Converters.matches; +import static org.apache.johnzon.mapper.reflection.Generics.resolve; public class Mappings { public static class ClassMapping { @@ -65,6 +68,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 +86,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 { @@ -100,22 +118,26 @@ public class Mappings { public final Adapter converter; public final Adapter itemConverter; public final ObjectConverter.Writer objectConverter; + public final boolean dynamic; public final boolean primitive; public final boolean array; public final boolean map; public final boolean collection; + public final Collection<String> ignoreNested; - public Getter(final AccessMode.Reader reader, + public Getter(final AccessMode.Reader reader, final boolean dynamic, final boolean primitive, final boolean array, final boolean collection, final boolean map, final MapperConverter converter, final ObjectConverter.Writer providedObjectConverter, - final int version) { + final int version, final String[] ignoreNested) { this.reader = reader; this.version = version; + this.dynamic = dynamic; this.array = array; this.collection = collection; this.primitive = primitive; + this.ignoreNested = ignoreNested == null || ignoreNested.length == 0 ? null : new HashSet<String>(asList(ignoreNested)); Adapter theConverter = null; Adapter theItemConverter = null; @@ -238,10 +260,10 @@ public class Mappings { this.config = config; } - public CollectionMapping findCollectionMapping(final ParameterizedType genericType) { + public CollectionMapping findCollectionMapping(final ParameterizedType genericType, final Type enclosingType) { CollectionMapping collectionMapping = collections.get(genericType); if (collectionMapping == null) { - collectionMapping = createCollectionMapping(genericType); + collectionMapping = createCollectionMapping(genericType, enclosingType); if (collectionMapping == null) { return null; } @@ -253,7 +275,7 @@ public class Mappings { return collectionMapping; } - private <T> CollectionMapping createCollectionMapping(final ParameterizedType aType) { + private <T> CollectionMapping createCollectionMapping(final ParameterizedType aType, final Type root) { final Type[] fieldArgTypes = aType.getActualTypeArguments(); final Type raw = aType.getRawType(); if (fieldArgTypes.length == 1 && Class.class.isInstance(raw)) { @@ -263,6 +285,8 @@ public class Mappings { collectionType = List.class; } else if (SortedSet.class.isAssignableFrom(r)) { collectionType = SortedSet.class; + } else if (EnumSet.class.isAssignableFrom(r)) { + collectionType = EnumSet.class; } else if (Set.class.isAssignableFrom(r)) { collectionType = Set.class; } else if (Deque.class.isAssignableFrom(r)) { @@ -275,7 +299,8 @@ public class Mappings { return null; } - final CollectionMapping mapping = new CollectionMapping(isPrimitive(fieldArgTypes[0]), collectionType, fieldArgTypes[0]); + final CollectionMapping mapping = new CollectionMapping(isPrimitive(fieldArgTypes[0]), collectionType, + Generics.resolve(fieldArgTypes[0], Class.class.isInstance(root) ? Class.class.cast(root) : null)); collections.putIfAbsent(aType, mapping); return mapping; } @@ -353,13 +378,13 @@ public class Mappings { final JohnzonVirtualObjects virtualObjects = clazz.getAnnotation(JohnzonVirtualObjects.class); if (virtualObjects != null) { for (final JohnzonVirtualObject virtualObject : virtualObjects.value()) { - handleVirtualObject(virtualFields, virtualObject, getters, setters, readers, writers, copyDate); + handleVirtualObject(virtualFields, virtualObject, getters, setters, readers, writers, copyDate, clazz); } } final JohnzonVirtualObject virtualObject = clazz.getAnnotation(JohnzonVirtualObject.class); if (virtualObject != null) { - handleVirtualObject(virtualFields, virtualObject, getters, setters, readers, writers, copyDate); + handleVirtualObject(virtualFields, virtualObject, getters, setters, readers, writers, copyDate, clazz); } } @@ -368,7 +393,7 @@ public class Mappings { if (virtualFields.contains(key)) { continue; } - addGetterIfNeeded(getters, key, reader.getValue(), copyDate); + addGetterIfNeeded(getters, key, reader.getValue(), copyDate, clazz); } for (final Map.Entry<String, AccessMode.Writer> writer : writers.entrySet()) { @@ -376,7 +401,7 @@ public class Mappings { if (virtualFields.contains(key)) { continue; } - addSetterIfNeeded(setters, key, writer.getValue(), copyDate); + addSetterIfNeeded(setters, key, writer.getValue(), copyDate, clazz); } final Method anyGetter = accessMode.findAnyGetter(clazz); @@ -387,7 +412,7 @@ public class Mappings { accessMode.findWriter(clazz), anyGetter != null ? new Getter( new MethodAccessMode.MethodReader(anyGetter, anyGetter.getReturnType()), - false, false, false, true, null, null, -1) : null, + false,false, false, false, true, null, null, -1, null) : null, accessMode.findAnySetter(clazz)); accessMode.afterParsed(clazz); @@ -416,7 +441,8 @@ public class Mappings { private void addSetterIfNeeded(final Map<String, Setter> setters, final String key, final AccessMode.Writer value, - final boolean copyDate) { + final boolean copyDate, + final Class<?> rootClass) { final JohnzonIgnore writeIgnore = value.getAnnotation(JohnzonIgnore.class); if (writeIgnore == null || writeIgnore.minVersion() >= 0) { if (key.equals("metaClass")) { @@ -425,7 +451,7 @@ 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(), param, + value, isPrimitive(param), returnType != null && returnType.isArray(), resolve(param, rootClass), findConverter(copyDate, value), value.findObjectConverterReader(), writeIgnore != null ? writeIgnore.minVersion() : -1); setters.put(key, setter); @@ -435,19 +461,22 @@ public class Mappings { private void addGetterIfNeeded(final Map<String, Getter> getters, final String key, final AccessMode.Reader value, - final boolean copyDate) { + final boolean copyDate, + final Class<?> rootClass) { final JohnzonIgnore readIgnore = value.getAnnotation(JohnzonIgnore.class); + final JohnzonIgnoreNested ignoreNested = value.getAnnotation(JohnzonIgnoreNested.class); if (readIgnore == null || readIgnore.minVersion() >= 0) { 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, isPrimitive(returnType), + final Getter getter = new Getter(value, returnType == Object.class, isPrimitive(returnType), returnType != null && returnType.isArray(), (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()))) || (returnType != null && Map.class.isAssignableFrom(returnType)), findConverter(copyDate, value), value.findObjectConverterWriter(), - readIgnore != null ? readIgnore.minVersion() : -1); + readIgnore != null ? readIgnore.minVersion() : -1, + ignoreNested != null ? ignoreNested.properties() : null); getters.put(key, getter); } } @@ -459,7 +488,8 @@ public class Mappings { final Map<String, Setter> setters, final Map<String, AccessMode.Reader> readers, final Map<String, AccessMode.Writer> writers, - final boolean copyDate) { + final boolean copyDate, + final Class<?> rootClazz) { final String[] path = o.path(); if (path.length < 1) { throw new IllegalArgumentException("@JohnzonVirtualObject need a path"); @@ -479,13 +509,13 @@ public class Mappings { if (f.read()) { final AccessMode.Reader reader = readers.get(name); if (reader != null) { - addGetterIfNeeded(objectGetters, name, reader, copyDate); + addGetterIfNeeded(objectGetters, name, reader, copyDate, rootClazz); } } if (f.write()) { final AccessMode.Writer writer = writers.get(name); if (writer != null) { - addSetterIfNeeded(objectSetters, name, writer, copyDate); + addSetterIfNeeded(objectSetters, name, writer, copyDate, rootClazz); } } } @@ -494,7 +524,8 @@ public class Mappings { final Getter getter = getters.get(key); final MapBuilderReader newReader = new MapBuilderReader(objectGetters, path, config.getVersion()); - getters.put(key, new Getter(getter == null ? newReader : new CompositeReader(getter.reader, newReader), false, false, false, true, null, null, -1)); + getters.put(key, new Getter(getter == null ? newReader : + new CompositeReader(getter.reader, newReader), false, false, false, false, true, null, null, -1, null)); final Setter newSetter = setters.get(key); final MapUnwrapperWriter newWriter = new MapUnwrapperWriter(objectSetters, path); @@ -531,13 +562,6 @@ public class Mappings { if (Date.class.isAssignableFrom(type) && copyDate) { converter = new DateWithCopyConverter(Adapter.class.cast(adapters.get(new AdapterKey(Date.class, String.class)))); - } else if (type.isEnum()) { - final AdapterKey key = new AdapterKey(String.class, type); - converter = adapters.get(key); // first ensure user didnt override it - if (converter == null) { - converter = new ConverterAdapter(new EnumConverter(type)); - adapters.put(key, (Adapter<?, ?>) converter); - } } else { for (final Map.Entry<AdapterKey, Adapter<?, ?>> adapterEntry : adapters.entrySet()) { if (adapterEntry.getKey().getFrom() == adapterEntry.getKey().getTo()) { // String -> String @@ -555,6 +579,14 @@ public class Mappings { } } } + if (converter == null && type.isEnum()) { + final AdapterKey key = new AdapterKey(String.class, type); + converter = adapters.get(key); // first ensure user didnt override it + if (converter == null) { + converter = new ConverterAdapter(new EnumConverter(type)); + adapters.put(key, (Adapter<?, ?>) converter); + } + } } return converter; } http://git-wip-us.apache.org/repos/asf/johnzon/blob/76fe13de/johnzon-mapper/src/main/java/org/apache/johnzon/mapper/SerializeValueFilter.java ---------------------------------------------------------------------- diff --git a/johnzon-mapper/src/main/java/org/apache/johnzon/mapper/SerializeValueFilter.java b/johnzon-mapper/src/main/java/org/apache/johnzon/mapper/SerializeValueFilter.java new file mode 100644 index 0000000..e29fd52 --- /dev/null +++ b/johnzon-mapper/src/main/java/org/apache/johnzon/mapper/SerializeValueFilter.java @@ -0,0 +1,28 @@ +/* + * 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; + +public interface SerializeValueFilter { + /** + * @param name the attribute name if set. + * @param value the value which will get serialized. + * @return true if the value should be ignored and not serialized. + */ + boolean shouldIgnore(String name, Object value); +} http://git-wip-us.apache.org/repos/asf/johnzon/blob/76fe13de/johnzon-mapper/src/main/java/org/apache/johnzon/mapper/access/AccessMode.java ---------------------------------------------------------------------- diff --git a/johnzon-mapper/src/main/java/org/apache/johnzon/mapper/access/AccessMode.java b/johnzon-mapper/src/main/java/org/apache/johnzon/mapper/access/AccessMode.java index 8575aa3..4d590bf 100644 --- a/johnzon-mapper/src/main/java/org/apache/johnzon/mapper/access/AccessMode.java +++ b/johnzon-mapper/src/main/java/org/apache/johnzon/mapper/access/AccessMode.java @@ -52,6 +52,7 @@ public interface AccessMode { String[] getParameterNames(); Adapter<?, ?>[] getParameterConverter(); Adapter<?, ?>[] getParameterItemConverter(); + ObjectConverter.Codec<?>[] getObjectConverter(); } Factory findFactory(Class<?> clazz);
