JOHNZON-39 constructor instantiation using @ConstructorProperties
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 3997ca3..8235853 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
@@ -100,6 +100,10 @@
instance().writeTo(t, rawType, genericType, annotations, mediaType, httpHeaders, entityStream);
}
+ public void setSupportConstructors(final boolean supportConstructors) {
+ builder.setSupportConstructors(supportConstructors);
+ }
+
public void setPretty(final boolean pretty) {
builder.setPretty(pretty);
}
diff --git a/johnzon-mapper/src/main/java/org/apache/johnzon/mapper/JohnzonConverter.java b/johnzon-mapper/src/main/java/org/apache/johnzon/mapper/JohnzonConverter.java
index 1c1a01d..b3691cc 100644
--- a/johnzon-mapper/src/main/java/org/apache/johnzon/mapper/JohnzonConverter.java
+++ b/johnzon-mapper/src/main/java/org/apache/johnzon/mapper/JohnzonConverter.java
@@ -21,10 +21,12 @@
import java.lang.annotation.Retention;
import java.lang.annotation.Target;
+import static java.lang.annotation.ElementType.FIELD;
import static java.lang.annotation.ElementType.METHOD;
+import static java.lang.annotation.ElementType.PARAMETER;
import static java.lang.annotation.RetentionPolicy.RUNTIME;
-@Target(METHOD)
+@Target({ METHOD, FIELD, PARAMETER })
@Retention(RUNTIME)
public @interface JohnzonConverter {
Class<? extends Converter<?>> value();
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 7a70575..05f76c6 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
@@ -84,13 +84,14 @@
public Mapper(final JsonReaderFactory readerFactory, final JsonGeneratorFactory generatorFactory,
final boolean doClose, final Map<Class<?>, Converter<?>> converters,
final int version, final Comparator<String> attributeOrder, final boolean skipNull, final boolean skipEmptyArray,
- final AccessMode accessMode, final boolean hiddenConstructorSupported, final boolean treatByteArrayAsBase64) {
+ final AccessMode accessMode, final boolean hiddenConstructorSupported, final boolean useConstructors,
+ final boolean treatByteArrayAsBase64) {
this.readerFactory = readerFactory;
this.generatorFactory = generatorFactory;
this.close = doClose;
this.converters = new ConcurrentHashMap<Type, Converter<?>>(converters);
this.version = version;
- this.mappings = new Mappings(attributeOrder, accessMode, hiddenConstructorSupported);
+ this.mappings = new Mappings(attributeOrder, accessMode, hiddenConstructorSupported, useConstructors);
this.skipNull = skipNull;
this.skipEmptyArray = skipEmptyArray;
this.treatByteArrayAsBase64 = treatByteArrayAsBase64;
@@ -587,15 +588,13 @@
throw new IllegalArgumentException(classMapping.clazz.getName() + " can't be instantiated by Johnzon, this is a write only class");
}
- final Object t = classMapping.constructor.newInstance();
+ final Object t = !classMapping.constructorHasArguments ?
+ classMapping.constructor.newInstance() : classMapping.constructor.newInstance(createParameters(classMapping, object));
for (final Map.Entry<String, Mappings.Setter> setter : classMapping.setters.entrySet()) {
final JsonValue jsonValue = object.get(setter.getKey());
final Mappings.Setter value = setter.getValue();
final AccessMode.Writer setterMethod = value.writer;
- final Object convertedValue = value.converter == null ?
- toObject(jsonValue, value.paramType) : jsonValue.getValueType() == ValueType.STRING ?
- value.converter.fromString(JsonString.class.cast(jsonValue).getString()) :
- value.converter.fromString(jsonValue.toString());
+ final Object convertedValue = toValue(jsonValue, value.converter, value.paramType);
if (convertedValue != null) {
setterMethod.write(t, convertedValue);
@@ -605,6 +604,21 @@
return t;
}
+ private Object toValue(final JsonValue jsonValue, final Converter<?> converter, final Type type) throws Exception {
+ return converter == null ?
+ toObject(jsonValue, type) : jsonValue.getValueType() == ValueType.STRING ?
+ converter.fromString(JsonString.class.cast(jsonValue).getString()) :
+ converter.fromString(jsonValue.toString());
+ }
+
+ 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]);
+ }
+ return objects;
+ }
+
private Object toObject(final JsonValue jsonValue, final Type type) throws Exception {
if (jsonValue == null || jsonValue == JsonValue.NULL) {
return null;
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 d46217a..5802ae8 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
@@ -94,6 +94,7 @@
private AccessMode accessMode = new MethodAccessMode(false);
private boolean treatByteArrayAsBase64;
private final Map<Class<?>, Converter<?>> converters = new HashMap<Class<?>, Converter<?>>(DEFAULT_CONVERTERS);
+ private boolean supportConstructors;
public Mapper build() {
if (readerFactory == null || generatorFactory == null) {
@@ -134,6 +135,7 @@
skipNull, skipEmptyArray,
accessMode,
supportHiddenAccess,
+ supportConstructors,
treatByteArrayAsBase64);
}
@@ -240,4 +242,8 @@
return this;
}
+ public MapperBuilder setSupportConstructors(final boolean supportConstructors) {
+ this.supportConstructors = supportConstructors;
+ return this;
+ }
}
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 19a90d8..6fafcc3 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
@@ -23,6 +23,8 @@
import org.apache.johnzon.mapper.JohnzonIgnore;
import org.apache.johnzon.mapper.access.AccessMode;
+import java.beans.ConstructorProperties;
+import java.lang.annotation.Annotation;
import java.lang.reflect.Constructor;
import java.lang.reflect.Modifier;
import java.lang.reflect.ParameterizedType;
@@ -47,25 +49,65 @@
public final Map<String, Getter> getters;
public final Map<String, Setter> setters;
public final Constructor<?> constructor;
+ public final boolean constructorHasArguments;
+ public final String[] constructorParameters;
+ public final Converter<?>[] constructorParameterConverters;
+ public final Type[] constructorParameterTypes;
protected ClassMapping(final Class<?> clazz,
final Map<String, Getter> getters, final Map<String, Setter> setters,
- final boolean acceptHiddenConstructor) {
+ final boolean acceptHiddenConstructor, final boolean useConstructor) {
this.clazz = clazz;
this.getters = getters;
this.setters = setters;
- this.constructor = findConstructor(acceptHiddenConstructor);
+ this.constructor = findConstructor(acceptHiddenConstructor, useConstructor);
+
+ this.constructorHasArguments = this.constructor.getGenericParameterTypes().length > 0;
+ if (this.constructorHasArguments) {
+ this.constructorParameterTypes = this.constructor.getGenericParameterTypes();
+
+ 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];
+ 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();
+ } catch (final Exception e) {
+ throw new IllegalArgumentException(e);
+ }
+ }
+ }
+ }
+ } else {
+ this.constructorParameterTypes = null;
+ this.constructorParameters = null;
+ this.constructorParameterConverters = null;
+ }
}
- private Constructor<?> findConstructor(final boolean acceptHiddenConstructor) {
+ private Constructor<?> findConstructor(final boolean acceptHiddenConstructor, final boolean useConstructor) {
+ Constructor<?> found = null;
for (final Constructor<?> c : clazz.getDeclaredConstructors()) {
if (c.getParameterTypes().length == 0) {
if (!Modifier.isPublic(c.getModifiers()) && acceptHiddenConstructor) {
c.setAccessible(true);
}
- return c;
+ found = c;
+ if (!useConstructor) {
+ break;
+ }
+ } else if (c.getAnnotation(ConstructorProperties.class) != null) {
+ found = c;
+ break;
}
}
+ if (found != null) {
+ return found;
+ }
try {
return clazz.getConstructor();
} catch (final NoSuchMethodException e) {
@@ -129,12 +171,15 @@
protected final ConcurrentMap<Type, CollectionMapping> collections = new ConcurrentHashMap<Type, CollectionMapping>();
protected final Comparator<String> fieldOrdering;
private final boolean supportHiddenConstructors;
+ private final boolean supportConstructors;
private final AccessMode accessMode;
- public Mappings(final Comparator<String> attributeOrder, final AccessMode accessMode, final boolean supportHiddenConstructors) {
+ public Mappings(final Comparator<String> attributeOrder, final AccessMode accessMode,
+ final boolean supportHiddenConstructors, final boolean supportConstructors) {
this.fieldOrdering = attributeOrder;
this.accessMode = accessMode;
this.supportHiddenConstructors = supportHiddenConstructors;
+ this.supportConstructors = supportConstructors;
}
public <T> CollectionMapping findCollectionMapping(final ParameterizedType genericType) {
@@ -258,7 +303,7 @@
setters.put(key, new Setter(value, isPrimitive(param), param, findConverter(value), writeIgnore != null ? writeIgnore.minVersion() : -1));
}
}
- return new ClassMapping(clazz, getters, setters, supportHiddenConstructors);
+ return new ClassMapping(clazz, getters, setters, supportHiddenConstructors, supportConstructors);
}
private static Converter findConverter(final AccessMode.DecoratedType method) {
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 49b9954..4415c53 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
@@ -22,6 +22,7 @@
import org.apache.johnzon.mapper.reflection.JohnzonParameterizedType;
import org.junit.Test;
+import java.beans.ConstructorProperties;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.StringReader;
@@ -438,6 +439,18 @@
assertEquals(asList("a", "b"), value.getTheCollection());
}
+
+ @Test
+ public void constructor() {
+ final ConstructorUsage value = new MapperBuilder().setSupportConstructors(true).build()
+ .readObject(
+ new ByteArrayInputStream(
+ "{\"converted\":\"yeah\",\"value\":\"test\",\"collection\":[\"a\",\"b\"]}".getBytes()),
+ ConstructorUsage.class);
+ assertEquals("test", value.aValue);
+ assertEquals(asList("a", "b"), value.theCollection);
+ }
+
@Test
public void aliases() {
{
@@ -821,6 +834,31 @@
return theCollection;
}
}
+
+ public static class ConstructorUsage {
+ private final String foo;
+ private final String aValue;
+ private final Collection<String> theCollection;
+
+ @ConstructorProperties({ "value", "collection", "converted" })
+ public ConstructorUsage(final String aValue, final Collection<String> theCollection, @JohnzonConverter(YeahConverter.class) final String foo) {
+ this.aValue = aValue;
+ this.foo = foo;
+ this.theCollection = theCollection;
+ }
+
+ public static class YeahConverter implements Converter<String> {
+ @Override
+ public String toString(final String instance) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public String fromString(final String text) {
+ return "yeah";
+ }
+ }
+ }
/*public static class ByteArray {