You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@johnzon.apache.org by st...@apache.org on 2016/04/03 19:01:23 UTC

[04/10] incubator-johnzon git commit: JOHNZON-71 introduce MapperConfig and improve ObjectConverter interface

JOHNZON-71 introduce MapperConfig and improve ObjectConverter interface


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

Branch: refs/heads/master
Commit: e41b829b34e785e28b24d9771e27976a0fee2ce6
Parents: 45cfcf2
Author: Mark Struberg <st...@apache.org>
Authored: Sun Mar 27 19:04:09 2016 +0200
Committer: Mark Struberg <st...@apache.org>
Committed: Sun Mar 27 19:04:09 2016 +0200

----------------------------------------------------------------------
 .../johnzon/core/JsonParserFactoryImpl.java     |   2 +-
 .../apache/johnzon/mapper/JsonbGenerator.java   |  61 --
 .../org/apache/johnzon/mapper/JsonbParser.java  |  40 -
 .../java/org/apache/johnzon/mapper/Mapper.java  |  65 +-
 .../apache/johnzon/mapper/MapperBuilder.java    |  76 +-
 .../org/apache/johnzon/mapper/MapperConfig.java | 131 ++++
 .../apache/johnzon/mapper/MappingGenerator.java |  61 ++
 .../apache/johnzon/mapper/MappingParser.java    |  49 ++
 .../org/apache/johnzon/mapper/Mappings.java     | 765 ++++++++++++++++++
 .../apache/johnzon/mapper/ObjectConverter.java  |   7 +-
 .../johnzon/mapper/reflection/Mappings.java     | 770 -------------------
 .../org/apache/johnzon/mapper/AdapterTest.java  |   6 +-
 .../apache/johnzon/mapper/ObjectTypeTest.java   |  41 +-
 13 files changed, 1107 insertions(+), 967 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/incubator-johnzon/blob/e41b829b/johnzon-core/src/main/java/org/apache/johnzon/core/JsonParserFactoryImpl.java
----------------------------------------------------------------------
diff --git a/johnzon-core/src/main/java/org/apache/johnzon/core/JsonParserFactoryImpl.java b/johnzon-core/src/main/java/org/apache/johnzon/core/JsonParserFactoryImpl.java
index 6480ac8..c57f144 100644
--- a/johnzon-core/src/main/java/org/apache/johnzon/core/JsonParserFactoryImpl.java
+++ b/johnzon-core/src/main/java/org/apache/johnzon/core/JsonParserFactoryImpl.java
@@ -32,7 +32,7 @@ import javax.json.JsonObject;
 import javax.json.stream.JsonParser;
 import javax.json.stream.JsonParserFactory;
 
-class JsonParserFactoryImpl extends AbstractJsonFactory implements JsonParserFactory {
+public class JsonParserFactoryImpl extends AbstractJsonFactory implements JsonParserFactory {
     public static final String MAX_STRING_LENGTH = "org.apache.johnzon.max-string-length";
     public static final int DEFAULT_MAX_STRING_LENGTH = Integer.getInteger(MAX_STRING_LENGTH, 10 * 1024 * 1024); //10m
     

http://git-wip-us.apache.org/repos/asf/incubator-johnzon/blob/e41b829b/johnzon-mapper/src/main/java/org/apache/johnzon/mapper/JsonbGenerator.java
----------------------------------------------------------------------
diff --git a/johnzon-mapper/src/main/java/org/apache/johnzon/mapper/JsonbGenerator.java b/johnzon-mapper/src/main/java/org/apache/johnzon/mapper/JsonbGenerator.java
deleted file mode 100644
index 9edfa01..0000000
--- a/johnzon-mapper/src/main/java/org/apache/johnzon/mapper/JsonbGenerator.java
+++ /dev/null
@@ -1,61 +0,0 @@
-/*
- * 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 javax.json.stream.JsonGenerator;
-
-/**
- * Handles writing Json for Objects.
- * Internally it uses a {@link JsonGenerator} to write JSON
- *
- * To write JSON-P structure elements you can use the {@link #getJsonGenerator()} method.
- *
- */
-public interface JsonbGenerator {
-
-    /**
-     * @return the {@link JsonGenerator} used internally to write the JSON output.
-     */
-    JsonGenerator getJsonGenerator();
-
-    /**
-     * Write the given Object o into the current JSON layer.
-     * This will <em>not</em> open a new json layer ('{', '}')
-     * but really just write the attributes of o to the currently opened layer.
-     *
-     * Consider you have a class
-     * <pre>
-     *     public class Customer {
-     *         private String firstName;
-     *         private String lastName;
-     *         private Address address;
-     *         ...
-     *     }
-     * </pre>
-     * then the resulting JSON String will e.g. look like
-     * <pre>
-     *     "firstName":"Karl", "lastName":"SomeName", "address":{"street":"mystreet"}
-     * </pre>
-     * @param o the object to write
-     * @return itself, for easier chaining of commands
-     */
-    JsonbGenerator writeObject(Object o);
-
-
-}

http://git-wip-us.apache.org/repos/asf/incubator-johnzon/blob/e41b829b/johnzon-mapper/src/main/java/org/apache/johnzon/mapper/JsonbParser.java
----------------------------------------------------------------------
diff --git a/johnzon-mapper/src/main/java/org/apache/johnzon/mapper/JsonbParser.java b/johnzon-mapper/src/main/java/org/apache/johnzon/mapper/JsonbParser.java
deleted file mode 100644
index ef128f3..0000000
--- a/johnzon-mapper/src/main/java/org/apache/johnzon/mapper/JsonbParser.java
+++ /dev/null
@@ -1,40 +0,0 @@
-/*
- * 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 javax.json.stream.JsonGenerator;
-import javax.json.stream.JsonParser;
-
-/**
- * Handles reading Json for Objects.
- * Internally it uses a {@link JsonParser} to write JSON
- *
- * To write JSON-P structure elements you can use the {@link #getJsonParser()} ()} method.
- *
- */
-public interface JsonbParser {
-
-    /**
-     * @return the {@link JsonGenerator} used internally to write the JSON output.
-     */
-    JsonParser getJsonParser();
-
-
-
-}

http://git-wip-us.apache.org/repos/asf/incubator-johnzon/blob/e41b829b/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 063a6e3..53e0904 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
@@ -25,7 +25,6 @@ import org.apache.johnzon.mapper.internal.AdapterKey;
 import org.apache.johnzon.mapper.internal.ConverterAdapter;
 import org.apache.johnzon.mapper.reflection.JohnzonCollectionType;
 import org.apache.johnzon.mapper.reflection.JohnzonParameterizedType;
-import org.apache.johnzon.mapper.reflection.Mappings;
 
 import javax.json.JsonArray;
 import javax.json.JsonNumber;
@@ -54,7 +53,6 @@ import java.lang.reflect.ParameterizedType;
 import java.lang.reflect.Type;
 import java.math.BigDecimal;
 import java.math.BigInteger;
-import java.nio.charset.Charset;
 import java.util.ArrayDeque;
 import java.util.ArrayList;
 import java.util.Collection;
@@ -87,43 +85,28 @@ public class Mapper implements Closeable {
     private static final Adapter<Object, String> FALLBACK_CONVERTER = new ConverterAdapter<Object>(new FallbackConverter());
     private static final JohnzonParameterizedType ANY_LIST = new JohnzonParameterizedType(List.class, Object.class);
 
+    protected final MapperConfig config;
     protected final Mappings mappings;
     protected final JsonReaderFactory readerFactory;
     protected final JsonGeneratorFactory generatorFactory;
-    protected final boolean close;
     protected final ConcurrentMap<AdapterKey, Adapter<?, ?>> adapters;
     protected final ConcurrentMap<Adapter<?, ?>, AdapterKey> reverseAdaptersRegistry = new ConcurrentHashMap<Adapter<?, ?>, AdapterKey>();
     protected final int version;
-    protected final boolean skipNull;
-    protected final boolean skipEmptyArray;
-    protected final boolean treatByteArrayAsBase64;
-    protected final boolean treatByteArrayAsBase64URL;
-    protected final boolean readAttributeBeforeWrite;
-    protected final Charset encoding;
     protected final ReaderHandler readerHandler;
     protected final Collection<Closeable> closeables;
 
-    // CHECKSTYLE:OFF
-    public Mapper(final JsonReaderFactory readerFactory, final JsonGeneratorFactory generatorFactory,
-                  final boolean doClose, final Map<AdapterKey, Adapter<?, ?>> adapters,
-                  final int version, final Comparator<String> attributeOrder, final boolean skipNull, final boolean skipEmptyArray,
-                  final AccessMode accessMode, final boolean treatByteArrayAsBase64, final boolean treatByteArrayAsBase64URL, final Charset encoding,
-                  final Collection<Closeable> closeables, final boolean readAttributeBeforeWrite) {
-    // CHECKSTYLE:ON
+    Mapper(final JsonReaderFactory readerFactory, final JsonGeneratorFactory generatorFactory, MapperConfig config,
+                  final Map<AdapterKey, Adapter<?, ?>> adapters,
+                  final int version, final Comparator<String> attributeOrder,
+                  final Collection<Closeable> closeables) {
         this.readerFactory = readerFactory;
         this.generatorFactory = generatorFactory;
-        this.close = doClose;
+        this.config = config;
         this.adapters = new ConcurrentHashMap<AdapterKey, Adapter<?, ?>>(adapters);
         this.version = version;
-        this.mappings = new Mappings(attributeOrder, accessMode, version, this.adapters);
-        this.skipNull = skipNull;
-        this.skipEmptyArray = skipEmptyArray;
-        this.treatByteArrayAsBase64 = treatByteArrayAsBase64;
-        this.treatByteArrayAsBase64URL = treatByteArrayAsBase64URL;
-        this.encoding = encoding;
+        this.mappings = new Mappings(attributeOrder, config.getAccessMode(), version, this.adapters);
         this.readerHandler = ReaderHandler.create(readerFactory);
         this.closeables = closeables;
-        this.readAttributeBeforeWrite = readAttributeBeforeWrite;
     }
 
     private static JsonGenerator writePrimitives(final JsonGenerator generator, final Object value) {
@@ -245,7 +228,7 @@ public class Mapper implements Closeable {
     }
 
     public <T> void writeArray(final Collection<T> object, final OutputStream stream) {
-        writeArray(object, new OutputStreamWriter(stream, encoding));
+        writeArray(object, new OutputStreamWriter(stream, config.getEncoding()));
     }
 
     public <T> void writeArray(final Collection<T> object, final Writer stream) {
@@ -276,7 +259,7 @@ public class Mapper implements Closeable {
     }
 
     private void doCloseOrFlush(final JsonGenerator generator) {
-        if (close) {
+        if (config.isClose()) {
             generator.close();
         } else {
             generator.flush();
@@ -284,7 +267,7 @@ public class Mapper implements Closeable {
     }
 
     public <T> void writeIterable(final Iterable<T> object, final OutputStream stream) {
-        writeIterable(object, new OutputStreamWriter(stream, encoding));
+        writeIterable(object, new OutputStreamWriter(stream, config.getEncoding()));
     }
 
     public <T> void writeIterable(final Iterable<T> object, final Writer stream) {
@@ -311,7 +294,7 @@ public class Mapper implements Closeable {
             } catch (final IOException e) {
                 throw new MapperException(e);
             } finally {
-                if (close) {
+                if (config.isClose()) {
                     try {
                         stream.close();
                     } catch (final IOException e) {
@@ -332,7 +315,7 @@ public class Mapper implements Closeable {
     }
 
     public void writeObject(final Object object, final OutputStream stream) {
-        final JsonGenerator generator = generatorFactory.createGenerator(stream, encoding);
+        final JsonGenerator generator = generatorFactory.createGenerator(stream, config.getEncoding());
         doWriteHandlingNullObject(object, generator);
     }
 
@@ -435,7 +418,7 @@ public class Mapper implements Closeable {
             }
 
             if (value == null) {
-                if (skipNull && !getter.reader.isNillable()) {
+                if (config.isSkipNull() && !getter.reader.isNillable()) {
                     continue;
                 } else {
                     gen.writeNull(getterEntry.getKey());
@@ -462,7 +445,7 @@ public class Mapper implements Closeable {
             final Object key = entry.getKey();
 
             if (value == null) {
-                if (skipNull) {
+                if (config.isSkipNull()) {
                     continue;
                 } else {
                     gen.writeNull(key == null ? "null" : key.toString());
@@ -490,16 +473,16 @@ public class Mapper implements Closeable {
                                      final String key, final Object value) throws InvocationTargetException, IllegalAccessException {
         if (array) {
             final int length = Array.getLength(value);
-            if (length == 0 && skipEmptyArray) {
+            if (length == 0 && config.isSkipEmptyArray()) {
                 return generator;
             }
             
-            if(treatByteArrayAsBase64 && (type == byte[].class /*|| type == Byte[].class*/)) {
+            if(config.isTreatByteArrayAsBase64() && (type == byte[].class /*|| type == Byte[].class*/)) {
                 String base64EncodedByteArray = DatatypeConverter.printBase64Binary((byte[]) value);
                 generator.write(key, base64EncodedByteArray);
                 return generator;
             }
-            if(treatByteArrayAsBase64URL && (type == byte[].class /*|| type == Byte[].class*/)) {
+            if(config.isTreatByteArrayAsBase64URL() && (type == byte[].class /*|| type == Byte[].class*/)) {
                 return generator.write(key, String.valueOf(Adapter.class.cast(adapters.get(new AdapterKey(byte[].class, String.class))).to(value)));
             }
 
@@ -542,7 +525,7 @@ public class Mapper implements Closeable {
                 newGen = doWriteArray(Collection.class.cast(o), generator);
             } else if (o != null && o.getClass().isArray()) {
                 final int length = Array.getLength(o);
-                if (length > 0 || !skipEmptyArray) {
+                if (length > 0 || !config.isSkipEmptyArray()) {
                     newGen = generator.writeStartArray();
                     for (int i = 0; i < length; i++) {
                         newGen = writeItem(newGen, Array.get(o, i));
@@ -613,7 +596,7 @@ public class Mapper implements Closeable {
         } catch (final Exception e) {
             throw new MapperException(e);
         } finally {
-            if (close) {
+            if (config.isClose()) {
                 reader.close();
             }
         }
@@ -630,7 +613,7 @@ public class Mapper implements Closeable {
         } catch (final Exception e) {
             throw new MapperException(e);
         } finally {
-            if (close) {
+            if (config.isClose()) {
                 reader.close();
             }
         }
@@ -655,7 +638,7 @@ public class Mapper implements Closeable {
         } catch (final Exception e) {
             throw new MapperException(e);
         } finally {
-            if (close) {
+            if (config.isClose()) {
                 reader.close();
             }
         }
@@ -687,7 +670,7 @@ public class Mapper implements Closeable {
         } catch (final Exception e) {
             throw new MapperException(e);
         } finally {
-            if (close) {
+            if (config.isClose()) {
                 reader.close();
             }
         }
@@ -790,7 +773,7 @@ public class Mapper implements Closeable {
                 setterMethod.write(t, null);
             } else {
                 Object existingInstance = null;
-                if (readAttributeBeforeWrite) {
+                if (config.isReadAttributeBeforeWrite()) {
                     final Mappings.Getter getter = classMapping.getters.get(setter.getKey());
                     if (getter != null) {
                         try {
@@ -869,7 +852,7 @@ public class Mapper implements Closeable {
             throw new MapperException("Unable to parse " + jsonValue + " to boolean");
         }
 
-        if(treatByteArrayAsBase64 && jsonValue.getValueType() == ValueType.STRING && (type == byte[].class /*|| type == Byte[].class*/)) {
+        if(config.isTreatByteArrayAsBase64() && jsonValue.getValueType() == ValueType.STRING && (type == byte[].class /*|| type == Byte[].class*/)) {
             return DatatypeConverter.parseBase64Binary(((JsonString)jsonValue).getString());
         }
 

http://git-wip-us.apache.org/repos/asf/incubator-johnzon/blob/e41b829b/johnzon-mapper/src/main/java/org/apache/johnzon/mapper/MapperBuilder.java
----------------------------------------------------------------------
diff --git a/johnzon-mapper/src/main/java/org/apache/johnzon/mapper/MapperBuilder.java b/johnzon-mapper/src/main/java/org/apache/johnzon/mapper/MapperBuilder.java
index e78f2f2..eb23074 100644
--- a/johnzon-mapper/src/main/java/org/apache/johnzon/mapper/MapperBuilder.java
+++ b/johnzon-mapper/src/main/java/org/apache/johnzon/mapper/MapperBuilder.java
@@ -43,6 +43,8 @@ import org.apache.johnzon.mapper.converter.URLConverter;
 import org.apache.johnzon.mapper.internal.AdapterKey;
 import org.apache.johnzon.mapper.internal.ConverterAdapter;
 
+import org.apache.johnzon.core.JsonParserFactoryImpl;
+
 import javax.json.JsonReaderFactory;
 import javax.json.spi.JsonProvider;
 import javax.json.stream.JsonGenerator;
@@ -94,27 +96,19 @@ public class MapperBuilder {
         DEFAULT_CONVERTERS.put(new AdapterKey(Locale.class, String.class), new LocaleConverter());
     }
 
+    private MapperConfig builderConfig = new MapperConfig();
+
     private JsonReaderFactory readerFactory;
     private JsonGeneratorFactory generatorFactory;
-    private boolean doCloseOnStreams = false;
     private boolean supportHiddenAccess = true;
     private int version = -1;
     private int maxSize = -1;
     private int bufferSize = -1;
     private String bufferStrategy;
     private Comparator<String> attributeOrder = null;
-    private boolean skipNull = true;
-    private boolean skipEmptyArray = false;
-    private boolean supportsComments = false;
-    protected boolean pretty;
-    private AccessMode accessMode;
-    private boolean treatByteArrayAsBase64;
-    private boolean treatByteArrayAsBase64URL;
     private final Map<AdapterKey, Adapter<?, ?>> adapters = new HashMap<AdapterKey, Adapter<?, ?>>(DEFAULT_CONVERTERS);
     private boolean supportConstructors;
-    private Charset encoding = Charset.forName(System.getProperty("johnzon.mapper.encoding", "UTF-8"));
     private boolean useGetterForCollections;
-    private boolean readAttributeBeforeWrite;
     private String accessModeName;
     private final Collection<Closeable> closeables = new ArrayList<Closeable>();
 
@@ -123,9 +117,9 @@ public class MapperBuilder {
             final JsonProvider provider = JsonProvider.provider();
             final Map<String, Object> config = new HashMap<String, Object>();
             if (bufferStrategy != null) {
-                config.put("org.apache.johnzon.buffer-strategy", bufferStrategy);
+                config.put(JsonParserFactoryImpl.BUFFER_STRATEGY, bufferStrategy);
             }
-            if (pretty) {
+            if (builderConfig.isPrettyPrint()) {
                 config.put(JsonGenerator.PRETTY_PRINTING, true);
             }
 
@@ -134,46 +128,44 @@ public class MapperBuilder {
             }
 
             config.remove(JsonGenerator.PRETTY_PRINTING); // doesnt mean anything anymore for reader
-            if (supportsComments) {
-                config.put("org.apache.johnzon.supports-comments", "true");
+            if (builderConfig.isSupportsComments()) {
+                config.put(JsonParserFactoryImpl.SUPPORTS_COMMENTS, "true");
             }
             if (maxSize > 0) {
-                config.put("org.apache.johnzon.max-string-length", maxSize);
+                config.put(JsonParserFactoryImpl.MAX_STRING_LENGTH, maxSize);
             }
             if (bufferSize > 0) {
-                config.put("org.apache.johnzon.default-char-buffer", bufferSize);
+                config.put(JsonParserFactoryImpl.BUFFER_LENGTH, bufferSize);
             }
             if (readerFactory == null) {
                 readerFactory = provider.createReaderFactory(config);
             }
         }
 
-        if (accessMode == null) {
+        if (builderConfig.getAccessMode() == null) {
             if ("field".equalsIgnoreCase(accessModeName)) {
-                this.accessMode = new FieldAccessMode(supportConstructors, supportHiddenAccess);
+                builderConfig.setAccessMode(new FieldAccessMode(supportConstructors, supportHiddenAccess));
             } else if ("method".equalsIgnoreCase(accessModeName)) {
-                this.accessMode = new MethodAccessMode(supportConstructors, supportHiddenAccess, true);
+                builderConfig.setAccessMode(new MethodAccessMode(supportConstructors, supportHiddenAccess, true));
             } else if ("strict-method".equalsIgnoreCase(accessModeName)) {
-                this.accessMode = new MethodAccessMode(supportConstructors, supportHiddenAccess, false);
+                builderConfig.setAccessMode(new MethodAccessMode(supportConstructors, supportHiddenAccess, false));
             } else if ("both".equalsIgnoreCase(accessModeName)) {
-                this.accessMode = new FieldAndMethodAccessMode(supportConstructors, supportHiddenAccess);
+                builderConfig.setAccessMode(new FieldAndMethodAccessMode(supportConstructors, supportHiddenAccess));
             } else {
-                this.accessMode = new MethodAccessMode(supportConstructors, supportHiddenAccess, useGetterForCollections);
+                builderConfig.setAccessMode(new MethodAccessMode(supportConstructors, supportHiddenAccess, useGetterForCollections));
             }
         }
 
+        // new config so builderConfig can get tweaked again.
+        MapperConfig config = builderConfig.clone();
+
         return new Mapper(
             readerFactory, generatorFactory,
-            doCloseOnStreams,
+            config,
             adapters,
             version,
             attributeOrder,
-            skipNull, skipEmptyArray,
-            accessMode,
-            treatByteArrayAsBase64, treatByteArrayAsBase64URL,
-            encoding,
-            closeables,
-            readAttributeBeforeWrite);
+            closeables);
     }
 
     public MapperBuilder addCloseable(final Closeable closeable) {
@@ -182,11 +174,11 @@ public class MapperBuilder {
     }
 
     public MapperBuilder setIgnoreFieldsForType(final Class<?> type, final String... fields) {
-        if (BaseAccessMode.class.isInstance(accessMode)) {
+        if (BaseAccessMode.class.isInstance(builderConfig.getAccessMode())) {
             if (fields == null || fields.length == 0) {
-                BaseAccessMode.class.cast(accessMode).getFieldsToRemove().remove(type);
+                BaseAccessMode.class.cast(builderConfig.getAccessMode()).getFieldsToRemove().remove(type);
             } else {
-                BaseAccessMode.class.cast(accessMode).getFieldsToRemove().put(type, fields);
+                BaseAccessMode.class.cast(builderConfig.getAccessMode()).getFieldsToRemove().put(type, fields);
             }
         } else {
             throw new IllegalStateException("AccessMode is not an BaseAccessMode");
@@ -200,12 +192,12 @@ public class MapperBuilder {
     }
 
     public MapperBuilder setSupportsComments(final boolean supportsComments) {
-        this.supportsComments = supportsComments;
+        builderConfig.setSupportsComments(supportsComments);
         return this;
     }
 
     public MapperBuilder setPretty(final boolean pretty) {
-        this.pretty = pretty;
+        builderConfig.setPrettyPrint(pretty);
         return this;
     }
 
@@ -225,7 +217,7 @@ public class MapperBuilder {
     }
 
     public MapperBuilder setAccessMode(final AccessMode mode) {
-        this.accessMode = mode;
+        builderConfig.setAccessMode(mode);
         return this;
     }
 
@@ -259,7 +251,7 @@ public class MapperBuilder {
     }
 
     public MapperBuilder setDoCloseOnStreams(final boolean doCloseOnStreams) {
-        this.doCloseOnStreams = doCloseOnStreams;
+        builderConfig.setClose(doCloseOnStreams);
         return this;
     }
 
@@ -297,22 +289,22 @@ public class MapperBuilder {
     }
 
     public MapperBuilder setSkipNull(final boolean skipNull) {
-        this.skipNull = skipNull;
+        builderConfig.setSkipNull(skipNull);
         return this;
     }
 
     public MapperBuilder setSkipEmptyArray(final boolean skipEmptyArray) {
-        this.skipEmptyArray = skipEmptyArray;
+        builderConfig.setSkipEmptyArray(skipEmptyArray);
         return this;
     }
 
     public MapperBuilder setTreatByteArrayAsBase64(final boolean treatByteArrayAsBase64) {
-        this.treatByteArrayAsBase64 = treatByteArrayAsBase64;
+        builderConfig.setTreatByteArrayAsBase64(treatByteArrayAsBase64);
         return this;
     }
 
     public MapperBuilder setTreatByteArrayAsBase64URL(final boolean treatByteArrayAsBase64URL) {
-        this.treatByteArrayAsBase64URL = treatByteArrayAsBase64URL;
+        builderConfig.setTreatByteArrayAsBase64URL(treatByteArrayAsBase64URL);
         return this;
     }
 
@@ -322,12 +314,12 @@ public class MapperBuilder {
     }
 
     public MapperBuilder setEncoding(final String encoding) {
-        this.encoding = Charset.forName(encoding);
+        builderConfig.setEncoding(Charset.forName(encoding));
         return this;
     }
 
     public MapperBuilder setReadAttributeBeforeWrite(final boolean readAttributeBeforeWrite) {
-        this.readAttributeBeforeWrite = readAttributeBeforeWrite;
+        builderConfig.setReadAttributeBeforeWrite(readAttributeBeforeWrite);
         return this;
     }
 }

http://git-wip-us.apache.org/repos/asf/incubator-johnzon/blob/e41b829b/johnzon-mapper/src/main/java/org/apache/johnzon/mapper/MapperConfig.java
----------------------------------------------------------------------
diff --git a/johnzon-mapper/src/main/java/org/apache/johnzon/mapper/MapperConfig.java b/johnzon-mapper/src/main/java/org/apache/johnzon/mapper/MapperConfig.java
new file mode 100644
index 0000000..5c51e12
--- /dev/null
+++ b/johnzon-mapper/src/main/java/org/apache/johnzon/mapper/MapperConfig.java
@@ -0,0 +1,131 @@
+/*
+ * 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.nio.charset.Charset;
+
+import org.apache.johnzon.mapper.access.AccessMode;
+
+/**
+ * Contains internal configuration for all the mapper stuff
+ */
+class MapperConfig implements Cloneable {
+    private boolean close = false;
+    private boolean skipNull = true;
+    private boolean skipEmptyArray = false;
+    private boolean supportsComments = false;
+    private boolean treatByteArrayAsBase64;
+    private boolean treatByteArrayAsBase64URL;
+    private boolean readAttributeBeforeWrite;
+    private boolean prettyPrint;
+    private AccessMode accessMode;
+    private Charset encoding = Charset.forName(System.getProperty("johnzon.mapper.encoding", "UTF-8"));
+
+    MapperConfig() {
+    }
+
+    void setClose(boolean close) {
+        this.close = close;
+    }
+
+    public boolean isClose() {
+        return close;
+    }
+
+    public boolean isSkipNull() {
+        return skipNull;
+    }
+
+    void setSkipNull(boolean skipNull) {
+        this.skipNull = skipNull;
+    }
+
+    public boolean isSkipEmptyArray() {
+        return skipEmptyArray;
+    }
+
+    void setSkipEmptyArray(boolean skipEmptyArray) {
+        this.skipEmptyArray = skipEmptyArray;
+    }
+
+    public boolean isSupportsComments() {
+        return supportsComments;
+    }
+
+    void setSupportsComments(boolean supportsComments) {
+        this.supportsComments = supportsComments;
+    }
+
+    public boolean isTreatByteArrayAsBase64() {
+        return treatByteArrayAsBase64;
+    }
+
+    void setTreatByteArrayAsBase64(boolean treatByteArrayAsBase64) {
+        this.treatByteArrayAsBase64 = treatByteArrayAsBase64;
+    }
+
+    public boolean isTreatByteArrayAsBase64URL() {
+        return treatByteArrayAsBase64URL;
+    }
+
+    void setTreatByteArrayAsBase64URL(boolean treatByteArrayAsBase64URL) {
+        this.treatByteArrayAsBase64URL = treatByteArrayAsBase64URL;
+    }
+
+    public boolean isReadAttributeBeforeWrite() {
+        return readAttributeBeforeWrite;
+    }
+
+    void setReadAttributeBeforeWrite(boolean readAttributeBeforeWrite) {
+        this.readAttributeBeforeWrite = readAttributeBeforeWrite;
+    }
+
+    public boolean isPrettyPrint() {
+        return prettyPrint;
+    }
+
+    void setPrettyPrint(boolean prettyPrint) {
+        this.prettyPrint = prettyPrint;
+    }
+
+    public AccessMode getAccessMode() {
+        return accessMode;
+    }
+
+    void setAccessMode(AccessMode accessMode) {
+        this.accessMode = accessMode;
+    }
+
+    public Charset getEncoding() {
+        return encoding;
+    }
+
+    void setEncoding(Charset encoding) {
+        this.encoding = encoding;
+    }
+
+    @Override
+    public MapperConfig clone() {
+        try {
+            return (MapperConfig) super.clone();
+        } catch (CloneNotSupportedException e) {
+            return null;
+        }
+    }
+}

http://git-wip-us.apache.org/repos/asf/incubator-johnzon/blob/e41b829b/johnzon-mapper/src/main/java/org/apache/johnzon/mapper/MappingGenerator.java
----------------------------------------------------------------------
diff --git a/johnzon-mapper/src/main/java/org/apache/johnzon/mapper/MappingGenerator.java b/johnzon-mapper/src/main/java/org/apache/johnzon/mapper/MappingGenerator.java
new file mode 100644
index 0000000..b3719a2
--- /dev/null
+++ b/johnzon-mapper/src/main/java/org/apache/johnzon/mapper/MappingGenerator.java
@@ -0,0 +1,61 @@
+/*
+ * 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 javax.json.stream.JsonGenerator;
+
+/**
+ * Handles writing Json for Objects.
+ * Internally it uses a {@link JsonGenerator} to write JSON
+ *
+ * To write JSON-P structure elements you can use the {@link #getJsonGenerator()} method.
+ *
+ */
+public interface MappingGenerator {
+
+    /**
+     * @return the {@link JsonGenerator} used internally to write the JSON output.
+     */
+    JsonGenerator getJsonGenerator();
+
+    /**
+     * Write the given Object o into the current JSON layer.
+     * This will <em>not</em> open a new json layer ('{', '}')
+     * but really just write the attributes of o to the currently opened layer.
+     *
+     * Consider you have a class
+     * <pre>
+     *     public class Customer {
+     *         private String firstName;
+     *         private String lastName;
+     *         private Address address;
+     *         ...
+     *     }
+     * </pre>
+     * then the resulting JSON String will e.g. look like
+     * <pre>
+     *     "firstName":"Karl", "lastName":"SomeName", "address":{"street":"mystreet"}
+     * </pre>
+     * @param o the object to write
+     * @return itself, for easier chaining of commands
+     */
+    MappingGenerator writeObject(Object o);
+
+
+}

http://git-wip-us.apache.org/repos/asf/incubator-johnzon/blob/e41b829b/johnzon-mapper/src/main/java/org/apache/johnzon/mapper/MappingParser.java
----------------------------------------------------------------------
diff --git a/johnzon-mapper/src/main/java/org/apache/johnzon/mapper/MappingParser.java b/johnzon-mapper/src/main/java/org/apache/johnzon/mapper/MappingParser.java
new file mode 100644
index 0000000..615dfaf
--- /dev/null
+++ b/johnzon-mapper/src/main/java/org/apache/johnzon/mapper/MappingParser.java
@@ -0,0 +1,49 @@
+/*
+ * 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.reflect.Type;
+
+import javax.json.JsonReader;
+import javax.json.JsonValue;
+import javax.json.stream.JsonParser;
+
+/**
+ * Handles reading Json for Objects.
+ * Internally it uses a {@link JsonParser} to write JSON
+ *
+ * To write JSON-P structure elements you can use the {@link #getJsonParser()} ()} method.
+ *
+ */
+public interface MappingParser {
+
+    /**
+     * @return the {@link JsonParser} used internally to read the JSON input.
+     */
+    JsonParser getJsonParser();
+
+    /**
+     * @return the {@link JsonReader} to read in full {@link javax.json.JsonValue}s from the {@link #getJsonParser()}
+     */
+    JsonReader getJsonReader();
+
+    <T> T readObject(Type targetType);
+
+    <T> T readObject(JsonValue jsonValue, Type targetType);
+}

http://git-wip-us.apache.org/repos/asf/incubator-johnzon/blob/e41b829b/johnzon-mapper/src/main/java/org/apache/johnzon/mapper/Mappings.java
----------------------------------------------------------------------
diff --git a/johnzon-mapper/src/main/java/org/apache/johnzon/mapper/Mappings.java b/johnzon-mapper/src/main/java/org/apache/johnzon/mapper/Mappings.java
new file mode 100644
index 0000000..da210fc
--- /dev/null
+++ b/johnzon-mapper/src/main/java/org/apache/johnzon/mapper/Mappings.java
@@ -0,0 +1,765 @@
+/*
+ * 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 org.apache.johnzon.mapper.access.AccessMode;
+import org.apache.johnzon.mapper.converter.DateWithCopyConverter;
+import org.apache.johnzon.mapper.converter.EnumConverter;
+import org.apache.johnzon.mapper.internal.AdapterKey;
+import org.apache.johnzon.mapper.internal.ConverterAdapter;
+import org.apache.johnzon.mapper.reflection.JohnzonParameterizedType;
+
+import java.lang.annotation.Annotation;
+import java.lang.reflect.Array;
+import java.lang.reflect.ParameterizedType;
+import java.lang.reflect.Type;
+import java.math.BigDecimal;
+import java.math.BigInteger;
+import java.util.Collection;
+import java.util.Comparator;
+import java.util.Date;
+import java.util.Deque;
+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;
+import java.util.Set;
+import java.util.SortedSet;
+import java.util.TreeMap;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentMap;
+
+import static java.util.Arrays.asList;
+import static org.apache.johnzon.mapper.reflection.Converters.matches;
+
+public class Mappings {
+    public static class ClassMapping {
+        final Class<?> clazz;
+        final AccessMode.Factory factory;
+        final Map<String, Getter> getters;
+        final Map<String, Setter> setters;
+
+
+        protected ClassMapping(final Class<?> clazz, final AccessMode.Factory factory,
+                               final Map<String, Getter> getters, final Map<String, Setter> setters) {
+            this.clazz = clazz;
+            this.factory = factory;
+            this.getters = getters;
+            this.setters = setters;
+        }
+    }
+
+    public static class CollectionMapping {
+        final Class<?> raw;
+        final Type arg;
+        final boolean primitive;
+
+        public CollectionMapping(final boolean primitive, final Class<?> collectionType, final Type fieldArgType) {
+            this.raw = collectionType;
+            this.arg = fieldArgType;
+            this.primitive = primitive;
+        }
+    }
+
+    public static class Getter {
+        final AccessMode.Reader reader;
+        final int version;
+        final Adapter converter;
+        final Adapter itemConverter;
+        final boolean primitive;
+        final boolean array;
+        final boolean map;
+        final boolean collection;
+
+        public Getter(final AccessMode.Reader reader,
+                      final boolean primitive, final boolean array,
+                      final boolean collection, final boolean map,
+                      final Adapter<?, ?> converter,
+                      final int version) {
+            this.reader = reader;
+            this.version = version;
+            this.array = array;
+            this.collection = collection;
+            this.primitive = primitive;
+            if (converter != null && matches(reader.getType(), converter)) {
+                this.converter = converter;
+                this.itemConverter = null;
+            } else if (converter != null) {
+                this.converter = null;
+                this.itemConverter = converter;
+            } else {
+                this.converter = null;
+                this.itemConverter = null;
+            }
+            this.map = map && this.converter == null;
+        }
+
+        @Override
+        public String toString() {
+            return "Getter{" +
+                "reader=" + reader +
+                ", version=" + version +
+                ", converter=" + converter +
+                ", itemConverter=" + itemConverter +
+                ", primitive=" + primitive +
+                ", array=" + array +
+                ", map=" + map +
+                ", collection=" + collection +
+                '}';
+        }
+    }
+
+    public static class Setter {
+        public final AccessMode.Writer writer;
+        public final int version;
+        public final Type paramType;
+        public final Adapter<?, ?> converter;
+        public final Adapter<?, ?> itemConverter;
+        public final boolean primitive;
+        public final boolean array;
+
+        public Setter(final AccessMode.Writer writer, final boolean primitive, final boolean array,
+                      final Type paramType, final Adapter<?, ?> converter,
+                      final int version) {
+            this.writer = writer;
+            this.paramType = paramType;
+            this.version = version;
+            this.primitive = primitive;
+            this.array = array;
+            if (converter != null && matches(writer.getType(), converter)) {
+                this.converter = converter;
+                this.itemConverter = null;
+            } else if (converter != null) {
+                this.converter = null;
+                this.itemConverter = converter;
+            } else {
+                this.converter = null;
+                this.itemConverter = null;
+            }
+        }
+
+        @Override
+        public String toString() {
+            return "Setter{" +
+                "writer=" + writer +
+                ", version=" + version +
+                ", paramType=" + paramType +
+                ", converter=" + converter +
+                ", itemConverter=" + itemConverter +
+                ", primitive=" + primitive +
+                ", 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;
+    protected final ConcurrentMap<AdapterKey, Adapter<?, ?>> adapters;
+    private final AccessMode accessMode;
+    private final int version;
+
+    public Mappings(final Comparator<String> attributeOrder, final AccessMode accessMode,
+                    final int version, final ConcurrentMap<AdapterKey, Adapter<?, ?>> adapters) {
+        this.fieldOrdering = attributeOrder;
+        this.accessMode = accessMode;
+        this.version = version;
+        this.adapters = adapters;
+    }
+
+    public <T> CollectionMapping findCollectionMapping(final ParameterizedType genericType) {
+        CollectionMapping collectionMapping = collections.get(genericType);
+        if (collectionMapping == null) {
+            collectionMapping = createCollectionMapping(genericType);
+            if (collectionMapping == null) {
+                return null;
+            }
+            final CollectionMapping existing = collections.putIfAbsent(genericType, collectionMapping);
+            if (existing != null) {
+                collectionMapping = existing;
+            }
+        }
+        return collectionMapping;
+    }
+
+    private <T> CollectionMapping createCollectionMapping(final ParameterizedType aType) {
+        final Type[] fieldArgTypes = aType.getActualTypeArguments();
+        final Type raw = aType.getRawType();
+        if (fieldArgTypes.length == 1 && Class.class.isInstance(raw)) {
+            final Class<?> r = Class.class.cast(raw);
+            final Class<?> collectionType;
+            if (List.class.isAssignableFrom(r)) {
+                collectionType = List.class;
+            } else if (SortedSet.class.isAssignableFrom(r)) {
+                collectionType = SortedSet.class;
+            } else if (Set.class.isAssignableFrom(r)) {
+                collectionType = Set.class;
+            } else if (Deque.class.isAssignableFrom(r)) {
+                collectionType = Deque.class;
+            } else if (Queue.class.isAssignableFrom(r)) {
+                collectionType = Queue.class;
+            } else if (Collection.class.isAssignableFrom(r)) {
+                collectionType = Collection.class;
+            } else {
+                return null;
+            }
+
+            final CollectionMapping mapping = new CollectionMapping(isPrimitive(fieldArgTypes[0]), collectionType, fieldArgTypes[0]);
+            collections.putIfAbsent(aType, mapping);
+            return mapping;
+        }
+        return null;
+    }
+
+    // has JSon API a method for this type
+    public static boolean isPrimitive(final Type type) {
+        if (type == String.class) {
+            return true;
+        } else if (type == char.class || type == Character.class) {
+            return true;
+        } else if (type == long.class || type == Long.class) {
+            return true;
+        } else if (type == int.class || type == Integer.class
+            || type == byte.class || type == Byte.class
+            || type == short.class || type == Short.class) {
+            return true;
+        } else if (type == double.class || type == Double.class
+            || type == float.class || type == Float.class) {
+            return true;
+        } else if (type == boolean.class || type == Boolean.class) {
+            return true;
+        } else if (type == BigDecimal.class) {
+            return true;
+        } else if (type == BigInteger.class) {
+            return true;
+        }
+        return false;
+    }
+
+    public ClassMapping getClassMapping(final Type clazz) {
+        return classes.get(clazz);
+    }
+
+    public ClassMapping findOrCreateClassMapping(final Type clazz) {
+        ClassMapping classMapping = classes.get(clazz);
+        if (classMapping == null) {
+            if (!Class.class.isInstance(clazz) || Map.class.isAssignableFrom(Class.class.cast(clazz))) {
+                return null;
+            }
+
+            classMapping = createClassMapping(Class.class.cast(clazz));
+            final ClassMapping existing = classes.putIfAbsent(clazz, classMapping);
+            if (existing != null) {
+                classMapping = existing;
+            }
+        }
+        return classMapping;
+    }
+
+    protected ClassMapping createClassMapping(final Class<?> inClazz) {
+        boolean copyDate = false;
+        for (final Class<?> itf : inClazz.getInterfaces()) {
+            if ("org.apache.openjpa.enhance.PersistenceCapable".equals(itf.getName())) {
+                copyDate = true;
+                break;
+            }
+        }
+        final Class<?> clazz = findModelClass(inClazz);
+
+        Comparator<String> fieldComparator = accessMode.fieldComparator(inClazz);
+        fieldComparator = fieldComparator == null ? fieldOrdering : fieldComparator;
+
+        final Map<String, Getter> getters = fieldComparator == null ? newOrderedMap(Getter.class) : new TreeMap<String, Getter>(fieldComparator);
+        final Map<String, Setter> setters = fieldComparator == null ? newOrderedMap(Setter.class) : new TreeMap<String, Setter>(fieldComparator);
+
+        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, copyDate);
+                }
+            }
+
+            final JohnzonVirtualObject virtualObject = clazz.getAnnotation(JohnzonVirtualObject.class);
+            if (virtualObject != null) {
+                handleVirtualObject(virtualFields, virtualObject, getters, setters, readers, writers, copyDate);
+            }
+        }
+
+        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(), copyDate);
+        }
+
+        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(), copyDate);
+        }
+        return new ClassMapping(clazz, accessMode.findFactory(clazz), getters, setters);
+    }
+
+    protected Class<?> findModelClass(final Class<?> inClazz) {
+        Class<?> clazz = inClazz;
+        // unproxy to get a clean model
+        while (clazz != null && clazz != Object.class
+            && (clazz.getName().contains("$$") || clazz.getName().contains("$proxy")
+            || clazz.getName().startsWith("org.apache.openjpa.enhance.") /* subclassing mode, not the default */)) {
+            clazz = clazz.getSuperclass();
+        }
+        if (clazz == null || clazz == Object.class) { // shouldn't occur but a NPE protection
+            clazz = inClazz;
+        }
+        return clazz;
+    }
+
+    private <T> Map<String, T> newOrderedMap(final Class<T> value) {
+        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 boolean copyDate) {
+        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(copyDate, 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 boolean copyDate) {
+        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(copyDate, 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 boolean copyDate) {
+        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(Getter.class);
+        final Map<String, Setter> objectSetters = newOrderedMap(Setter.class);
+
+        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, copyDate);
+                }
+            }
+            if (f.write()) {
+                final AccessMode.Writer writer = writers.get(name);
+                if (writer != null) {
+                    addSetterIfNeeded(objectSetters, name, writer, copyDate);
+                }
+            }
+        }
+
+        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 Adapter findConverter(final boolean copyDate, final AccessMode.DecoratedType decoratedType) {
+        Adapter converter = decoratedType.findConverter();
+        if (converter != null) {
+            return converter;
+        }
+
+        final JohnzonConverter annotation = decoratedType.getAnnotation(JohnzonConverter.class);
+
+        Type typeToTest = decoratedType.getType();
+        if (annotation != null) {
+            try {
+                converter = new ConverterAdapter(annotation.value().newInstance());
+            } catch (final Exception e) {
+                throw new IllegalArgumentException(e);
+            }
+        } else if (ParameterizedType.class.isInstance(decoratedType.getType())) {
+            final ParameterizedType type = ParameterizedType.class.cast(decoratedType.getType());
+            final Type rawType = type.getRawType();
+            if (Class.class.isInstance(rawType)
+                && Collection.class.isAssignableFrom(Class.class.cast(rawType))
+                && type.getActualTypeArguments().length >= 1) {
+                typeToTest = type.getActualTypeArguments()[0];
+            } // TODO: map
+        }
+        if (converter == null && Class.class.isInstance(typeToTest)) {
+            final Class type = Class.class.cast(typeToTest);
+            if (Date.class.isAssignableFrom(type) && copyDate) {
+                converter = new DateWithCopyConverter(Adapter.class.cast(adapters.get(new AdapterKey(Date.class, String.class))));
+            } else if (type.isEnum()) {
+                final AdapterKey key = new AdapterKey(String.class, type);
+                converter = adapters.get(key); // first ensure user didnt override it
+                if (converter == null) {
+                    converter = new ConverterAdapter(new EnumConverter(type));
+                    adapters.put(key, converter);
+                }
+            } else {
+                for (final Map.Entry<AdapterKey, Adapter<?, ?>> adapterEntry : adapters.entrySet()) {
+                    if (adapterEntry.getKey().getFrom() == adapterEntry.getKey().getTo()) { // String -> String
+                        continue;
+                    }
+                    if (adapterEntry.getKey().getFrom() == type && !(
+                            // ignore internal converters to let primitives be correctly handled
+                            ConverterAdapter.class.isInstance(adapterEntry.getValue()) &&
+                            ConverterAdapter.class.cast(adapterEntry.getValue()).getConverter().getClass().getName().startsWith("org.apache.johnzon.mapper."))) {
+
+                        if (converter != null) {
+                            throw new IllegalArgumentException("Ambiguous adapter for " + decoratedType);
+                        }
+                        converter = adapterEntry.getValue();
+                    }
+                }
+            }
+        }
+        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.from(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");
+        }
+
+        @Override
+        public <T extends Annotation> T getClassOrPackageAnnotation(final Class<T> clazz) {
+            return null;
+        }
+
+        @Override
+        public Adapter<?, ?> findConverter() {
+            return null;
+        }
+
+        @Override
+        public boolean isNillable() {
+            return false;
+        }
+    }
+
+    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");
+        }
+
+        @Override
+        public <T extends Annotation> T getClassOrPackageAnnotation(final Class<T> clazz) {
+            return null;
+        }
+
+        @Override
+        public Adapter<?, ?> findConverter() {
+            return null;
+        }
+
+        @Override
+        public boolean isNillable() {
+            return false;
+        }
+    }
+
+    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");
+        }
+
+        @Override
+        public <T extends Annotation> T getClassOrPackageAnnotation(final Class<T> clazz) {
+            return null;
+        }
+
+        @Override
+        public Adapter<?, ?> findConverter() {
+            for (final AccessMode.Reader r : delegates) {
+                final Adapter<?, ?> converter = r.findConverter();
+                if (converter != null) {
+                    return converter;
+                }
+            }
+            return null;
+        }
+
+        @Override
+        public boolean isNillable() {
+            for (final AccessMode.Reader r : delegates) {
+                if (r.isNillable()) {
+                    return true;
+                }
+            }
+            return false;
+        }
+    }
+
+    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");
+        }
+
+        @Override
+        public <T extends Annotation> T getClassOrPackageAnnotation(final Class<T> clazz) {
+            return null;
+        }
+
+        @Override
+        public Adapter<?, ?> findConverter() {
+            for (final AccessMode.Writer r : delegates) {
+                final Adapter<?, ?> converter = r.findConverter();
+                if (converter != null) {
+                    return converter;
+                }
+            }
+            return null;
+        }
+
+        @Override
+        public boolean isNillable() {
+            for (final AccessMode.Writer r : delegates) {
+                if (r.isNillable()) {
+                    return true;
+                }
+            }
+            return false;
+        }
+    }
+}

http://git-wip-us.apache.org/repos/asf/incubator-johnzon/blob/e41b829b/johnzon-mapper/src/main/java/org/apache/johnzon/mapper/ObjectConverter.java
----------------------------------------------------------------------
diff --git a/johnzon-mapper/src/main/java/org/apache/johnzon/mapper/ObjectConverter.java b/johnzon-mapper/src/main/java/org/apache/johnzon/mapper/ObjectConverter.java
index e831e80..7f5d02d 100644
--- a/johnzon-mapper/src/main/java/org/apache/johnzon/mapper/ObjectConverter.java
+++ b/johnzon-mapper/src/main/java/org/apache/johnzon/mapper/ObjectConverter.java
@@ -20,9 +20,6 @@ package org.apache.johnzon.mapper;
 
 import java.lang.reflect.Type;
 
-import javax.json.stream.JsonGenerator;
-import javax.json.stream.JsonParser;
-
 /**
  * Convert a given Java Type a nested JSON representation.
  * And the other way around.
@@ -33,7 +30,7 @@ import javax.json.stream.JsonParser;
  * @param <T>
  */
 public interface ObjectConverter<T> {
-    void writeJson(T instance, JsonbGenerator jsonbGenerator);
+    void writeJson(T instance, MappingGenerator jsonbGenerator);
 
-    T fromJson(JsonbParser jsonbParser, Type targetType);
+    T fromJson(MappingParser jsonbParser, Type targetType);
 }