You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@johnzon.apache.org by rm...@apache.org on 2015/07/28 11:51:52 UTC
incubator-johnzon git commit: JOHNZON-50 enhancing both mode to
support merging of annotations,
field still but method is read - and doesnt fully overwrite - if field doesnt
have the information,
JOHNZON-48 adding Karl Grosse tests for @Converter for ite
Repository: incubator-johnzon
Updated Branches:
refs/heads/master 1ea4fd8c3 -> 70a77af7f
JOHNZON-50 enhancing both mode to support merging of annotations, field still but method is read - and doesnt fully overwrite - if field doesnt have the information, JOHNZON-48 adding Karl Grosse tests for @Converter for item type of collections, enhancing his patch to support collections and eagerly enum converter caching
Project: http://git-wip-us.apache.org/repos/asf/incubator-johnzon/repo
Commit: http://git-wip-us.apache.org/repos/asf/incubator-johnzon/commit/70a77af7
Tree: http://git-wip-us.apache.org/repos/asf/incubator-johnzon/tree/70a77af7
Diff: http://git-wip-us.apache.org/repos/asf/incubator-johnzon/diff/70a77af7
Branch: refs/heads/master
Commit: 70a77af7f4271e06f6295d5b6b50070ae0e12d73
Parents: 1ea4fd8
Author: Romain Manni-Bucau <rm...@starbucks.com>
Authored: Tue Jul 28 02:51:24 2015 -0700
Committer: Romain Manni-Bucau <rm...@starbucks.com>
Committed: Tue Jul 28 02:51:44 2015 -0700
----------------------------------------------------------------------
.../org/apache/johnzon/mapper/Converter.java | 8 +
.../java/org/apache/johnzon/mapper/Mapper.java | 61 +++---
.../apache/johnzon/mapper/MapperBuilder.java | 34 ++--
.../mapper/access/FieldAndMethodAccessMode.java | 69 ++++++-
.../johnzon/mapper/converter/EnumConverter.java | 16 +-
.../johnzon/mapper/reflection/Mappings.java | 146 ++++++++++++++-
.../org/apache/johnzon/mapper/EnumTest.java | 185 +++++++++++++++++++
.../johnzon/mapper/ObjectConverterTest.java | 152 +++++++++++++++
8 files changed, 619 insertions(+), 52 deletions(-)
----------------------------------------------------------------------
http://git-wip-us.apache.org/repos/asf/incubator-johnzon/blob/70a77af7/johnzon-mapper/src/main/java/org/apache/johnzon/mapper/Converter.java
----------------------------------------------------------------------
diff --git a/johnzon-mapper/src/main/java/org/apache/johnzon/mapper/Converter.java b/johnzon-mapper/src/main/java/org/apache/johnzon/mapper/Converter.java
index 3c36273..6439853 100644
--- a/johnzon-mapper/src/main/java/org/apache/johnzon/mapper/Converter.java
+++ b/johnzon-mapper/src/main/java/org/apache/johnzon/mapper/Converter.java
@@ -18,7 +18,15 @@
*/
package org.apache.johnzon.mapper;
+import java.lang.reflect.Type;
+
public interface Converter<T> {
String toString(T instance);
T fromString(String text);
+
+ // for generic converters it allows to explicitely provide the converted type (ex: enum converter)
+ // typically useful when generic type get resolved to a TypeVariable
+ interface TypeAccess {
+ Type type();
+ }
}
http://git-wip-us.apache.org/repos/asf/incubator-johnzon/blob/70a77af7/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 fb2ec78..e733af0 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
@@ -85,7 +85,7 @@ public class Mapper {
// CHECKSTYLE:OFF
public Mapper(final JsonReaderFactory readerFactory, final JsonGeneratorFactory generatorFactory,
- final boolean doClose, final Map<Class<?>, Converter<?>> converters,
+ final boolean doClose, final Map<Type, Converter<?>> converters,
final int version, final Comparator<String> attributeOrder, final boolean skipNull, final boolean skipEmptyArray,
final AccessMode accessMode, final boolean hiddenConstructorSupported, final boolean useConstructors,
final boolean treatByteArrayAsBase64,
@@ -96,7 +96,7 @@ public class Mapper {
this.close = doClose;
this.converters = new ConcurrentHashMap<Type, Converter<?>>(converters);
this.version = version;
- this.mappings = new Mappings(attributeOrder, accessMode, hiddenConstructorSupported, useConstructors, version, converters);
+ this.mappings = new Mappings(attributeOrder, accessMode, hiddenConstructorSupported, useConstructors, version, this.converters);
this.skipNull = skipNull;
this.skipEmptyArray = skipEmptyArray;
this.treatByteArrayAsBase64 = treatByteArrayAsBase64;
@@ -361,6 +361,7 @@ public class Mapper {
generator = writeValue(generator, val.getClass(),
getter.primitive, getter.array,
getter.collection, getter.map,
+ getter.itemConverter,
getterEntry.getKey(),
val);
}
@@ -389,7 +390,7 @@ public class Mapper {
final boolean collection = clazz || primitive || array ? false : Collection.class.isAssignableFrom(valueClass);
final boolean map = clazz || primitive || array || collection ? false : Map.class.isAssignableFrom(valueClass);
generator = writeValue(generator, valueClass,
- primitive, array, collection, map,
+ primitive, array, collection, map, null /* TODO? */,
key == null ? "null" : key.toString(), value);
}
return generator;
@@ -398,6 +399,7 @@ public class Mapper {
private JsonGenerator writeValue(final JsonGenerator generator, final Class<?> type,
final boolean primitive, final boolean array,
final boolean collection, final boolean map,
+ final Converter itemConverter,
final String key, final Object value) throws InvocationTargetException, IllegalAccessException {
if (array) {
final int length = Array.getLength(value);
@@ -413,13 +415,14 @@ public class Mapper {
JsonGenerator gen = generator.writeStartArray(key);
for (int i = 0; i < length; i++) {
- gen = writeItem(gen, Array.get(value, i));
+ final Object o = Array.get(value, i);
+ gen = writeItem(gen, itemConverter != null ? itemConverter.toString(o) : o);
}
return gen.writeEnd();
} else if (collection) {
JsonGenerator gen = generator.writeStartArray(key);
for (final Object o : Collection.class.cast(value)) {
- gen = writeItem(gen, o);
+ gen = writeItem(gen, itemConverter != null ? itemConverter.toString(o) : o);
}
return gen.writeEnd();
} else if (map) {
@@ -431,7 +434,7 @@ public class Mapper {
} else {
final Converter<?> converter = findConverter(type);
if (converter != null) {
- return writeValue(generator, String.class, true, false, false, false, key,
+ return writeValue(generator, String.class, true, false, false, false, null, key,
doConvertFrom(value, (Converter<Object>) converter));
}
return doWriteObjectBody(generator.writeStartObject(key), value).writeEnd();
@@ -490,7 +493,7 @@ public class Mapper {
throw new UnsupportedOperationException("type " + genericType + " not supported");
}
try {
- return mapCollection(mapping, reader.readArray());
+ return mapCollection(mapping, reader.readArray(), null);
} catch (final Exception e) {
throw new MapperException(e);
} finally {
@@ -515,7 +518,7 @@ public class Mapper {
throw new UnsupportedOperationException("type " + genericType + " not supported");
}
try {
- return mapCollection(mapping, reader.readArray());
+ return mapCollection(mapping, reader.readArray(), null);
} catch (final Exception e) {
throw new MapperException(e);
} finally {
@@ -537,7 +540,7 @@ public class Mapper {
private <T> T[] mapArray(final Class<T> clazz, final JsonReader reader) {
try {
- return (T[]) buildArrayWithComponentType(reader.readArray(), clazz);
+ return (T[]) buildArrayWithComponentType(reader.readArray(), clazz, null);
} catch (final Exception e) {
throw new MapperException(e);
} finally {
@@ -583,7 +586,7 @@ public class Mapper {
}
for (final Map.Entry<String, JsonValue> value : object.entrySet()) {
- map.put(convertTo(keyType, value.getKey()), toObject(value.getValue(), fieldArgTypes[1]));
+ map.put(convertTo(keyType, value.getKey()), toObject(value.getValue(), fieldArgTypes[1], null));
}
return map;
}
@@ -604,7 +607,7 @@ public class Mapper {
final JsonValue jsonValue = object.get(setter.getKey());
final Mappings.Setter value = setter.getValue();
final AccessMode.Writer setterMethod = value.writer;
- final Object convertedValue = toValue(jsonValue, value.converter, value.paramType);
+ final Object convertedValue = toValue(jsonValue, value.converter, value.itemConverter, value.paramType);
if (convertedValue != null) {
setterMethod.write(t, convertedValue);
@@ -614,9 +617,9 @@ public class Mapper {
return t;
}
- private Object toValue(final JsonValue jsonValue, final Converter<?> converter, final Type type) throws Exception {
+ private Object toValue(final JsonValue jsonValue, final Converter<?> converter, final Converter<?> itemConverter, final Type type) throws Exception {
return converter == null ?
- toObject(jsonValue, type) : jsonValue.getValueType() == ValueType.STRING ?
+ toObject(jsonValue, type, itemConverter) : jsonValue.getValueType() == ValueType.STRING ?
converter.fromString(JsonString.class.cast(jsonValue).getString()) :
converter.fromString(jsonValue.toString());
}
@@ -624,12 +627,14 @@ public class Mapper {
private Object[] createParameters(final Mappings.ClassMapping mapping, final JsonObject object) throws Exception {
final Object[] objects = new Object[mapping.constructorParameters.length];
for (int i = 0; i < mapping.constructorParameters.length; i++) {
- objects[i] = toValue(object.get(mapping.constructorParameters[i]), mapping.constructorParameterConverters[i], mapping.constructorParameterTypes[i]);
+ objects[i] = toValue(
+ object.get(mapping.constructorParameters[i]), mapping.constructorParameterConverters[i],
+ mapping.constructorItemParameterConverters[i], mapping.constructorParameterTypes[i]);
}
return objects;
}
- private Object toObject(final JsonValue jsonValue, final Type type) throws Exception {
+ private Object toObject(final JsonValue jsonValue, final Type type, final Converter<?> itemConverter) throws Exception {
if (jsonValue == null || jsonValue == JsonValue.NULL) {
return null;
}
@@ -671,7 +676,7 @@ public class Mapper {
if (JsonObject.class.isInstance(jsonValue)) {
return buildObject(type, JsonObject.class.cast(jsonValue));
} else if (JsonArray.class.isInstance(jsonValue)) {
- return buildArray(type, JsonArray.class.cast(jsonValue));
+ return buildArray(type, JsonArray.class.cast(jsonValue), itemConverter);
} else if (JsonNumber.class.isInstance(jsonValue)) {
final JsonNumber number = JsonNumber.class.cast(jsonValue);
@@ -708,36 +713,41 @@ public class Mapper {
return number.bigDecimalValue();
}
} else if (JsonString.class.isInstance(jsonValue) || Object.class == type) {
- return convertTo(Class.class.cast(type), JsonString.class.cast(jsonValue).getString());
+ final String string = JsonString.class.cast(jsonValue).getString();
+ if (itemConverter == null) {
+ return convertTo(Class.class.cast(type), string);
+ } else {
+ return itemConverter.fromString(string);
+ }
}
throw new MapperException("Unable to parse " + jsonValue + " to " + type);
}
- private Object buildArray(final Type type, final JsonArray jsonArray) throws Exception {
+ private Object buildArray(final Type type, final JsonArray jsonArray, final Converter<?> itemConverter) throws Exception {
if (Class.class.isInstance(type)) {
final Class clazz = Class.class.cast(type);
if (clazz.isArray()) {
final Class<?> componentType = clazz.getComponentType();
- return buildArrayWithComponentType(jsonArray, componentType);
+ return buildArrayWithComponentType(jsonArray, componentType, itemConverter);
}
}
if (ParameterizedType.class.isInstance(type)) {
final Mappings.CollectionMapping mapping = mappings.findCollectionMapping(ParameterizedType.class.cast(type));
if (mapping != null) {
- return mapCollection(mapping, jsonArray);
+ return mapCollection(mapping, jsonArray, itemConverter);
}
}
if (Object.class == type) {
- return buildArray(ANY_LIST, jsonArray);
+ return buildArray(ANY_LIST, jsonArray, null);
}
throw new UnsupportedOperationException("type " + type + " not supported");
}
- private <T> Collection<T> mapCollection(final Mappings.CollectionMapping mapping, final JsonArray jsonArray) throws Exception {
+ private <T> Collection<T> mapCollection(final Mappings.CollectionMapping mapping, final JsonArray jsonArray, final Converter<?> itemConverter) throws Exception {
final Collection collection;
if (SortedSet.class == mapping.raw) {
@@ -753,17 +763,16 @@ public class Mapper {
}
for (final JsonValue value : jsonArray) {
- final Object element = toObject(value, mapping.arg);
- collection.add(element);
+ collection.add(toObject(value, mapping.arg, itemConverter));
}
return collection;
}
- private Object buildArrayWithComponentType(final JsonArray jsonArray, final Class<?> componentType) throws Exception {
+ private Object buildArrayWithComponentType(final JsonArray jsonArray, final Class<?> componentType, final Converter<?> itemConverter) throws Exception {
final Object array = Array.newInstance(componentType, jsonArray.size());
int i = 0;
for (final JsonValue value : jsonArray) {
- Array.set(array, i++, toObject(value, componentType));
+ Array.set(array, i++, toObject(value, componentType, itemConverter));
}
return array;
}
http://git-wip-us.apache.org/repos/asf/incubator-johnzon/blob/70a77af7/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 4f7e68c..cee2841 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
@@ -18,19 +18,6 @@
*/
package org.apache.johnzon.mapper;
-import java.math.BigDecimal;
-import java.math.BigInteger;
-import java.nio.charset.Charset;
-import java.util.Comparator;
-import java.util.Date;
-import java.util.HashMap;
-import java.util.Map;
-
-import javax.json.JsonReaderFactory;
-import javax.json.spi.JsonProvider;
-import javax.json.stream.JsonGenerator;
-import javax.json.stream.JsonGeneratorFactory;
-
import org.apache.johnzon.mapper.access.AccessMode;
import org.apache.johnzon.mapper.access.BaseAccessMode;
import org.apache.johnzon.mapper.access.FieldAccessMode;
@@ -51,6 +38,19 @@ import org.apache.johnzon.mapper.converter.LongConverter;
import org.apache.johnzon.mapper.converter.ShortConverter;
import org.apache.johnzon.mapper.converter.StringConverter;
+import javax.json.JsonReaderFactory;
+import javax.json.spi.JsonProvider;
+import javax.json.stream.JsonGenerator;
+import javax.json.stream.JsonGeneratorFactory;
+import java.lang.reflect.Type;
+import java.math.BigDecimal;
+import java.math.BigInteger;
+import java.nio.charset.Charset;
+import java.util.Comparator;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.Map;
+
public class MapperBuilder {
private static final Map<Class<?>, Converter<?>> DEFAULT_CONVERTERS = new HashMap<Class<?>, Converter<?>>();
@@ -94,7 +94,7 @@ public class MapperBuilder {
protected boolean pretty;
private AccessMode accessMode = new MethodAccessMode(false);
private boolean treatByteArrayAsBase64;
- private final Map<Class<?>, Converter<?>> converters = new HashMap<Class<?>, Converter<?>>(DEFAULT_CONVERTERS);
+ private final Map<Type, Converter<?>> converters = new HashMap<Type, Converter<?>>(DEFAULT_CONVERTERS);
private boolean supportConstructors;
private Charset encoding = Charset.forName(System.getProperty("johnzon.mapper.encoding", "UTF-8"));
@@ -230,11 +230,17 @@ public class MapperBuilder {
return this;
}
+ @Deprecated // use addConverter
public MapperBuilder addPropertyEditor(final Class<?> clazz, final Converter<?> converter) {
this.converters.put(clazz, converter);
return this;
}
+ public MapperBuilder addConverter(final Type clazz, final Converter<?> converter) {
+ this.converters.put(clazz, converter);
+ return this;
+ }
+
public MapperBuilder setVersion(final int version) {
this.version = version;
return this;
http://git-wip-us.apache.org/repos/asf/incubator-johnzon/blob/70a77af7/johnzon-mapper/src/main/java/org/apache/johnzon/mapper/access/FieldAndMethodAccessMode.java
----------------------------------------------------------------------
diff --git a/johnzon-mapper/src/main/java/org/apache/johnzon/mapper/access/FieldAndMethodAccessMode.java b/johnzon-mapper/src/main/java/org/apache/johnzon/mapper/access/FieldAndMethodAccessMode.java
index 0d8e94e..0c3a2da 100644
--- a/johnzon-mapper/src/main/java/org/apache/johnzon/mapper/access/FieldAndMethodAccessMode.java
+++ b/johnzon-mapper/src/main/java/org/apache/johnzon/mapper/access/FieldAndMethodAccessMode.java
@@ -18,6 +18,8 @@
*/
package org.apache.johnzon.mapper.access;
+import java.lang.annotation.Annotation;
+import java.lang.reflect.Type;
import java.util.HashMap;
import java.util.Map;
@@ -29,14 +31,77 @@ public class FieldAndMethodAccessMode extends BaseAccessMode {
@Override
public Map<String, Reader> doFindReaders(final Class<?> clazz) {
final Map<String, Reader> readers = new HashMap<String, Reader>(fields.findReaders(clazz));
- readers.putAll(methods.findReaders(clazz));
+ for (final Map.Entry<String, Reader> entry : methods.findReaders(clazz).entrySet()) {
+ final Reader existing = readers.get(entry.getKey());
+ if (existing == null) {
+ readers.put(entry.getKey(), entry.getValue());
+ } else {
+ readers.put(entry.getKey(), new CompositeReader(existing, entry.getValue()));
+ }
+ }
return readers;
}
@Override
public Map<String, Writer> doFindWriters(final Class<?> clazz) {
final Map<String, Writer> writers = new HashMap<String, Writer>(fields.findWriters(clazz));
- writers.putAll(methods.findWriters(clazz));
+ for (final Map.Entry<String, Writer> entry : methods.findWriters(clazz).entrySet()) {
+ final Writer existing = writers.get(entry.getKey());
+ if (existing == null) {
+ writers.put(entry.getKey(), entry.getValue());
+ } else {
+ writers.put(entry.getKey(), new CompositeWriter(existing, entry.getValue()));
+ }
+ }
return writers;
}
+
+ private static abstract class CompositeDecoratedType implements DecoratedType {
+ protected final DecoratedType type1;
+ private final DecoratedType type2;
+
+ private CompositeDecoratedType(final DecoratedType type1, final DecoratedType type2) {
+ this.type1 = type1;
+ this.type2 = type2;
+ }
+
+ @Override
+ public <T extends Annotation> T getAnnotation(final Class<T> clazz) {
+ final T found = type1.getAnnotation(clazz);
+ return found == null ? type2.getAnnotation(clazz) : found;
+ }
+
+ @Override
+ public Type getType() {
+ return type1.getType();
+ }
+ }
+
+ private static final class CompositeReader extends CompositeDecoratedType implements Reader {
+ private final Reader reader;
+
+ private CompositeReader(final Reader type1, final DecoratedType type2) {
+ super(type1, type2);
+ reader = type1;
+ }
+
+ @Override
+ public Object read(final Object instance) {
+ return reader.read(instance);
+ }
+ }
+
+ private static final class CompositeWriter extends CompositeDecoratedType implements Writer {
+ private final Writer writer;
+
+ private CompositeWriter(final Writer type1, final DecoratedType type2) {
+ super(type1, type2);
+ writer = type1;
+ }
+
+ @Override
+ public void write(final Object instance, final Object value) {
+ writer.write(instance, value);
+ }
+ }
}
http://git-wip-us.apache.org/repos/asf/incubator-johnzon/blob/70a77af7/johnzon-mapper/src/main/java/org/apache/johnzon/mapper/converter/EnumConverter.java
----------------------------------------------------------------------
diff --git a/johnzon-mapper/src/main/java/org/apache/johnzon/mapper/converter/EnumConverter.java b/johnzon-mapper/src/main/java/org/apache/johnzon/mapper/converter/EnumConverter.java
index 793356d..f5cd2ae 100644
--- a/johnzon-mapper/src/main/java/org/apache/johnzon/mapper/converter/EnumConverter.java
+++ b/johnzon-mapper/src/main/java/org/apache/johnzon/mapper/converter/EnumConverter.java
@@ -20,13 +20,17 @@ package org.apache.johnzon.mapper.converter;
import org.apache.johnzon.mapper.Converter;
+import java.lang.reflect.Type;
import java.util.HashMap;
import java.util.Map;
-public class EnumConverter<T extends Enum<T>> implements Converter<T> {
+public class EnumConverter<T extends Enum<T>> implements Converter<T>, Converter.TypeAccess {
private final Map<String, T> values;
+ private final Class<T> enumType;
public EnumConverter(final Class<T> aClass) {
+ this.enumType = aClass;
+
final T[] enumConstants = aClass.getEnumConstants();
values = new HashMap<String, T>(enumConstants.length);
for (final T t : enumConstants) {
@@ -43,4 +47,14 @@ public class EnumConverter<T extends Enum<T>> implements Converter<T> {
public T fromString(final String text) {
return values.get(text);
}
+
+ @Override
+ public String toString() {
+ return getClass().getSimpleName() + values.keySet();
+ }
+
+ @Override
+ public Type type() {
+ return enumType;
+ }
}
http://git-wip-us.apache.org/repos/asf/incubator-johnzon/blob/70a77af7/johnzon-mapper/src/main/java/org/apache/johnzon/mapper/reflection/Mappings.java
----------------------------------------------------------------------
diff --git a/johnzon-mapper/src/main/java/org/apache/johnzon/mapper/reflection/Mappings.java b/johnzon-mapper/src/main/java/org/apache/johnzon/mapper/reflection/Mappings.java
index 60ccace..69cf6bb 100644
--- a/johnzon-mapper/src/main/java/org/apache/johnzon/mapper/reflection/Mappings.java
+++ b/johnzon-mapper/src/main/java/org/apache/johnzon/mapper/reflection/Mappings.java
@@ -25,6 +25,7 @@ import org.apache.johnzon.mapper.JohnzonVirtualObject;
import org.apache.johnzon.mapper.JohnzonVirtualObjects;
import org.apache.johnzon.mapper.access.AccessMode;
import org.apache.johnzon.mapper.converter.DateWithCopyConverter;
+import org.apache.johnzon.mapper.converter.EnumConverter;
import java.beans.ConstructorProperties;
import java.lang.annotation.Annotation;
@@ -62,6 +63,7 @@ public class Mappings {
public final boolean constructorHasArguments;
public final String[] constructorParameters;
public final Converter<?>[] constructorParameterConverters;
+ public final Converter<?>[] constructorItemParameterConverters;
public final Type[] constructorParameterTypes;
protected ClassMapping(final Class<?> clazz,
@@ -76,16 +78,25 @@ public class Mappings {
if (this.constructorHasArguments) {
this.constructorParameterTypes = this.constructor.getGenericParameterTypes();
+ // TODO: java 8 gives access to it without annotation
this.constructorParameters = new String[this.constructor.getGenericParameterTypes().length];
final ConstructorProperties constructorProperties = this.constructor.getAnnotation(ConstructorProperties.class);
System.arraycopy(constructorProperties.value(), 0, this.constructorParameters, 0, this.constructorParameters.length);
this.constructorParameterConverters = new Converter<?>[this.constructor.getGenericParameterTypes().length];
+ this.constructorItemParameterConverters = new Converter<?>[this.constructorParameterConverters.length];
for (int i = 0; i < this.constructorParameters.length; i++) {
for (final Annotation a : this.constructor.getParameterAnnotations()[i]) {
if (a.annotationType() == JohnzonConverter.class) {
try {
- this.constructorParameterConverters[i] = JohnzonConverter.class.cast(a).value().newInstance();
+ final Converter<?> converter = JohnzonConverter.class.cast(a).value().newInstance();
+ if (matches(this.constructor.getParameterTypes()[i], converter)) {
+ this.constructorParameterConverters[i] = converter;
+ this.constructorItemParameterConverters[i] = null;
+ } else {
+ this.constructorParameterConverters[i] = null;
+ this.constructorItemParameterConverters[i] = converter;
+ }
} catch (final Exception e) {
throw new IllegalArgumentException(e);
}
@@ -96,6 +107,7 @@ public class Mappings {
this.constructorParameterTypes = null;
this.constructorParameters = null;
this.constructorParameterConverters = null;
+ this.constructorItemParameterConverters = null;
}
}
@@ -142,6 +154,7 @@ public class Mappings {
public final AccessMode.Reader reader;
public final int version;
public final Converter<Object> converter;
+ public final Converter<Object> itemConverter;
public final boolean primitive;
public final boolean array;
public final boolean map;
@@ -150,14 +163,38 @@ public class Mappings {
public Getter(final AccessMode.Reader reader,
final boolean primitive, final boolean array,
final boolean collection, final boolean map,
- final Converter<Object> converter, final int version) {
+ final Converter<Object> converter,
+ final int version) {
this.reader = reader;
- this.converter = converter;
this.version = version;
this.array = array;
this.map = map && converter == null;
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;
+ }
+ }
+
+ @Override
+ public String toString() {
+ return "Getter{" +
+ "reader=" + reader +
+ ", version=" + version +
+ ", converter=" + converter +
+ ", itemConverter=" + itemConverter +
+ ", primitive=" + primitive +
+ ", array=" + array +
+ ", map=" + map +
+ ", collection=" + collection +
+ '}';
}
}
@@ -166,26 +203,96 @@ public class Mappings {
public final int version;
public final Type paramType;
public final Converter<?> converter;
+ public final Converter<?> 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 Converter<?> converter, final int version) {
+ final Type paramType, final Converter<?> converter,
+ final int version) {
this.writer = writer;
this.paramType = paramType;
- this.converter = converter;
this.version = version;
this.primitive = primitive;
this.array = array;
+ 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 +
+ '}';
}
}
+ // TODO: more ParameterizedType and maybe TypeClosure support
+ private static boolean matches(final Type type, final Converter<?> converter) {
+ Type convertType = null;
+ if (Converter.TypeAccess.class.isInstance(converter)) {
+ convertType = Converter.TypeAccess.class.cast(converter).type();
+ } else {
+ for (final Type pt : converter.getClass().getGenericInterfaces()) {
+ if (ParameterizedType.class.isInstance(pt) && ParameterizedType.class.cast(pt).getRawType() == Converter.class) {
+ convertType = ParameterizedType.class.cast(pt).getActualTypeArguments()[0];
+ break;
+ }
+ }
+ }
+
+ if (convertType == null) { // compatibility, previously nested converter were not supported
+ return true;
+ }
+
+ if (ParameterizedType.class.isInstance(type)) {
+ final ParameterizedType parameterizedType = ParameterizedType.class.cast(type);
+ final Type rawType = parameterizedType.getRawType();
+ if (Class.class.isInstance(rawType)) {
+ final Class<?> clazz = Class.class.cast(rawType);
+ if (Collection.class.isAssignableFrom(clazz) && parameterizedType.getActualTypeArguments().length == 1) {
+ final Type argType = parameterizedType.getActualTypeArguments()[0];
+ if (Class.class.isInstance(argType) && Class.class.isInstance(convertType)) {
+ return !Class.class.cast(convertType).isAssignableFrom(Class.class.cast(argType));
+ }
+ } else if (Map.class.isAssignableFrom(clazz) && parameterizedType.getActualTypeArguments().length == 2) {
+ final Type argType = parameterizedType.getActualTypeArguments()[1];
+ if (Class.class.isInstance(argType) && Class.class.isInstance(convertType)) {
+ return !Class.class.cast(convertType).isAssignableFrom(Class.class.cast(argType));
+ }
+ }
+ return true; // actually here we suppose we dont know
+ }
+ }
+ if (Class.class.isInstance(type)) {
+ final Class<?> clazz = Class.class.cast(type);
+ if (clazz.isArray()) {
+ return !Class.class.cast(convertType).isAssignableFrom(clazz.getComponentType());
+ }
+ }
+ return true;
+ }
+
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 Map<Class<?>, Converter<?>> converters;
+ protected final ConcurrentMap<Type, Converter<?>> converters;
private final boolean supportHiddenConstructors;
private final boolean supportConstructors;
private final AccessMode accessMode;
@@ -193,7 +300,7 @@ public class Mappings {
public Mappings(final Comparator<String> attributeOrder, final AccessMode accessMode,
final boolean supportHiddenConstructors, final boolean supportConstructors,
- final int version, final Map<Class<?>, Converter<?>> converters) {
+ final int version, final ConcurrentMap<Type, Converter<?>> converters) {
this.fieldOrdering = attributeOrder;
this.accessMode = accessMode;
this.supportHiddenConstructors = supportHiddenConstructors;
@@ -446,14 +553,35 @@ public class Mappings {
private Converter findConverter(final boolean copyDate, final AccessMode.DecoratedType method) {
Converter converter = null;
final JohnzonConverter annotation = method.getAnnotation(JohnzonConverter.class);
+
+
+ Type typeToTest = method.getType();
if (annotation != null) {
try {
converter = annotation.value().newInstance();
} catch (final Exception e) {
throw new IllegalArgumentException(e);
}
- } else if (Class.class.isInstance(method.getType()) && Date.class.isAssignableFrom(Class.class.cast(method.getType())) && copyDate) {
- converter = new DateWithCopyConverter(Converter.class.cast(converters.get(Date.class)));
+ } else if (ParameterizedType.class.isInstance(method.getType())) {
+ final ParameterizedType type = ParameterizedType.class.cast(method.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(Converter.class.cast(converters.get(Date.class)));
+ } else if (type.isEnum()) {
+ converter = converters.get(type); // first ensure user didnt override it
+ if (converter == null) {
+ converter = new EnumConverter(type);
+ converters.put(type, converter);
+ }
+ }
}
return converter;
}
http://git-wip-us.apache.org/repos/asf/incubator-johnzon/blob/70a77af7/johnzon-mapper/src/test/java/org/apache/johnzon/mapper/EnumTest.java
----------------------------------------------------------------------
diff --git a/johnzon-mapper/src/test/java/org/apache/johnzon/mapper/EnumTest.java b/johnzon-mapper/src/test/java/org/apache/johnzon/mapper/EnumTest.java
new file mode 100644
index 0000000..841e4ec
--- /dev/null
+++ b/johnzon-mapper/src/test/java/org/apache/johnzon/mapper/EnumTest.java
@@ -0,0 +1,185 @@
+/*
+ * 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.junit.Assert;
+import org.junit.Test;
+
+import java.util.Arrays;
+import java.util.Comparator;
+import java.util.List;
+
+public class EnumTest {
+
+ @Test
+ public void testSimpleEnumAccessModeBoth() {
+ testSimpleField(newTestMapperBuilder().setAccessModeName("both")
+ .build());
+ }
+
+ @Test
+ public void testSimpleEnumAccessModeField() {
+ testSimpleField(newTestMapperBuilder().setAccessModeName("field")
+ .build());
+ }
+
+ private void testSimpleField(Mapper mapper) {
+ SimpleObject object = new SimpleObject(MyEnum.TWO);
+
+ String objectAsString = mapper.writeObjectAsString(object);
+ Assert.assertEquals("{\"myEnum\":\"TWO\"}", objectAsString);
+
+ Assert.assertEquals(object.myEnum, mapper.<SimpleObject>readObject(objectAsString, SimpleObject.class).myEnum);
+ }
+
+ @Test
+ public void testSimpleEnumWithCollectionAccessModeBoth() {
+ testCollection(newTestMapperBuilder().setAccessModeName("both")
+ .build());
+ }
+
+ @Test
+ public void testSimpleEnumWithCollectionAccessModeField() {
+ testCollection(newTestMapperBuilder().setAccessModeName("field")
+ .build());
+ }
+
+
+ private void testCollection(Mapper mapper) {
+ CollectionObject object = new CollectionObject(MyEnum.ONE,
+ MyEnum.TWO,
+ MyEnum.THREE,
+ MyEnum.TWO);// duplicate entry isn't a mistake
+
+ String jsonString = "{\"enums\":[\"ONE\",\"TWO\",\"THREE\",\"TWO\"]}";
+
+ Assert.assertEquals(object.enums, mapper.<CollectionObject>readObject(jsonString, CollectionObject.class).enums);
+
+ String johnzonString = mapper.writeObjectAsString(object);
+ Assert.assertEquals(jsonString, johnzonString);
+ Assert.assertEquals(object.enums, mapper.<CollectionObject>readObject(johnzonString, CollectionObject.class).enums);
+ }
+
+
+ @Test
+ public void testAdvancedEnumAccessModeBoth() {
+ testAdvancedEnum(newTestMapperBuilder().setAccessModeName("both")
+ .build());
+ }
+
+ @Test
+ public void testAdvancedEnumAccessModeField() {
+ testAdvancedEnum(newTestMapperBuilder().setAccessModeName("field")
+ .build());
+ }
+
+ private void testAdvancedEnum(Mapper mapper) {
+ AdvancedEnumObject object = new AdvancedEnumObject(AdvancedEnum.VALUE_1, Arrays.asList(AdvancedEnum.VALUE_2,
+ AdvancedEnum.VALUE_1,
+ AdvancedEnum.VALUE_1,
+ AdvancedEnum.VALUE_2));
+
+ String jsonString = "{\"advancedEnum\":\"VALUE_1\",\"advancedEnums\":[\"VALUE_2\",\"VALUE_1\",\"VALUE_1\",\"VALUE_2\"]}";
+
+ AdvancedEnumObject johnzonObject = mapper.readObject(jsonString, AdvancedEnumObject.class);
+ Assert.assertEquals(object.advancedEnum, johnzonObject.advancedEnum);
+ Assert.assertEquals(object.advancedEnums, johnzonObject.advancedEnums);
+
+ String johnzonString = mapper.writeObjectAsString(object);
+ Assert.assertEquals(jsonString, johnzonString);
+
+ johnzonObject = mapper.readObject(johnzonString, AdvancedEnumObject.class);
+ Assert.assertEquals(object.advancedEnum, johnzonObject.advancedEnum);
+ Assert.assertEquals(object.advancedEnums, johnzonObject.advancedEnums);
+ }
+
+ private MapperBuilder newTestMapperBuilder() {
+ return new MapperBuilder().setAttributeOrder(new Comparator<String>() {
+ @Override
+ public int compare(final String o1, final String o2) {
+ return o1.compareTo(o2);
+ }
+ });
+ }
+
+ public enum MyEnum {
+ ONE,
+ TWO,
+ THREE
+ }
+
+ public static class SimpleObject {
+ private MyEnum myEnum;
+
+ private SimpleObject() {
+ }
+
+ private SimpleObject(MyEnum myEnum) {
+ this.myEnum = myEnum;
+ }
+ }
+
+ public static class CollectionObject {
+ private List<MyEnum> enums;
+
+ private CollectionObject() {
+ }
+
+ private CollectionObject(MyEnum... enums) {
+ this.enums = Arrays.asList(enums);
+ }
+ }
+
+
+ public enum AdvancedEnum {
+ VALUE_1("one", 1),
+ VALUE_2("two", 2);
+
+ private String string;
+ private int i;
+
+ private AdvancedEnum(String string, int i) {
+ this.string = string;
+ this.i = i;
+ }
+
+
+ public String getString() {
+ return string;
+ }
+
+ public int getI() {
+ return i;
+ }
+ }
+
+ public static class AdvancedEnumObject {
+ private AdvancedEnum advancedEnum;
+ private List<AdvancedEnum> advancedEnums;
+
+ private AdvancedEnumObject() {
+ }
+
+ public AdvancedEnumObject(AdvancedEnum advancedEnum, List<AdvancedEnum> advancedEnums) {
+ this.advancedEnum = advancedEnum;
+ this.advancedEnums = advancedEnums;
+ }
+ }
+
+}
\ No newline at end of file
http://git-wip-us.apache.org/repos/asf/incubator-johnzon/blob/70a77af7/johnzon-mapper/src/test/java/org/apache/johnzon/mapper/ObjectConverterTest.java
----------------------------------------------------------------------
diff --git a/johnzon-mapper/src/test/java/org/apache/johnzon/mapper/ObjectConverterTest.java b/johnzon-mapper/src/test/java/org/apache/johnzon/mapper/ObjectConverterTest.java
new file mode 100644
index 0000000..f138a0b
--- /dev/null
+++ b/johnzon-mapper/src/test/java/org/apache/johnzon/mapper/ObjectConverterTest.java
@@ -0,0 +1,152 @@
+/*
+ * 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.junit.Assert;
+import org.junit.Test;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Comparator;
+import java.util.List;
+
+public class ObjectConverterTest {
+
+ @Test
+ public void testObjectConverter() {
+ Contact contact = new Contact();
+ contact.linkedPersons.addAll(Arrays.asList(new Person("f1", "l1"), new Person("f2", "l2")));
+ contact.linkedPersonsArray = new Person[] { new Person("f3", "l3"), new Person("f4", "l4") };
+
+ MapperBuilder mapperBuilder = new MapperBuilder();
+ mapperBuilder.addConverter(Person.class, new PersonConverter());
+ Mapper mapper = mapperBuilder.setAccessModeName("both").setAttributeOrder(new Comparator<String>() {
+ @Override
+ public int compare(final String o1, final String o2) {
+ return o1.compareTo(o2);
+ }
+ }).build();
+
+ String s = mapper.writeObjectAsString(contact);
+ Contact c = mapper.readObject(s, Contact.class);
+ String expected = "{\"linkedPersons\":[\"f1|l1\",\"f2|l2\"],\"linkedPersonsArray\":[\"f3|l3\",\"f4|l4\"]}";
+ Assert.assertEquals(expected, s);
+ Assert.assertEquals(contact, c);
+ }
+
+
+ public static class PersonConverter implements Converter<Person> {
+ @Override
+ public String toString(Person instance) {
+ if (instance == null) {
+ return null;
+ }
+ return instance.getFirstName() + "|" + instance.getLastName();
+ }
+
+ @Override
+ public Person fromString(String text) {
+ if (text == null) {
+ return null;
+ }
+ String[] split = text.split("\\|");
+ if (split.length == 2) {
+ return new Person(split[0], split[1]);
+ }
+ return null;
+ }
+ }
+
+ public static class Contact {
+ @JohnzonConverter(PersonConverter.class)
+ private List<Person> linkedPersons = new ArrayList<Person>();
+
+ @JohnzonConverter(PersonConverter.class)
+ private Person[] linkedPersonsArray;
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (o == null || getClass() != o.getClass()) {
+ return false;
+ }
+
+ final Contact contact = Contact.class.cast(o);
+ return linkedPersons.equals(contact.linkedPersons) && Arrays.equals(linkedPersonsArray, contact.linkedPersonsArray);
+
+ }
+
+ @Override
+ public int hashCode() {
+ int result = linkedPersons.hashCode();
+ result = 31 * result + Arrays.hashCode(linkedPersonsArray);
+ return result;
+ }
+ }
+
+ public static class Person {
+ private String firstName;
+ private String lastName;
+
+ //no default constructor on purpose
+
+ private Person(String firstName, String lastName) {
+ this.firstName = firstName;
+ this.lastName = lastName;
+ }
+
+ public String getFirstName() {
+ return firstName;
+ }
+
+ public String getLastName() {
+ return lastName;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (!(o instanceof Person)) {
+ return false;
+ }
+
+ Person person = (Person) o;
+
+ if (firstName != null ? !firstName.equals(person.firstName) : person.firstName != null) {
+ return false;
+ }
+ if (lastName != null ? !lastName.equals(person.lastName) : person.lastName != null) {
+ return false;
+ }
+
+ return true;
+ }
+
+ @Override
+ public int hashCode() {
+ int result = firstName != null ? firstName.hashCode() : 0;
+ result = 31 * result + (lastName != null ? lastName.hashCode() : 0);
+ return result;
+ }
+ }
+}
\ No newline at end of file