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");
