Hi guys, started to try to tackle "views". My idea is to have 2 features:
- the one of this commit (ie expand an object without defining any other class) - (TODO) support a explicit view. I'm not yet sure for this last one (ie which technical solution: 1) interface model == filtering, 2) another object taking "this" as constructor param and implementing as desired the logic, 3) other). I added with this commit @Experimental to mark it "under discuss", allow us to push quickly feature and discuss them from code - I think for us it would be better than discussing then pushing while we are not that big. Any feedback on these 2 things (@Experimental and @JohnzonVirtualObject) are very welcomed. Don't hesitate to hack as well on it. Side note: I did my best to support merge of nested object, ie if 2 virtual objects conflicts cause they have a common path one will not overwrite the other. Romain Manni-Bucau @rmannibucau <https://twitter.com/rmannibucau> | Blog <http://rmannibucau.wordpress.com> | Github <https://github.com/rmannibucau> | LinkedIn <https://www.linkedin.com/in/rmannibucau> | Tomitriber <http://www.tomitribe.com> ---------- Forwarded message ---------- From: <[email protected]> Date: 2015-03-15 20:26 GMT+01:00 Subject: incubator-johnzon git commit: JOHNZON-40 virtual object support for our mapper To: [email protected] Repository: incubator-johnzon Updated Branches: refs/heads/master dcc3a2c2a -> 5d656c825 JOHNZON-40 virtual object support for our mapper Project: http://git-wip-us.apache.org/repos/asf/incubator-johnzon/repo Commit: http://git-wip-us.apache.org/repos/asf/incubator-johnzon/commit/5d656c82 Tree: http://git-wip-us.apache.org/repos/asf/incubator-johnzon/tree/5d656c82 Diff: http://git-wip-us.apache.org/repos/asf/incubator-johnzon/diff/5d656c82 Branch: refs/heads/master Commit: 5d656c825e6351f26a93cbffcf10ef49b310ded9 Parents: dcc3a2c Author: Romain Manni-Bucau <[email protected]> Authored: Sun Mar 15 20:25:35 2015 +0100 Committer: Romain Manni-Bucau <[email protected]> Committed: Sun Mar 15 20:25:35 2015 +0100 ---------------------------------------------------------------------- .../org/apache/johnzon/mapper/Experimental.java | 33 ++ .../johnzon/mapper/JohnzonVirtualObject.java | 52 +++ .../johnzon/mapper/JohnzonVirtualObjects.java | 36 ++ .../java/org/apache/johnzon/mapper/Mapper.java | 17 +- .../johnzon/mapper/reflection/Mappings.java | 360 +++++++++++++++++-- .../org/apache/johnzon/mapper/MapperTest.java | 49 +++ 6 files changed, 510 insertions(+), 37 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/incubator-johnzon/blob/5d656c82/johnzon-mapper/src/main/java/org/apache/johnzon/mapper/Experimental.java ---------------------------------------------------------------------- diff --git a/johnzon-mapper/src/main/java/org/apache/johnzon/mapper/Experimental.java b/johnzon-mapper/src/main/java/org/apache/johnzon/mapper/Experimental.java new file mode 100644 index 0000000..ba7fe01 --- /dev/null +++ b/johnzon-mapper/src/main/java/org/apache/johnzon/mapper/Experimental.java @@ -0,0 +1,33 @@ +/* + * 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.TYPE; +import static java.lang.annotation.RetentionPolicy.RUNTIME; + +/** + * Marker for experimental API. + */ +@Target(TYPE) +@Retention(RUNTIME) +public @interface Experimental { +} http://git-wip-us.apache.org/repos/asf/incubator-johnzon/blob/5d656c82/johnzon-mapper/src/main/java/org/apache/johnzon/mapper/JohnzonVirtualObject.java ---------------------------------------------------------------------- diff --git a/johnzon-mapper/src/main/java/org/apache/johnzon/mapper/JohnzonVirtualObject.java b/johnzon-mapper/src/main/java/org/apache/johnzon/mapper/JohnzonVirtualObject.java new file mode 100644 index 0000000..cc256fd --- /dev/null +++ b/johnzon-mapper/src/main/java/org/apache/johnzon/mapper/JohnzonVirtualObject.java @@ -0,0 +1,52 @@ +/* + * 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.Inherited; +import java.lang.annotation.Retention; +import java.lang.annotation.Target; + +import static java.lang.annotation.ElementType.TYPE; +import static java.lang.annotation.RetentionPolicy.RUNTIME; + +/** + * Example: @JohnzonVirtualObject(path = {"nested", "nested-again"}, field = { "a", "b" }) + * will generate {"nested":{"nested-again":{"a":"xxx", "b": "yyy"}}} + */ +@Target(TYPE) +@Retention(RUNTIME) +@Inherited +@Experimental +public @interface JohnzonVirtualObject { + /** + * @return the virtual object(s) path. + */ + String[] path(); + + /** + * @return the list of fields to consider. + */ + Field[] fields(); + + @interface Field { + String value(); + boolean read() default true; + boolean write() default true; + } +} http://git-wip-us.apache.org/repos/asf/incubator-johnzon/blob/5d656c82/johnzon-mapper/src/main/java/org/apache/johnzon/mapper/JohnzonVirtualObjects.java ---------------------------------------------------------------------- diff --git a/johnzon-mapper/src/main/java/org/apache/johnzon/mapper/JohnzonVirtualObjects.java b/johnzon-mapper/src/main/java/org/apache/johnzon/mapper/JohnzonVirtualObjects.java new file mode 100644 index 0000000..e539b10 --- /dev/null +++ b/johnzon-mapper/src/main/java/org/apache/johnzon/mapper/JohnzonVirtualObjects.java @@ -0,0 +1,36 @@ +/* + * 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.Inherited; +import java.lang.annotation.Retention; +import java.lang.annotation.Target; + +import static java.lang.annotation.ElementType.TYPE; +import static java.lang.annotation.RetentionPolicy.RUNTIME; + +@Target(TYPE) +@Retention(RUNTIME) +@Inherited +public @interface JohnzonVirtualObjects { + /** + * @return list of virtual objects for this class. + */ + JohnzonVirtualObject[] value(); +} http://git-wip-us.apache.org/repos/asf/incubator-johnzon/blob/5d656c82/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 6a92f5d..efe1868 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 @@ -24,6 +24,7 @@ import java.io.InputStream; import java.io.OutputStream; import java.io.OutputStreamWriter; import java.io.Reader; +import java.io.StringReader; import java.io.StringWriter; import java.io.Writer; import java.lang.reflect.Array; @@ -93,7 +94,7 @@ public class Mapper { this.close = doClose; this.converters = new ConcurrentHashMap<Type, Converter<?>>(converters); this.version = version; - this.mappings = new Mappings(attributeOrder, accessMode, hiddenConstructorSupported, useConstructors); + this.mappings = new Mappings(attributeOrder, accessMode, hiddenConstructorSupported, useConstructors, version); this.skipNull = skipNull; this.skipEmptyArray = skipEmptyArray; this.treatByteArrayAsBase64 = treatByteArrayAsBase64; @@ -352,11 +353,13 @@ public class Mapper { } } + final Object val = getter.converter == null ? value : getter.converter.toString(value); + generator = writeValue(generator, value.getClass(), getter.primitive, getter.array, getter.collection, getter.map, getterEntry.getKey(), - getter.converter == null ? value : getter.converter.toString(value)); + val); } return generator; } @@ -453,14 +456,16 @@ public class Mapper { return newGen; } + public <T> T readObject(final String string, final Type clazz) { + return readObject(new StringReader(string), clazz); + } + public <T> T readObject(final Reader stream, final Type clazz) { - final JsonReader reader = readerFactory.createReader(stream); - return mapObject(clazz, reader); + return mapObject(clazz, readerFactory.createReader(stream)); } public <T> T readObject(final InputStream stream, final Type clazz) { - final JsonReader reader = readerFactory.createReader(stream); - return mapObject(clazz, reader); + return mapObject(clazz, readerFactory.createReader(stream)); } private <T> T mapObject(final Type clazz, final JsonReader reader) { http://git-wip-us.apache.org/repos/asf/incubator-johnzon/blob/5d656c82/johnzon-mapper/src/main/java/org/apache/johnzon/mapper/reflection/Mappings.java ---------------------------------------------------------------------- diff --git a/johnzon-mapper/src/main/java/org/apache/johnzon/mapper/reflection/Mappings.java b/johnzon-mapper/src/main/java/org/apache/johnzon/mapper/reflection/Mappings.java index 1401d1f..77a433a 100644 --- a/johnzon-mapper/src/main/java/org/apache/johnzon/mapper/reflection/Mappings.java +++ b/johnzon-mapper/src/main/java/org/apache/johnzon/mapper/reflection/Mappings.java @@ -21,10 +21,13 @@ package org.apache.johnzon.mapper.reflection; import org.apache.johnzon.mapper.Converter; import org.apache.johnzon.mapper.JohnzonConverter; import org.apache.johnzon.mapper.JohnzonIgnore; +import org.apache.johnzon.mapper.JohnzonVirtualObject; +import org.apache.johnzon.mapper.JohnzonVirtualObjects; import org.apache.johnzon.mapper.access.AccessMode; import java.beans.ConstructorProperties; import java.lang.annotation.Annotation; +import java.lang.reflect.Array; import java.lang.reflect.Constructor; import java.lang.reflect.Modifier; import java.lang.reflect.ParameterizedType; @@ -34,6 +37,9 @@ import java.math.BigInteger; import java.util.Collection; import java.util.Comparator; import java.util.HashMap; +import java.util.HashSet; +import java.util.LinkedHashMap; +import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.Queue; @@ -43,6 +49,8 @@ import java.util.TreeMap; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; +import static java.util.Arrays.asList; + public class Mappings { public static class ClassMapping { public final Class<?> clazz; @@ -157,29 +165,37 @@ public class Mappings { public final Type paramType; public final Converter<?> converter; public final boolean primitive; + public final boolean array; - public Setter(final AccessMode.Writer writer, final boolean primitive, final Type paramType, final Converter<?> converter, final int version) { + public Setter(final AccessMode.Writer writer, final boolean primitive, final boolean array, + final Type paramType, final Converter<?> converter, final int version) { this.writer = writer; this.paramType = paramType; this.converter = converter; this.version = version; this.primitive = primitive; + this.array = array; } } + private static final JohnzonParameterizedType VIRTUAL_TYPE = new JohnzonParameterizedType(Map.class, String.class, Object.class); + protected final ConcurrentMap<Type, ClassMapping> classes = new ConcurrentHashMap<Type, ClassMapping>(); protected final ConcurrentMap<Type, CollectionMapping> collections = new ConcurrentHashMap<Type, CollectionMapping>(); protected final Comparator<String> fieldOrdering; private final boolean supportHiddenConstructors; private final boolean supportConstructors; private final AccessMode accessMode; + private final int version; public Mappings(final Comparator<String> attributeOrder, final AccessMode accessMode, - final boolean supportHiddenConstructors, final boolean supportConstructors) { + final boolean supportHiddenConstructors, final boolean supportConstructors, + final int version) { this.fieldOrdering = attributeOrder; this.accessMode = accessMode; this.supportHiddenConstructors = supportHiddenConstructors; this.supportConstructors = supportConstructors; + this.version = version; } public <T> CollectionMapping findCollectionMapping(final ParameterizedType genericType) { @@ -270,42 +286,133 @@ public class Mappings { } private ClassMapping createClassMapping(final Class<?> clazz) { - final Map<String, Getter> getters = fieldOrdering != null ? - new TreeMap<String, Getter>(fieldOrdering) : new HashMap<String, Getter>(); - final Map<String, Setter> setters = fieldOrdering != null ? - new TreeMap<String, Setter>(fieldOrdering) : new HashMap<String, Setter>(); - - for (final Map.Entry<String, AccessMode.Reader> reader : accessMode.findReaders(clazz).entrySet()) { - final AccessMode.Reader value = reader.getValue(); - final JohnzonIgnore readIgnore = value.getAnnotation(JohnzonIgnore.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; - getters.put(reader.getKey(), new Getter(value, 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(value), - readIgnore != null ? readIgnore.minVersion() : -1)); + final Map<String, Getter> getters = newOrderedMap(); + final Map<String, Setter> setters = newOrderedMap(); + + final Map<String, AccessMode.Reader> readers = accessMode.findReaders(clazz); + final Map<String, AccessMode.Writer> writers = accessMode.findWriters(clazz); + + final Collection<String> virtualFields = new HashSet<String>(); + { + final JohnzonVirtualObjects virtualObjects = clazz.getAnnotation(JohnzonVirtualObjects.class); + if (virtualObjects != null) { + for (final JohnzonVirtualObject virtualObject : virtualObjects.value()) { + handleVirtualObject(virtualFields, virtualObject, getters, setters, readers, writers); + } + } + + final JohnzonVirtualObject virtualObject = clazz.getAnnotation(JohnzonVirtualObject.class); + if (virtualObject != null) { + handleVirtualObject(virtualFields, virtualObject, getters, setters, readers, writers); } } - for (final Map.Entry<String, AccessMode.Writer> writer : accessMode.findWriters(clazz).entrySet()) { - final AccessMode.Writer value = writer.getValue(); - final JohnzonIgnore writeIgnore = value.getAnnotation(JohnzonIgnore.class); - if (writeIgnore == null || writeIgnore.minVersion() >= 0) { - final String key = writer.getKey(); - if (key.equals("metaClass")) { - continue; - } - final Type param = value.getType(); - setters.put(key, new Setter(value, isPrimitive(param), param, findConverter(value), writeIgnore != null ? writeIgnore.minVersion() : -1)); + + for (final Map.Entry<String, AccessMode.Reader> reader : readers.entrySet()) { + final String key = reader.getKey(); + if (virtualFields.contains(key)) { + continue; + } + addGetterIfNeeded(getters, key, reader.getValue()); + } + + for (final Map.Entry<String, AccessMode.Writer> writer : writers.entrySet()) { + final String key = writer.getKey(); + if (virtualFields.contains(key)) { + continue; } + addSetterIfNeeded(setters, key, writer.getValue()); } return new ClassMapping(clazz, getters, setters, supportHiddenConstructors, supportConstructors); } + private <T> Map<String, T> newOrderedMap() { + return fieldOrdering != null ? new TreeMap<String, T>(fieldOrdering) : new HashMap<String, T>(); + } + + private void addSetterIfNeeded(final Map<String, Setter> setters, + final String key, + final AccessMode.Writer value) { + final JohnzonIgnore writeIgnore = value.getAnnotation(JohnzonIgnore.class); + if (writeIgnore == null || writeIgnore.minVersion() >= 0) { + if (key.equals("metaClass")) { + return; + } + 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, + findConverter(value), writeIgnore != null ? writeIgnore.minVersion() : -1); + setters.put(key, setter); + } + } + + private void addGetterIfNeeded(final Map<String, Getter> getters, + final String key, + final AccessMode.Reader value) { + final JohnzonIgnore readIgnore = value.getAnnotation(JohnzonIgnore.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), + 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(value), + readIgnore != null ? readIgnore.minVersion() : -1); + getters.put(key, getter); + } + } + + // idea is quite trivial, simulate an object with a Map<String, Object> + private void handleVirtualObject(final Collection<String> virtualFields, + final JohnzonVirtualObject o, + final Map<String, Getter> getters, + final Map<String, Setter> setters, + final Map<String, AccessMode.Reader> readers, + final Map<String, AccessMode.Writer> writers) { + final String[] path = o.path(); + if (path.length < 1) { + throw new IllegalArgumentException("@JohnzonVirtualObject need a path"); + } + + // add them to ignored fields + for (final JohnzonVirtualObject.Field f : o.fields()) { + virtualFields.add(f.value()); + } + + // build "this" model + final Map<String, Getter> objectGetters = newOrderedMap(); + final Map<String, Setter> objectSetters = newOrderedMap(); + + for (final JohnzonVirtualObject.Field f : o.fields()) { + final String name = f.value(); + if (f.read()) { + final AccessMode.Reader reader = readers.get(name); + if (reader != null) { + addGetterIfNeeded(objectGetters, name, reader); + } + } + if (f.write()) { + final AccessMode.Writer writer = writers.get(name); + if (writer != null) { + addSetterIfNeeded(objectSetters, name, writer); + } + } + } + + final String key = path[0]; + + final Getter getter = getters.get(key); + final MapBuilderReader newReader = new MapBuilderReader(objectGetters, path, version); + getters.put(key, new Getter(getter == null ? newReader : new CompositeReader(getter.reader, newReader), false, false, false, true, null, -1)); + + final Setter newSetter = setters.get(key); + final MapUnwrapperWriter newWriter = new MapUnwrapperWriter(objectSetters, path); + setters.put(key, new Setter(newSetter == null ? newWriter : new CompositeWriter(newSetter.writer, newWriter), false, false, VIRTUAL_TYPE, null, -1)); + } + private static Converter findConverter(final AccessMode.DecoratedType method) { Converter converter = null; if (method.getAnnotation(JohnzonConverter.class) != null) { @@ -317,4 +424,195 @@ public class Mappings { } return converter; } + + private static class MapBuilderReader implements AccessMode.Reader { + private final Map<String, Getter> getters; + private final Map<String, Object> template; + private final String[] paths; + private final int version; + + public MapBuilderReader(final Map<String, Getter> objectGetters, final String[] paths, final int version) { + this.getters = objectGetters; + this.paths = paths; + this.template = new LinkedHashMap<String, Object>(); + this.version = version; + + Map<String, Object> last = this.template; + for (int i = 1; i < paths.length; i++) { + final Map<String, Object> newLast = new LinkedHashMap<String, Object>(); + last.put(paths[i], newLast); + last = newLast; + } + } + + @Override + public Object read(final Object instance) { + final Map<String, Object> map = new LinkedHashMap<String, Object>(template); + Map<String, Object> nested = map; + for (int i = 1; i < paths.length; i++) { + nested = Map.class.cast(nested.get(paths[i])); + } + for (final Map.Entry<String, Getter> g : getters.entrySet()) { + final Mappings.Getter getter = g.getValue(); + final Object value = getter.reader.read(instance); + final Object val = value == null || getter.converter == null ? value : getter.converter.toString(value); + if (val == null) { + continue; + } + if (getter.version >= 0 && version >= getter.version) { + continue; + } + + nested.put(g.getKey(), val); + } + return map; + } + + @Override + public Type getType() { + return VIRTUAL_TYPE; + } + + @Override + public <T extends Annotation> T getAnnotation(final Class<T> clazz) { + throw new UnsupportedOperationException("getAnnotation shouldn't get called for virtual fields"); + } + } + + private static class MapUnwrapperWriter implements AccessMode.Writer { + private final Map<String, Setter> writers; + private final Map<String, Class<?>> componentTypes; + private final String[] paths; + + public MapUnwrapperWriter(final Map<String, Setter> writers, final String[] paths) { + this.writers = writers; + this.paths = paths; + this.componentTypes = new HashMap<String, Class<?>>(); + + for (final Map.Entry<String, Setter> setter : writers.entrySet()) { + if (setter.getValue().array) { + componentTypes.put(setter.getKey(), Class.class.cast(setter.getValue().paramType).getComponentType()); + } + } + } + + @Override + public void write(final Object instance, final Object value) { + Map<String, Object> nested = null; + for (final String path : paths) { + nested = Map.class.cast(nested == null ? value : nested.get(path)); + if (nested == null) { + return; + } + } + + for (final Map.Entry<String, Setter> setter : writers.entrySet()) { + final Setter setterValue = setter.getValue(); + final String key = setter.getKey(); + final Object rawValue = nested.get(key); + Object val = value == null || setterValue.converter == null ? + rawValue : Converter.class.cast(setterValue.converter).toString(rawValue); + if (val == null) { + continue; + } + + if (setterValue.array && Collection.class.isInstance(val)) { + final Collection<?> collection = Collection.class.cast(val); + final Object[] array = (Object[]) Array.newInstance(componentTypes.get(key), collection.size()); + val = collection.toArray(array); + } + + final AccessMode.Writer setterMethod = setterValue.writer; + setterMethod.write(instance, val); + } + } + + @Override + public Type getType() { + return VIRTUAL_TYPE; + } + + @Override + public <T extends Annotation> T getAnnotation(final Class<T> clazz) { + throw new UnsupportedOperationException("getAnnotation shouldn't get called for virtual fields"); + } + } + + private static class CompositeReader implements AccessMode.Reader { + private final AccessMode.Reader[] delegates; + + public CompositeReader(final AccessMode.Reader... delegates) { + final Collection<AccessMode.Reader> all = new LinkedList<AccessMode.Reader>(); + for (final AccessMode.Reader r : delegates) { + if (CompositeReader.class.isInstance(r)) { + all.addAll(asList(CompositeReader.class.cast(r).delegates)); + } else { + all.add(r); + } + } + this.delegates = all.toArray(new AccessMode.Reader[all.size()]); + } + + @Override + public Object read(final Object instance) { + final Map<String, Object> map = new LinkedHashMap<String, Object>(); + for (final AccessMode.Reader reader : delegates) { + final Map<String, Object> readerMap = (Map<String, Object>) reader.read(instance); + for (final Map.Entry<String, Object> entry :readerMap.entrySet()) { + final Object o = map.get(entry.getKey()); + if (o == null) { + map.put(entry.getKey(), entry.getValue()); + } else if (Map.class.isInstance(o)) { + // TODO + } else { + throw new IllegalStateException(entry.getKey() + " is ambiguous"); + } + } + } + return map; + } + + @Override + public Type getType() { + return VIRTUAL_TYPE; + } + + @Override + public <T extends Annotation> T getAnnotation(final Class<T> clazz) { + throw new UnsupportedOperationException("getAnnotation shouldn't get called for virtual fields"); + } + } + + private static class CompositeWriter implements AccessMode.Writer { + private final AccessMode.Writer[] delegates; + + public CompositeWriter(final AccessMode.Writer... writers) { + final Collection<AccessMode.Writer> all = new LinkedList<AccessMode.Writer>(); + for (final AccessMode.Writer r : writers) { + if (CompositeWriter.class.isInstance(r)) { + all.addAll(asList(CompositeWriter.class.cast(r).delegates)); + } else { + all.add(r); + } + } + this.delegates = all.toArray(new AccessMode.Writer[all.size()]); + } + + @Override + public void write(final Object instance, final Object value) { + for (final AccessMode.Writer w : delegates) { + w.write(instance, value); + } + } + + @Override + public Type getType() { + return VIRTUAL_TYPE; + } + + @Override + public <T extends Annotation> T getAnnotation(final Class<T> clazz) { + throw new UnsupportedOperationException("getAnnotation shouldn't get called for virtual fields"); + } + } } http://git-wip-us.apache.org/repos/asf/incubator-johnzon/blob/5d656c82/johnzon-mapper/src/test/java/org/apache/johnzon/mapper/MapperTest.java ---------------------------------------------------------------------- diff --git a/johnzon-mapper/src/test/java/org/apache/johnzon/mapper/MapperTest.java b/johnzon-mapper/src/test/java/org/apache/johnzon/mapper/MapperTest.java index 4415c53..c39a9d5 100644 --- a/johnzon-mapper/src/test/java/org/apache/johnzon/mapper/MapperTest.java +++ b/johnzon-mapper/src/test/java/org/apache/johnzon/mapper/MapperTest.java @@ -18,6 +18,7 @@ */ package org.apache.johnzon.mapper; +import org.apache.johnzon.mapper.access.FieldAccessMode; import org.apache.johnzon.mapper.reflection.JohnzonCollectionType; import org.apache.johnzon.mapper.reflection.JohnzonParameterizedType; import org.junit.Test; @@ -37,6 +38,7 @@ import java.util.List; import java.util.Map; import static java.util.Arrays.asList; +import static org.junit.Assert.assertArrayEquals; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotNull; @@ -475,6 +477,31 @@ public class MapperTest { } } + @Test + public void fakedObject() { + final ChildOfFakedObject source = new ChildOfFakedObject(); + source.a = 1; + source.b = 2; + source.c = new String[] { "3", "4" }; + source.children = asList("5", "6"); + + final Mapper mapper = new MapperBuilder().setAttributeOrder(new Comparator<String>() { + @Override + public int compare(final String o1, final String o2) { + return o1.compareTo(o2); + } + }).setAccessMode(new FieldAccessMode()).build(); + + final String asString = mapper.writeObjectAsString(source); + assertEquals("{\"children\":[\"5\",\"6\"],\"nested\":{\"b\":2,\"sub\":{\"a\":1,\"c\":[\"3\",\"4\"]}}}", asString); + + final ChildOfFakedObject childOfFakedObject = mapper.readObject(asString, ChildOfFakedObject.class); + assertEquals(source.a, childOfFakedObject.a); + assertEquals(source.b, childOfFakedObject.b); + assertArrayEquals(source.c, childOfFakedObject.c); + assertEquals(source.children, childOfFakedObject.children); + } + public static class NanHolder { private Double nan = Double.NaN; @@ -859,6 +886,28 @@ public class MapperTest { } } } + + public static class FakeNestedObject { + protected int a; + protected int b; + protected String[] c; + } + + @JohnzonVirtualObjects({ + @JohnzonVirtualObject( + path = "nested", + fields = @JohnzonVirtualObject.Field("b") + ), + @JohnzonVirtualObject( + path = { "nested", "sub" }, + fields = { + @JohnzonVirtualObject.Field("a"), @JohnzonVirtualObject.Field("c") + } + ) + }) + public static class ChildOfFakedObject extends FakeNestedObject { + protected List<String> children; + } /*public static class ByteArray {
