Repository: incubator-johnzon
Updated Branches:
  refs/heads/master 0020886be -> ba4235ca4


class support for serializer/deserializer, partial support for adapters (only 
write side cause read one is quite challenging) + enforceQuoteString option in 
case we encounter a jsonb/need conflict


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

Branch: refs/heads/master
Commit: ba4235ca4cf4c6588dc7463310c0fb47f54aa108
Parents: 0020886
Author: Romain Manni-Bucau <[email protected]>
Authored: Fri Jun 3 13:15:24 2016 +0200
Committer: Romain Manni-Bucau <[email protected]>
Committed: Fri Jun 3 13:15:24 2016 +0200

----------------------------------------------------------------------
 .../jaxrs/ConfigurableJohnzonProvider.java      |   4 +
 .../WildcardConfigurableJohnzonProvider.java    |   4 +
 .../apache/johnzon/jsonb/JohnzonBuilder.java    |   3 +
 .../apache/johnzon/jsonb/JsonbAccessMode.java   | 249 +++++++++++++------
 .../johnzon/jsonb/ClassConverterTest.java       | 111 +++++++++
 .../apache/johnzon/jsonb/JsonbWriteTest.java    |   5 +-
 .../java/org/apache/johnzon/mapper/Mapper.java  |   2 +-
 .../apache/johnzon/mapper/MapperBuilder.java    |  11 +-
 .../org/apache/johnzon/mapper/MapperConfig.java |   9 +-
 .../johnzon/mapper/MappingGeneratorImpl.java    |  12 +-
 .../johnzon/mapper/MappingParserImpl.java       |  11 +-
 .../org/apache/johnzon/mapper/Mappings.java     |  17 +-
 .../johnzon/mapper/access/AccessMode.java       |   8 +
 .../johnzon/mapper/access/BaseAccessMode.java   |  10 +
 .../mapper/converter/ReversedAdapter.java       |  39 +++
 .../apache/johnzon/mapper/MapperConfigTest.java |   3 +-
 .../org/apache/johnzon/mapper/MapperTest.java   |   3 +-
 17 files changed, 413 insertions(+), 88 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/incubator-johnzon/blob/ba4235ca/johnzon-jaxrs/src/main/java/org/apache/johnzon/jaxrs/ConfigurableJohnzonProvider.java
----------------------------------------------------------------------
diff --git 
a/johnzon-jaxrs/src/main/java/org/apache/johnzon/jaxrs/ConfigurableJohnzonProvider.java
 
b/johnzon-jaxrs/src/main/java/org/apache/johnzon/jaxrs/ConfigurableJohnzonProvider.java
index ce783ab..7bdb597 100644
--- 
a/johnzon-jaxrs/src/main/java/org/apache/johnzon/jaxrs/ConfigurableJohnzonProvider.java
+++ 
b/johnzon-jaxrs/src/main/java/org/apache/johnzon/jaxrs/ConfigurableJohnzonProvider.java
@@ -199,4 +199,8 @@ public class ConfigurableJohnzonProvider<T> implements 
MessageBodyWriter<T>, Mes
     public void setReadAttributeBeforeWrite(final boolean rabw) {
         builder.setReadAttributeBeforeWrite(rabw);
     }
+
+    public void setEnforceQuoteString(final boolean val) {
+        builder.setEnforceQuoteString(val);
+    }
 }

http://git-wip-us.apache.org/repos/asf/incubator-johnzon/blob/ba4235ca/johnzon-jaxrs/src/main/java/org/apache/johnzon/jaxrs/WildcardConfigurableJohnzonProvider.java
----------------------------------------------------------------------
diff --git 
a/johnzon-jaxrs/src/main/java/org/apache/johnzon/jaxrs/WildcardConfigurableJohnzonProvider.java
 
b/johnzon-jaxrs/src/main/java/org/apache/johnzon/jaxrs/WildcardConfigurableJohnzonProvider.java
index f404167..aa44fec 100644
--- 
a/johnzon-jaxrs/src/main/java/org/apache/johnzon/jaxrs/WildcardConfigurableJohnzonProvider.java
+++ 
b/johnzon-jaxrs/src/main/java/org/apache/johnzon/jaxrs/WildcardConfigurableJohnzonProvider.java
@@ -207,4 +207,8 @@ public class WildcardConfigurableJohnzonProvider<T> 
implements MessageBodyWriter
     public void setEncoding(final String encoding) {
         builder.setEncoding(encoding);
     }
+
+    public void setEnforceQuoteString(final boolean val) {
+        builder.setEnforceQuoteString(val);
+    }
 }

http://git-wip-us.apache.org/repos/asf/incubator-johnzon/blob/ba4235ca/johnzon-jsonb/src/main/java/org/apache/johnzon/jsonb/JohnzonBuilder.java
----------------------------------------------------------------------
diff --git 
a/johnzon-jsonb/src/main/java/org/apache/johnzon/jsonb/JohnzonBuilder.java 
b/johnzon-jsonb/src/main/java/org/apache/johnzon/jsonb/JohnzonBuilder.java
index 390c2bc..ea2a937 100644
--- a/johnzon-jsonb/src/main/java/org/apache/johnzon/jsonb/JohnzonBuilder.java
+++ b/johnzon-jsonb/src/main/java/org/apache/johnzon/jsonb/JohnzonBuilder.java
@@ -189,6 +189,9 @@ public class JohnzonBuilder implements JsonbBuilder {
                 });
 
         config.getProperty("johnzon.attributeOrder").ifPresent(comp -> 
builder.setAttributeOrder(Comparator.class.cast(comp)));
+        config.getProperty("johnzon.enforceQuoteString")
+                .map(v -> !Boolean.class.isInstance(v) ? 
Boolean.parseBoolean(v.toString()) : Boolean.class.cast(v))
+                .ifPresent(builder::setEnforceQuoteString);
 
         final Map<AdapterKey, Adapter<?, ?>> defaultConverters = 
createJava8Converters(builder);
 

http://git-wip-us.apache.org/repos/asf/incubator-johnzon/blob/ba4235ca/johnzon-jsonb/src/main/java/org/apache/johnzon/jsonb/JsonbAccessMode.java
----------------------------------------------------------------------
diff --git 
a/johnzon-jsonb/src/main/java/org/apache/johnzon/jsonb/JsonbAccessMode.java 
b/johnzon-jsonb/src/main/java/org/apache/johnzon/jsonb/JsonbAccessMode.java
index f660a5e..5f30a62 100644
--- a/johnzon-jsonb/src/main/java/org/apache/johnzon/jsonb/JsonbAccessMode.java
+++ b/johnzon-jsonb/src/main/java/org/apache/johnzon/jsonb/JsonbAccessMode.java
@@ -30,10 +30,12 @@ import 
org.apache.johnzon.jsonb.serializer.JohnzonSerializationContext;
 import org.apache.johnzon.jsonb.spi.JohnzonAdapterFactory;
 import org.apache.johnzon.mapper.Adapter;
 import org.apache.johnzon.mapper.ObjectConverter;
+import org.apache.johnzon.mapper.TypeAwareAdapter;
 import org.apache.johnzon.mapper.access.AccessMode;
 import org.apache.johnzon.mapper.access.FieldAccessMode;
 import org.apache.johnzon.mapper.access.FieldAndMethodAccessMode;
 import org.apache.johnzon.mapper.access.MethodAccessMode;
+import org.apache.johnzon.mapper.converter.ReversedAdapter;
 import org.apache.johnzon.mapper.internal.AdapterKey;
 import org.apache.johnzon.mapper.internal.ConverterAdapter;
 
@@ -80,6 +82,8 @@ import java.util.OptionalDouble;
 import java.util.OptionalInt;
 import java.util.OptionalLong;
 import java.util.TreeMap;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentMap;
 import java.util.function.BiConsumer;
 import java.util.function.Function;
 import java.util.function.Supplier;
@@ -99,6 +103,7 @@ public class JsonbAccessMode implements AccessMode, 
Closeable {
     private final JohnzonAdapterFactory factory;
     private final Collection<JohnzonAdapterFactory.Instance<?>> toRelease = 
new ArrayList<>();
     private final Supplier<JsonParserFactory> parserFactory;
+    private final ConcurrentMap<Class<?>, ParsingCacheEntry> parsingCache = 
new ConcurrentHashMap<>();
 
     public JsonbAccessMode(final PropertyNamingStrategy 
propertyNamingStrategy, final String orderValue,
                            final PropertyVisibilityStrategy 
visibilityStrategy, final boolean caseSensitive,
@@ -341,40 +346,6 @@ public class JsonbAccessMode implements AccessMode, 
Closeable {
                 finalReader = initialReader;
             }
 
-            // we are visible
-            final JsonbTypeSerializer serializer = 
initialReader.getAnnotation(JsonbTypeSerializer.class);
-            final JsonbProperty property = 
initialReader.getAnnotation(JsonbProperty.class);
-            final JsonbNillable nillable = 
initialReader.getClassOrPackageAnnotation(JsonbNillable.class);
-            final boolean isNillable = nillable != null || (property != null 
&& property.nillable());
-            final JsonbTypeAdapter adapter = 
initialReader.getAnnotation(JsonbTypeAdapter.class);
-            final JsonbDateFormat dateFormat = 
initialReader.getAnnotation(JsonbDateFormat.class);
-            final JsonbNumberFormat numberFormat = 
initialReader.getAnnotation(JsonbNumberFormat.class);
-            validateAnnotations(initialReader, adapter, dateFormat, 
numberFormat);
-
-            final Adapter<?, ?> converter;
-            try {
-                converter = adapter == null && dateFormat == null && 
numberFormat == null ?
-                        defaultConverters.get(new 
AdapterKey(initialReader.getType(), String.class)) :
-                        toConverter(initialReader.getType(), adapter, 
dateFormat, numberFormat);
-            } catch (final InstantiationException | IllegalAccessException e) {
-                throw new IllegalArgumentException(e);
-            }
-
-            final ObjectConverter.Writer writer;
-            if (serializer != null) {
-                final Class<? extends JsonbSerializer> value = 
serializer.value();
-                final ParameterizedType pt = findPt(value, 
JsonbSerializer.class);
-                if (pt == null) {
-                    throw new IllegalArgumentException(value + " doesn't 
implement JsonbSerializer");
-                }
-                final JohnzonAdapterFactory.Instance<? extends 
JsonbSerializer> instance = newInstance(value);
-                toRelease.add(instance);
-                writer = (instance1, jsonbGenerator) ->
-                        instance.getValue().serialize(instance1, 
jsonbGenerator.getJsonGenerator(), new 
JohnzonSerializationContext(jsonbGenerator));
-            } else {
-                writer = null;
-            }
-
             // handle optionals since mapper is still only java 7
             final Type type;
             final Function<Object, Object> reader;
@@ -395,6 +366,10 @@ public class JsonbAccessMode implements AccessMode, 
Closeable {
                 reader = finalReader::read;
             }
 
+            final WriterConverters writerConverters = new 
WriterConverters(initialReader);
+            final JsonbProperty property = 
initialReader.getAnnotation(JsonbProperty.class);
+            final JsonbNillable nillable = 
initialReader.getClassOrPackageAnnotation(JsonbNillable.class);
+            final boolean isNillable = nillable != null || (property != null 
&& property.nillable());
             final String key = property == null || property.value().isEmpty() 
? naming.translateName(entry.getKey()) : property.value();
             if (result.put(key, new Reader() {
                 @Override
@@ -404,7 +379,7 @@ public class JsonbAccessMode implements AccessMode, 
Closeable {
 
                 @Override
                 public ObjectConverter.Writer<?> findObjectConverterWriter() {
-                    return writer;
+                    return writerConverters.writer;
                 }
 
                 @Override
@@ -424,7 +399,7 @@ public class JsonbAccessMode implements AccessMode, 
Closeable {
 
                 @Override
                 public Adapter<?, ?> findConverter() {
-                    return converter;
+                    return writerConverters.converter;
                 }
 
                 @Override
@@ -463,40 +438,6 @@ public class JsonbAccessMode implements AccessMode, 
Closeable {
                 finalWriter = initialWriter;
             }
 
-            // we are visible
-            final JsonbTypeDeserializer deserializer = 
initialWriter.getAnnotation(JsonbTypeDeserializer.class);
-            final JsonbProperty property = 
initialWriter.getAnnotation(JsonbProperty.class);
-            final JsonbNillable nillable = 
initialWriter.getClassOrPackageAnnotation(JsonbNillable.class);
-            final boolean isNillable = nillable != null || (property != null 
&& property.nillable());
-            final JsonbTypeAdapter adapter = 
initialWriter.getAnnotation(JsonbTypeAdapter.class);
-            final JsonbDateFormat dateFormat = 
initialWriter.getAnnotation(JsonbDateFormat.class);
-            final JsonbNumberFormat numberFormat = 
initialWriter.getAnnotation(JsonbNumberFormat.class);
-            validateAnnotations(initialWriter, adapter, dateFormat, 
numberFormat);
-
-            final Adapter<?, ?> converter;
-            try {
-                converter = adapter == null && dateFormat == null && 
numberFormat == null ?
-                        defaultConverters.get(new 
AdapterKey(initialWriter.getType(), String.class)) :
-                        toConverter(initialWriter.getType(), adapter, 
dateFormat, numberFormat);
-            } catch (final InstantiationException | IllegalAccessException e) {
-                throw new IllegalArgumentException(e);
-            }
-
-            final ObjectConverter.Reader reader;
-            if (deserializer != null) {
-                final Class<? extends JsonbDeserializer> value = 
deserializer.value();
-                final ParameterizedType pt = findPt(value, 
JsonbDeserializer.class);
-                if (pt == null) {
-                    throw new IllegalArgumentException(value + " doesn't 
implement JsonbDeserializer");
-                }
-                final JohnzonAdapterFactory.Instance<? extends 
JsonbDeserializer> instance = newInstance(value);
-                toRelease.add(instance);
-                reader = (jsonObject, targetType, parser) ->
-                        
instance.getValue().deserialize(parserFactory.get().createParser(jsonObject), 
new JohnzonDeserializationContext(parser), targetType);
-            } else {
-                reader = null;
-            }
-
             // handle optionals since mapper is still only java 7
             final Type type;
             final BiConsumer<Object, Object> writer;
@@ -517,6 +458,10 @@ public class JsonbAccessMode implements AccessMode, 
Closeable {
                 writer = finalWriter::write;
             }
 
+            final ReaderConverters converters = new 
ReaderConverters(initialWriter);
+            final JsonbProperty property = 
initialWriter.getAnnotation(JsonbProperty.class);
+            final JsonbNillable nillable = 
initialWriter.getClassOrPackageAnnotation(JsonbNillable.class);
+            final boolean isNillable = nillable != null || (property != null 
&& property.nillable());
             final String key = property == null || property.value().isEmpty() 
? naming.translateName(entry.getKey()) : property.value();
             if (result.put(key, new Writer() {
                 @Override
@@ -526,7 +471,7 @@ public class JsonbAccessMode implements AccessMode, 
Closeable {
 
                 @Override
                 public ObjectConverter.Reader<?> findObjectConverterReader() {
-                    return reader;
+                    return converters.reader;
                 }
 
                 @Override
@@ -546,7 +491,7 @@ public class JsonbAccessMode implements AccessMode, 
Closeable {
 
                 @Override
                 public Adapter<?, ?> findConverter() {
-                    return converter;
+                    return converters.converter;
                 }
 
                 @Override
@@ -561,13 +506,52 @@ public class JsonbAccessMode implements AccessMode, 
Closeable {
     }
 
     @Override
-    public Reader findReader(final Class<?> clazz) {
-        throw new UnsupportedOperationException("TODO");
+    public ObjectConverter.Reader<?> findReader(final Class<?> clazz) {
+        return getClassEntry(clazz).readers.reader;
+    }
+
+    @Override
+    public ObjectConverter.Writer<?> findWriter(final Class<?> clazz) {
+        return getClassEntry(clazz).writers.writer;
+    }
+
+    @Override
+    public Adapter<?, ?> findAdapter(final Class<?> clazz) { // TODO: find a 
way to not parse twice
+        final Adapter<?, ?> converter = getClassEntry(clazz).readers.converter;
+        if (converter != null && isReversedAdapter(clazz, 
converter.getClass(), converter)) {
+            return new ReversedAdapter<>(converter);
+        }
+        return converter;
     }
 
     @Override
-    public Writer findWriter(final Class<?> clazz) {
-        throw new UnsupportedOperationException("TODO");
+    public void afterParsed(final Class<?> clazz) {
+        parsingCache.remove(clazz);
+    }
+
+    private boolean isReversedAdapter(final Class<?> payloadType, final 
Class<?> aClass, final Adapter<?, ?> instance) {
+        if (TypeAwareAdapter.class.isInstance(instance)) {
+            return 
!payloadType.isAssignableFrom(Class.class.cast(TypeAwareAdapter.class.cast(instance).getTo()))
+                    && 
payloadType.isAssignableFrom(Class.class.cast(TypeAwareAdapter.class.cast(instance).getFrom()));
+        }
+        final Type[] genericInterfaces = aClass.getGenericInterfaces();
+        return 
Stream.of(genericInterfaces).filter(ParameterizedType.class::isInstance)
+                .filter(i -> 
Adapter.class.isAssignableFrom(Class.class.cast(ParameterizedType.class.cast(i).getRawType())))
+                .findFirst()
+                .map(pt -> 
payloadType.isAssignableFrom(Class.class.cast(ParameterizedType.class.cast(pt).getActualTypeArguments()[0])))
+                .orElseGet(() -> {
+                    final Class<?> superclass = aClass.getSuperclass();
+                    return superclass != Object.class && 
isReversedAdapter(payloadType, superclass, instance);
+                });
+    }
+
+    private ParsingCacheEntry getClassEntry(final Class<?> clazz) {
+        ParsingCacheEntry cache = parsingCache.get(clazz);
+        if (cache == null) {
+            cache = new ParsingCacheEntry(new ClassDecoratedType(clazz));
+            parsingCache.putIfAbsent(clazz, cache);
+        }
+        return cache;
     }
 
     private boolean isOptional(final DecoratedType value) {
@@ -636,4 +620,117 @@ public class JsonbAccessMode implements AccessMode, 
Closeable {
         toRelease.forEach(JohnzonAdapterFactory.Instance::release);
         toRelease.clear();
     }
+
+    private class ReaderConverters {
+        private Adapter<?, ?> converter;
+        private ObjectConverter.Reader reader;
+
+        ReaderConverters(final DecoratedType annotationHolder) {
+            final JsonbTypeDeserializer deserializer = 
annotationHolder.getAnnotation(JsonbTypeDeserializer.class);
+            final JsonbTypeAdapter adapter = 
annotationHolder.getAnnotation(JsonbTypeAdapter.class);
+            final JsonbDateFormat dateFormat = 
annotationHolder.getAnnotation(JsonbDateFormat.class);
+            final JsonbNumberFormat numberFormat = 
annotationHolder.getAnnotation(JsonbNumberFormat.class);
+            validateAnnotations(annotationHolder, adapter, dateFormat, 
numberFormat);
+
+            try {
+                converter = adapter == null && dateFormat == null && 
numberFormat == null ?
+                        defaultConverters.get(new 
AdapterKey(annotationHolder.getType(), String.class)) :
+                        toConverter(annotationHolder.getType(), adapter, 
dateFormat, numberFormat);
+            } catch (final InstantiationException | IllegalAccessException e) {
+                throw new IllegalArgumentException(e);
+            }
+
+            if (deserializer != null) {
+                final Class<? extends JsonbDeserializer> value = 
deserializer.value();
+                final ParameterizedType pt = findPt(value, 
JsonbDeserializer.class);
+                if (pt == null) {
+                    throw new IllegalArgumentException(value + " doesn't 
implement JsonbDeserializer");
+                }
+                final JohnzonAdapterFactory.Instance<? extends 
JsonbDeserializer> instance = newInstance(value);
+                toRelease.add(instance);
+                reader = (jsonObject, targetType, parser) ->
+                        
instance.getValue().deserialize(parserFactory.get().createParser(jsonObject), 
new JohnzonDeserializationContext(parser), targetType);
+            } else {
+                reader = null;
+            }
+        }
+    }
+
+    private class WriterConverters {
+        private Adapter<?, ?> converter;
+        private ObjectConverter.Writer writer;
+
+        WriterConverters(final DecoratedType initialReader) {
+            final JsonbTypeSerializer serializer = 
initialReader.getAnnotation(JsonbTypeSerializer.class);
+            final JsonbTypeAdapter adapter = 
initialReader.getAnnotation(JsonbTypeAdapter.class);
+            final JsonbDateFormat dateFormat = 
initialReader.getAnnotation(JsonbDateFormat.class);
+            final JsonbNumberFormat numberFormat = 
initialReader.getAnnotation(JsonbNumberFormat.class);
+            validateAnnotations(initialReader, adapter, dateFormat, 
numberFormat);
+
+            try {
+                converter = adapter == null && dateFormat == null && 
numberFormat == null ?
+                        defaultConverters.get(new 
AdapterKey(initialReader.getType(), String.class)) :
+                        toConverter(initialReader.getType(), adapter, 
dateFormat, numberFormat);
+            } catch (final InstantiationException | IllegalAccessException e) {
+                throw new IllegalArgumentException(e);
+            }
+
+            if (serializer != null) {
+                final Class<? extends JsonbSerializer> value = 
serializer.value();
+                final ParameterizedType pt = findPt(value, 
JsonbSerializer.class);
+                if (pt == null) {
+                    throw new IllegalArgumentException(value + " doesn't 
implement JsonbSerializer");
+                }
+                final JohnzonAdapterFactory.Instance<? extends 
JsonbSerializer> instance = newInstance(value);
+                toRelease.add(instance);
+                writer = (instance1, jsonbGenerator) ->
+                        instance.getValue().serialize(instance1, 
jsonbGenerator.getJsonGenerator(), new 
JohnzonSerializationContext(jsonbGenerator));
+            } else {
+                writer = null;
+            }
+        }
+    }
+
+    private static class ClassDecoratedType implements DecoratedType {
+        private final Class<?> annotations;
+
+        ClassDecoratedType(final Class<?> clazz) {
+            this.annotations = clazz;
+        }
+
+        @Override
+        public Type getType() {
+            return annotations;
+        }
+
+        @Override
+        public <T extends Annotation> T getAnnotation(final Class<T> clazz) {
+            return annotations.getAnnotation(clazz);
+        }
+
+        @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 class ParsingCacheEntry {
+        private final ReaderConverters readers;
+        private final WriterConverters writers;
+
+        ParsingCacheEntry(final DecoratedType type) {
+            readers = new ReaderConverters(type);
+            writers = new WriterConverters(type);
+        }
+    }
 }

http://git-wip-us.apache.org/repos/asf/incubator-johnzon/blob/ba4235ca/johnzon-jsonb/src/test/java/org/apache/johnzon/jsonb/ClassConverterTest.java
----------------------------------------------------------------------
diff --git 
a/johnzon-jsonb/src/test/java/org/apache/johnzon/jsonb/ClassConverterTest.java 
b/johnzon-jsonb/src/test/java/org/apache/johnzon/jsonb/ClassConverterTest.java
new file mode 100644
index 0000000..63a66c6
--- /dev/null
+++ 
b/johnzon-jsonb/src/test/java/org/apache/johnzon/jsonb/ClassConverterTest.java
@@ -0,0 +1,111 @@
+/*
+ * 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.jsonb;
+
+import org.junit.Test;
+
+import javax.json.bind.Jsonb;
+import javax.json.bind.adapter.JsonbAdapter;
+import javax.json.bind.annotation.JsonbTypeAdapter;
+import javax.json.bind.annotation.JsonbTypeDeserializer;
+import javax.json.bind.annotation.JsonbTypeSerializer;
+import javax.json.bind.serializer.DeserializationContext;
+import javax.json.bind.serializer.JsonbDeserializer;
+import javax.json.bind.serializer.JsonbSerializer;
+import javax.json.bind.serializer.SerializationContext;
+import javax.json.bind.spi.JsonbProvider;
+import javax.json.stream.JsonGenerator;
+import javax.json.stream.JsonParser;
+import java.lang.reflect.Type;
+
+import static org.junit.Assert.assertEquals;
+
+public class ClassConverterTest {
+    @Test
+    public void roundTripSerDeser() {
+        final Jsonb jsonb = JsonbProvider.provider().create().build();
+        final Whole2 whole = new Whole2();
+        whole.name = "test";
+        assertEquals("{\"text\":\"test\"}", jsonb.toJson(whole));
+        assertEquals("test", jsonb.fromJson("{\"text\":\"test\"}", 
Whole2.class).name);
+    }
+
+    @Test
+    public void writeAdapters() {
+        final Jsonb jsonb = JsonbProvider.provider().create().build();
+        final Whole whole = new Whole();
+        whole.name = "test";
+        assertEquals("{\"name2\":\">test<\"}", jsonb.toJson(whole));
+
+        // not really doable properly
+        // assertEquals("test", jsonb.fromJson("{\"name2\":\">test<\"}", 
Whole.class).name);
+    }
+
+    @JsonbTypeAdapter(MyAdapter.class)
+    public static class Whole {
+        String name;
+    }
+
+    @JsonbTypeSerializer(MySerializer.class)
+    @JsonbTypeDeserializer(MyDeserializer.class)
+    public static class Whole2 {
+        String name;
+    }
+
+    public static class Switch {
+        public String name2;
+    }
+
+    public static class MyDeserializer implements JsonbDeserializer<Whole2> {
+        @Override
+        public Whole2 deserialize(final JsonParser parser, final 
DeserializationContext ctx, final Type rtType) {
+            parser.next(); // start
+            parser.next();
+            assertEquals("text", parser.getString());
+            parser.next();
+            final Whole2 whole2 = new Whole2();
+            whole2.name = parser.getString();
+            parser.next(); // end
+            return whole2;
+        }
+    }
+
+    public static class MySerializer implements JsonbSerializer<Whole2> {
+        @Override
+        public void serialize(final Whole2 obj, final JsonGenerator generator, 
final SerializationContext ctx) {
+            generator.write("text", obj.name);
+        }
+    }
+
+    public static class MyAdapter implements JsonbAdapter<Whole, Switch> {
+        @Override
+        public Whole adaptToJson(final Switch obj) throws Exception {
+            final Whole whole = new Whole();
+            whole.name = obj.name2.replace("<", "").replace(">", "");
+            return whole;
+        }
+
+        @Override
+        public Switch adaptFromJson(final Whole obj) throws Exception {
+            final Switch aSwitch = new Switch();
+            aSwitch.name2 = '>' + obj.name + '<';
+            return aSwitch;
+        }
+    }
+}

http://git-wip-us.apache.org/repos/asf/incubator-johnzon/blob/ba4235ca/johnzon-jsonb/src/test/java/org/apache/johnzon/jsonb/JsonbWriteTest.java
----------------------------------------------------------------------
diff --git 
a/johnzon-jsonb/src/test/java/org/apache/johnzon/jsonb/JsonbWriteTest.java 
b/johnzon-jsonb/src/test/java/org/apache/johnzon/jsonb/JsonbWriteTest.java
index 273c7fc..7ec37c1 100644
--- a/johnzon-jsonb/src/test/java/org/apache/johnzon/jsonb/JsonbWriteTest.java
+++ b/johnzon-jsonb/src/test/java/org/apache/johnzon/jsonb/JsonbWriteTest.java
@@ -20,6 +20,7 @@ package org.apache.johnzon.jsonb;
 
 import org.junit.Test;
 
+import javax.json.bind.Jsonb;
 import javax.json.bind.annotation.JsonbDateFormat;
 import javax.json.bind.annotation.JsonbProperty;
 import javax.json.bind.spi.JsonbProvider;
@@ -75,7 +76,9 @@ public class JsonbWriteTest {
     public void date() {
         final DateFormatting simple = new DateFormatting();
         simple.setDate(LocalDateTime.now());
-        assertEquals("{\"date\":\"" + LocalDateTime.now().getYear() + "\"}", 
JsonbProvider.provider().create().build().toJson(simple));
+        final Jsonb build = JsonbProvider.provider().create().build();
+        final int currentYear = LocalDateTime.now().getYear();
+        assertEquals("{\"date\":\"" + currentYear + "\"}", 
build.toJson(simple));
     }
 
     public static class Simple {

http://git-wip-us.apache.org/repos/asf/incubator-johnzon/blob/ba4235ca/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 3826c4a..b9ebf50 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
@@ -107,7 +107,7 @@ public class Mapper implements Closeable {
                 || object == null) {
             try {
                 final String valueOf = String.valueOf(object);
-                stream.write(String.class.isInstance(object) ? '"' + valueOf + 
'"' : valueOf);
+                stream.write(config.isEnforceQuoteString() && 
String.class.isInstance(object) && !valueOf.startsWith("\"") ? '"' + valueOf + 
'"' : valueOf);
             } catch (final IOException e) {
                 throw new MapperException(e);
             } finally {

http://git-wip-us.apache.org/repos/asf/incubator-johnzon/blob/ba4235ca/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 73c32da..9091698 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
@@ -119,6 +119,7 @@ public class MapperBuilder {
     private boolean treatByteArrayAsBase64;
     private boolean treatByteArrayAsBase64URL;
     private boolean readAttributeBeforeWrite;
+    private boolean enforceQuoteString;
     private AccessMode accessMode;
     private Charset encoding = 
Charset.forName(System.getProperty("johnzon.mapper.encoding", "UTF-8"));
     private ConcurrentMap<AdapterKey, Adapter<?, ?>> adapters = new 
ConcurrentHashMap<AdapterKey, Adapter<?, ?>>(DEFAULT_CONVERTERS);
@@ -192,7 +193,7 @@ public class MapperBuilder {
                         version, close,
                         skipNull, skipEmptyArray,
                         treatByteArrayAsBase64, treatByteArrayAsBase64URL, 
readAttributeBeforeWrite,
-                        accessMode, encoding, attributeOrder),
+                        accessMode, encoding, attributeOrder, 
enforceQuoteString),
                 closeables);
     }
 
@@ -353,4 +354,12 @@ public class MapperBuilder {
         return this;
     }
 
+    public MapperBuilder setEnforceQuoteString() {
+        return setEnforceQuoteString(true);
+    }
+
+    public MapperBuilder setEnforceQuoteString(final boolean val) {
+        this.enforceQuoteString = val;
+        return this;
+    }
 }

http://git-wip-us.apache.org/repos/asf/incubator-johnzon/blob/ba4235ca/johnzon-mapper/src/main/java/org/apache/johnzon/mapper/MapperConfig.java
----------------------------------------------------------------------
diff --git 
a/johnzon-mapper/src/main/java/org/apache/johnzon/mapper/MapperConfig.java 
b/johnzon-mapper/src/main/java/org/apache/johnzon/mapper/MapperConfig.java
index 590c238..865bd0b 100644
--- a/johnzon-mapper/src/main/java/org/apache/johnzon/mapper/MapperConfig.java
+++ b/johnzon-mapper/src/main/java/org/apache/johnzon/mapper/MapperConfig.java
@@ -62,6 +62,7 @@ class MapperConfig implements Cloneable {
     private final Map<Class<?>, ObjectConverter.Writer<?>> 
objectConverterWriters;
     private final Map<Class<?>, ObjectConverter.Reader<?>> 
objectConverterReaders;
     private final Comparator<String> attributeOrder;
+    private final boolean enforceQuoteString;
 
     private final Map<Class<?>, ObjectConverter.Writer<?>> 
objectConverterWriterCache;
     private final Map<Class<?>, ObjectConverter.Reader<?>> 
objectConverterReaderCache;
@@ -76,7 +77,8 @@ class MapperConfig implements Cloneable {
                         final boolean treatByteArrayAsBase64, final boolean 
treatByteArrayAsBase64URL,
                         final boolean readAttributeBeforeWrite,
                         final AccessMode accessMode, final Charset encoding,
-                        final Comparator<String> attributeOrder) {
+                        final Comparator<String> attributeOrder,
+                        final boolean enforceQuoteString) {
     //CHECKSTYLE:ON
         this.objectConverterWriters = objectConverterWriters;
         this.objectConverterReaders = objectConverterReaders;
@@ -91,6 +93,7 @@ class MapperConfig implements Cloneable {
         this.encoding = encoding;
         this.adapters = adapters;
         this.attributeOrder = attributeOrder;
+        this.enforceQuoteString = enforceQuoteString;
 
         this.objectConverterWriterCache = new HashMap<Class<?>, 
ObjectConverter.Writer<?>>(objectConverterWriters.size());
         this.objectConverterReaderCache = new HashMap<Class<?>, 
ObjectConverter.Reader<?>>(objectConverterReaders.size());
@@ -268,4 +271,8 @@ class MapperConfig implements Cloneable {
     public Comparator<String> getAttributeOrder() {
         return attributeOrder;
     }
+
+    public boolean isEnforceQuoteString() {
+        return enforceQuoteString;
+    }
 }

http://git-wip-us.apache.org/repos/asf/incubator-johnzon/blob/ba4235ca/johnzon-mapper/src/main/java/org/apache/johnzon/mapper/MappingGeneratorImpl.java
----------------------------------------------------------------------
diff --git 
a/johnzon-mapper/src/main/java/org/apache/johnzon/mapper/MappingGeneratorImpl.java
 
b/johnzon-mapper/src/main/java/org/apache/johnzon/mapper/MappingGeneratorImpl.java
index 97e7fb4..3ed0655 100644
--- 
a/johnzon-mapper/src/main/java/org/apache/johnzon/mapper/MappingGeneratorImpl.java
+++ 
b/johnzon-mapper/src/main/java/org/apache/johnzon/mapper/MappingGeneratorImpl.java
@@ -224,13 +224,22 @@ public class MappingGeneratorImpl implements 
MappingGenerator {
     }
 
 
-    private JsonGenerator doWriteObjectBody(final Object object) throws 
IllegalAccessException, InvocationTargetException {
+    private void doWriteObjectBody(final Object object) throws 
IllegalAccessException, InvocationTargetException {
         final Class<?> objectClass = object.getClass();
         final Mappings.ClassMapping classMapping = 
mappings.findOrCreateClassMapping(objectClass);
         if (classMapping == null) {
             throw new MapperException("No mapping for " + 
objectClass.getName());
         }
 
+        if (classMapping.writer != null) {
+            classMapping.writer.writeJson(object, this);
+            return;
+        }
+        if (classMapping.adapter != null) {
+            doWriteObjectBody(classMapping.adapter.to(object));
+            return;
+        }
+
         for (final Map.Entry<String, Mappings.Getter> getterEntry : 
classMapping.getters.entrySet()) {
             final Mappings.Getter getter = getterEntry.getValue();
             if (getter.version >= 0 && config.getVersion() >= getter.version) {
@@ -261,7 +270,6 @@ public class MappingGeneratorImpl implements 
MappingGenerator {
                     getterEntry.getKey(),
                     val, getter.objectConverter);
         }
-        return generator;
     }
 
     private void writeValue(final Class<?> type,

http://git-wip-us.apache.org/repos/asf/incubator-johnzon/blob/ba4235ca/johnzon-mapper/src/main/java/org/apache/johnzon/mapper/MappingParserImpl.java
----------------------------------------------------------------------
diff --git 
a/johnzon-mapper/src/main/java/org/apache/johnzon/mapper/MappingParserImpl.java 
b/johnzon-mapper/src/main/java/org/apache/johnzon/mapper/MappingParserImpl.java
index 8640863..7a9fcb2 100644
--- 
a/johnzon-mapper/src/main/java/org/apache/johnzon/mapper/MappingParserImpl.java
+++ 
b/johnzon-mapper/src/main/java/org/apache/johnzon/mapper/MappingParserImpl.java
@@ -98,7 +98,7 @@ public class MappingParserImpl implements MappingParser {
     public <T> T readObject(Type targetType) {
 
         try {
-            if (jsonReader instanceof JsonReaderImpl) {
+            if 
(jsonReader.getClass().getName().equals("org.apache.johnzon.core.JsonReaderImpl"))
 {
                 // later in JSON-P 1.1 we can remove this hack again
                 return readObject(((JsonReaderImpl) jsonReader).readValue(), 
targetType);
             }
@@ -265,6 +265,15 @@ public class MappingParserImpl implements MappingParser {
             throw new MapperException("Can't map " + type);
         }
 
+        if (applyObjectConverter && classMapping.reader != null) {
+            return classMapping.reader.fromJson(object, type, new 
SuppressConversionMappingParser(this, object));
+        }
+        /* doesn't work yet
+        if (classMapping.adapter != null) {
+            return classMapping.adapter.from(t);
+        }
+        */
+
         if (classMapping.factory == null) {
             throw new MapperException(classMapping.clazz + " not 
instantiable");
         }

http://git-wip-us.apache.org/repos/asf/incubator-johnzon/blob/ba4235ca/johnzon-mapper/src/main/java/org/apache/johnzon/mapper/Mappings.java
----------------------------------------------------------------------
diff --git 
a/johnzon-mapper/src/main/java/org/apache/johnzon/mapper/Mappings.java 
b/johnzon-mapper/src/main/java/org/apache/johnzon/mapper/Mappings.java
index dcdb5bf..bf5149e 100644
--- a/johnzon-mapper/src/main/java/org/apache/johnzon/mapper/Mappings.java
+++ b/johnzon-mapper/src/main/java/org/apache/johnzon/mapper/Mappings.java
@@ -57,16 +57,19 @@ public class Mappings {
         final AccessMode.Factory factory;
         final Map<String, Getter> getters;
         final Map<String, Setter> setters;
-        final ObjectConverter.Reader<?> reader;
-        final ObjectConverter.Writer<?> writer;
+        final Adapter adapter;
+        final ObjectConverter.Reader reader;
+        final ObjectConverter.Writer writer;
 
         protected ClassMapping(final Class<?> clazz, final AccessMode.Factory 
factory,
                                final Map<String, Getter> getters, final 
Map<String, Setter> setters,
+                               final Adapter<?, ?> adapter,
                                final ObjectConverter.Reader<?> reader, final 
ObjectConverter.Writer<?> writer) {
             this.clazz = clazz;
             this.factory = factory;
             this.getters = getters;
             this.setters = setters;
+            this.adapter = adapter;
             this.writer = writer;
             this.reader = reader;
         }
@@ -369,7 +372,15 @@ public class Mappings {
             }
             addSetterIfNeeded(setters, key, writer.getValue(), copyDate);
         }
-        return new ClassMapping(clazz, accessMode.findFactory(clazz), getters, 
setters, accessMode.findReader(clazz), accessMode.findWriter(clazz));
+
+        final ClassMapping mapping = new ClassMapping(
+                clazz, accessMode.findFactory(clazz), getters, setters,
+                accessMode.findAdapter(clazz),
+                accessMode.findReader(clazz), accessMode.findWriter(clazz));
+
+        accessMode.afterParsed(clazz);
+
+        return mapping;
     }
 
     protected Class<?> findModelClass(final Class<?> inClazz) {

http://git-wip-us.apache.org/repos/asf/incubator-johnzon/blob/ba4235ca/johnzon-mapper/src/main/java/org/apache/johnzon/mapper/access/AccessMode.java
----------------------------------------------------------------------
diff --git 
a/johnzon-mapper/src/main/java/org/apache/johnzon/mapper/access/AccessMode.java 
b/johnzon-mapper/src/main/java/org/apache/johnzon/mapper/access/AccessMode.java
index e34facf..bf4eff1 100644
--- 
a/johnzon-mapper/src/main/java/org/apache/johnzon/mapper/access/AccessMode.java
+++ 
b/johnzon-mapper/src/main/java/org/apache/johnzon/mapper/access/AccessMode.java
@@ -59,4 +59,12 @@ public interface AccessMode {
     Map<String, Writer> findWriters(Class<?> clazz);
     ObjectConverter.Reader<?> findReader(Class<?> clazz);
     ObjectConverter.Writer<?> findWriter(Class<?> clazz);
+    Adapter<?, ?> findAdapter(Class<?> clazz);
+
+    /**
+     * Called once johnzon will not use AccessMode anymore. Can be used to 
clean up any local cache.
+     *
+     * @param clazz the parsed class.
+     */
+    void afterParsed(Class<?> clazz);
 }

http://git-wip-us.apache.org/repos/asf/incubator-johnzon/blob/ba4235ca/johnzon-mapper/src/main/java/org/apache/johnzon/mapper/access/BaseAccessMode.java
----------------------------------------------------------------------
diff --git 
a/johnzon-mapper/src/main/java/org/apache/johnzon/mapper/access/BaseAccessMode.java
 
b/johnzon-mapper/src/main/java/org/apache/johnzon/mapper/access/BaseAccessMode.java
index 2eb7b11..ca3e604 100644
--- 
a/johnzon-mapper/src/main/java/org/apache/johnzon/mapper/access/BaseAccessMode.java
+++ 
b/johnzon-mapper/src/main/java/org/apache/johnzon/mapper/access/BaseAccessMode.java
@@ -93,6 +93,16 @@ public abstract class BaseAccessMode implements AccessMode {
     }
 
     @Override
+    public Adapter<?, ?> findAdapter(Class<?> clazz) {
+        return null; // TODO: converter?
+    }
+
+    @Override
+    public void afterParsed(final Class<?> clazz) {
+        // no-op
+    }
+
+    @Override
     public Factory findFactory(final Class<?> clazz) {
         Constructor<?> constructor = null;
         for (final Constructor<?> c : clazz.getDeclaredConstructors()) {

http://git-wip-us.apache.org/repos/asf/incubator-johnzon/blob/ba4235ca/johnzon-mapper/src/main/java/org/apache/johnzon/mapper/converter/ReversedAdapter.java
----------------------------------------------------------------------
diff --git 
a/johnzon-mapper/src/main/java/org/apache/johnzon/mapper/converter/ReversedAdapter.java
 
b/johnzon-mapper/src/main/java/org/apache/johnzon/mapper/converter/ReversedAdapter.java
new file mode 100644
index 0000000..c7f60b0
--- /dev/null
+++ 
b/johnzon-mapper/src/main/java/org/apache/johnzon/mapper/converter/ReversedAdapter.java
@@ -0,0 +1,39 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.johnzon.mapper.converter;
+
+import org.apache.johnzon.mapper.Adapter;
+
+public class ReversedAdapter<A, B> implements Adapter<A, B> {
+    private final Adapter<B, A> delegate;
+
+    public ReversedAdapter(final Adapter<B, A> delegate) {
+        this.delegate = delegate;
+    }
+
+    @Override
+    public A to(final B b) {
+        return delegate.from(b);
+    }
+
+    @Override
+    public B from(final A a) {
+        return delegate.to(a);
+    }
+}

http://git-wip-us.apache.org/repos/asf/incubator-johnzon/blob/ba4235ca/johnzon-mapper/src/test/java/org/apache/johnzon/mapper/MapperConfigTest.java
----------------------------------------------------------------------
diff --git 
a/johnzon-mapper/src/test/java/org/apache/johnzon/mapper/MapperConfigTest.java 
b/johnzon-mapper/src/test/java/org/apache/johnzon/mapper/MapperConfigTest.java
index 52a3eeb..f51ce91 100644
--- 
a/johnzon-mapper/src/test/java/org/apache/johnzon/mapper/MapperConfigTest.java
+++ 
b/johnzon-mapper/src/test/java/org/apache/johnzon/mapper/MapperConfigTest.java
@@ -165,7 +165,8 @@ public class MapperConfigTest {
                                 false,
                                 new FieldAccessMode(true, true),
                                 Charset.forName("UTF-8"),
-                                null);
+                                null,
+                                false);
     }
 
 

http://git-wip-us.apache.org/repos/asf/incubator-johnzon/blob/ba4235ca/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 db2a212..7967060 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
@@ -163,6 +163,7 @@ public class MapperTest {
     public void justObjectAsModel() {
         final Mapper encodingAwareMapper = new 
MapperBuilder().setEncoding("UTF-8" /*otherwise guess algo fails for too small 
string*/).build();
         final Mapper simpleMapper = new MapperBuilder().build();
+        final Mapper enforcedQuotes = new 
MapperBuilder().setEnforceQuoteString().build();
         { // object
             final String object = 
"{\"a\":1,\"b\":true,\"c\":null,\"d\":[1,2]," +
                     
"\"e\":[\"i\",\"j\"],\"k\":{\"a\":1,\"b\":true,\"c\":null,\"d\":[1,2],\"e\":[\"i\",\"j\"]}}";
@@ -196,7 +197,7 @@ public class MapperTest {
             assertEquals("true", simpleMapper.writeObjectAsString(true));
             assertEquals("false", simpleMapper.writeObjectAsString(false));
             assertEquals("1", simpleMapper.writeObjectAsString(1));
-            assertEquals("\"val\"", simpleMapper.writeObjectAsString("val"));
+            assertEquals("\"val\"", enforcedQuotes.writeObjectAsString("val"));
             assertEquals("[\"val1\",\"val2\"]", 
simpleMapper.writeObjectAsString(asList("val1", "val2")));
             assertEquals("{\"a\":\"val\",\"b\":true,\"c\":1}", 
simpleMapper.writeObjectAsString(new TreeMap<String, Object>() {{
                 put("a", "val");


Reply via email to