You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@johnzon.apache.org by rm...@apache.org on 2015/03/15 20:26:04 UTC

incubator-johnzon git commit: JOHNZON-40 virtual object support for our mapper

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 <rm...@apache.org>
Authored: Sun Mar 15 20:25:35 2015 +0100
Committer: Romain Manni-Bucau <rm...@apache.org>
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 {
         


Fwd: incubator-johnzon git commit: JOHNZON-40 virtual object support for our mapper

Posted by Romain Manni-Bucau <rm...@gmail.com>.
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: <rm...@apache.org>
Date: 2015-03-15 20:26 GMT+01:00
Subject: incubator-johnzon git commit: JOHNZON-40 virtual object support
for our mapper
To: commits@johnzon.incubator.apache.org


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 <rm...@apache.org>
Authored: Sun Mar 15 20:25:35 2015 +0100
Committer: Romain Manni-Bucau <rm...@apache.org>
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 {