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/03/15 20:26:04 UTC
incubator-johnzon git commit: JOHNZON-40 virtual object support for
our mapper
Repository: incubator-johnzon
Updated Branches:
refs/heads/master dcc3a2c2a -> 5d656c825
JOHNZON-40 virtual object support for our mapper
Project: http://git-wip-us.apache.org/repos/asf/incubator-johnzon/repo
Commit: http://git-wip-us.apache.org/repos/asf/incubator-johnzon/commit/5d656c82
Tree: http://git-wip-us.apache.org/repos/asf/incubator-johnzon/tree/5d656c82
Diff: http://git-wip-us.apache.org/repos/asf/incubator-johnzon/diff/5d656c82
Branch: refs/heads/master
Commit: 5d656c825e6351f26a93cbffcf10ef49b310ded9
Parents: dcc3a2c
Author: Romain Manni-Bucau <rm...@apache.org>
Authored: Sun Mar 15 20:25:35 2015 +0100
Committer: Romain Manni-Bucau <rm...@apache.org>
Committed: Sun Mar 15 20:25:35 2015 +0100
----------------------------------------------------------------------
.../org/apache/johnzon/mapper/Experimental.java | 33 ++
.../johnzon/mapper/JohnzonVirtualObject.java | 52 +++
.../johnzon/mapper/JohnzonVirtualObjects.java | 36 ++
.../java/org/apache/johnzon/mapper/Mapper.java | 17 +-
.../johnzon/mapper/reflection/Mappings.java | 360 +++++++++++++++++--
.../org/apache/johnzon/mapper/MapperTest.java | 49 +++
6 files changed, 510 insertions(+), 37 deletions(-)
----------------------------------------------------------------------
http://git-wip-us.apache.org/repos/asf/incubator-johnzon/blob/5d656c82/johnzon-mapper/src/main/java/org/apache/johnzon/mapper/Experimental.java
----------------------------------------------------------------------
diff --git a/johnzon-mapper/src/main/java/org/apache/johnzon/mapper/Experimental.java b/johnzon-mapper/src/main/java/org/apache/johnzon/mapper/Experimental.java
new file mode 100644
index 0000000..ba7fe01
--- /dev/null
+++ b/johnzon-mapper/src/main/java/org/apache/johnzon/mapper/Experimental.java
@@ -0,0 +1,33 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.johnzon.mapper;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.Target;
+
+import static java.lang.annotation.ElementType.TYPE;
+import static java.lang.annotation.RetentionPolicy.RUNTIME;
+
+/**
+ * Marker for experimental API.
+ */
+@Target(TYPE)
+@Retention(RUNTIME)
+public @interface Experimental {
+}
http://git-wip-us.apache.org/repos/asf/incubator-johnzon/blob/5d656c82/johnzon-mapper/src/main/java/org/apache/johnzon/mapper/JohnzonVirtualObject.java
----------------------------------------------------------------------
diff --git a/johnzon-mapper/src/main/java/org/apache/johnzon/mapper/JohnzonVirtualObject.java b/johnzon-mapper/src/main/java/org/apache/johnzon/mapper/JohnzonVirtualObject.java
new file mode 100644
index 0000000..cc256fd
--- /dev/null
+++ b/johnzon-mapper/src/main/java/org/apache/johnzon/mapper/JohnzonVirtualObject.java
@@ -0,0 +1,52 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.johnzon.mapper;
+
+import java.lang.annotation.Inherited;
+import java.lang.annotation.Retention;
+import java.lang.annotation.Target;
+
+import static java.lang.annotation.ElementType.TYPE;
+import static java.lang.annotation.RetentionPolicy.RUNTIME;
+
+/**
+ * Example: @JohnzonVirtualObject(path = {"nested", "nested-again"}, field = { "a", "b" })
+ * will generate {"nested":{"nested-again":{"a":"xxx", "b": "yyy"}}}
+ */
+@Target(TYPE)
+@Retention(RUNTIME)
+@Inherited
+@Experimental
+public @interface JohnzonVirtualObject {
+ /**
+ * @return the virtual object(s) path.
+ */
+ String[] path();
+
+ /**
+ * @return the list of fields to consider.
+ */
+ Field[] fields();
+
+ @interface Field {
+ String value();
+ boolean read() default true;
+ boolean write() default true;
+ }
+}
http://git-wip-us.apache.org/repos/asf/incubator-johnzon/blob/5d656c82/johnzon-mapper/src/main/java/org/apache/johnzon/mapper/JohnzonVirtualObjects.java
----------------------------------------------------------------------
diff --git a/johnzon-mapper/src/main/java/org/apache/johnzon/mapper/JohnzonVirtualObjects.java b/johnzon-mapper/src/main/java/org/apache/johnzon/mapper/JohnzonVirtualObjects.java
new file mode 100644
index 0000000..e539b10
--- /dev/null
+++ b/johnzon-mapper/src/main/java/org/apache/johnzon/mapper/JohnzonVirtualObjects.java
@@ -0,0 +1,36 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.johnzon.mapper;
+
+import java.lang.annotation.Inherited;
+import java.lang.annotation.Retention;
+import java.lang.annotation.Target;
+
+import static java.lang.annotation.ElementType.TYPE;
+import static java.lang.annotation.RetentionPolicy.RUNTIME;
+
+@Target(TYPE)
+@Retention(RUNTIME)
+@Inherited
+public @interface JohnzonVirtualObjects {
+ /**
+ * @return list of virtual objects for this class.
+ */
+ JohnzonVirtualObject[] value();
+}
http://git-wip-us.apache.org/repos/asf/incubator-johnzon/blob/5d656c82/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 6a92f5d..efe1868 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
@@ -24,6 +24,7 @@ import java.io.InputStream;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.Reader;
+import java.io.StringReader;
import java.io.StringWriter;
import java.io.Writer;
import java.lang.reflect.Array;
@@ -93,7 +94,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);
+ this.mappings = new Mappings(attributeOrder, accessMode, hiddenConstructorSupported, useConstructors, version);
this.skipNull = skipNull;
this.skipEmptyArray = skipEmptyArray;
this.treatByteArrayAsBase64 = treatByteArrayAsBase64;
@@ -352,11 +353,13 @@ public class Mapper {
}
}
+ final Object val = getter.converter == null ? value : getter.converter.toString(value);
+
generator = writeValue(generator, value.getClass(),
getter.primitive, getter.array,
getter.collection, getter.map,
getterEntry.getKey(),
- getter.converter == null ? value : getter.converter.toString(value));
+ val);
}
return generator;
}
@@ -453,14 +456,16 @@ public class Mapper {
return newGen;
}
+ public <T> T readObject(final String string, final Type clazz) {
+ return readObject(new StringReader(string), clazz);
+ }
+
public <T> T readObject(final Reader stream, final Type clazz) {
- final JsonReader reader = readerFactory.createReader(stream);
- return mapObject(clazz, reader);
+ return mapObject(clazz, readerFactory.createReader(stream));
}
public <T> T readObject(final InputStream stream, final Type clazz) {
- final JsonReader reader = readerFactory.createReader(stream);
- return mapObject(clazz, reader);
+ return mapObject(clazz, readerFactory.createReader(stream));
}
private <T> T mapObject(final Type clazz, final JsonReader reader) {
http://git-wip-us.apache.org/repos/asf/incubator-johnzon/blob/5d656c82/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 1401d1f..77a433a 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
@@ -21,10 +21,13 @@ package org.apache.johnzon.mapper.reflection;
import org.apache.johnzon.mapper.Converter;
import org.apache.johnzon.mapper.JohnzonConverter;
import org.apache.johnzon.mapper.JohnzonIgnore;
+import org.apache.johnzon.mapper.JohnzonVirtualObject;
+import org.apache.johnzon.mapper.JohnzonVirtualObjects;
import org.apache.johnzon.mapper.access.AccessMode;
import java.beans.ConstructorProperties;
import java.lang.annotation.Annotation;
+import java.lang.reflect.Array;
import java.lang.reflect.Constructor;
import java.lang.reflect.Modifier;
import java.lang.reflect.ParameterizedType;
@@ -34,6 +37,9 @@ import java.math.BigInteger;
import java.util.Collection;
import java.util.Comparator;
import java.util.HashMap;
+import java.util.HashSet;
+import java.util.LinkedHashMap;
+import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Queue;
@@ -43,6 +49,8 @@ import java.util.TreeMap;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
+import static java.util.Arrays.asList;
+
public class Mappings {
public static class ClassMapping {
public final Class<?> clazz;
@@ -157,29 +165,37 @@ public class Mappings {
public final Type paramType;
public final Converter<?> converter;
public final boolean primitive;
+ public final boolean array;
- public Setter(final AccessMode.Writer writer, final boolean primitive, final Type paramType, final Converter<?> converter, final int version) {
+ public Setter(final AccessMode.Writer writer, final boolean primitive, final boolean array,
+ 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;
}
}
+ 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;
private final boolean supportHiddenConstructors;
private final boolean supportConstructors;
private final AccessMode accessMode;
+ private final int version;
public Mappings(final Comparator<String> attributeOrder, final AccessMode accessMode,
- final boolean supportHiddenConstructors, final boolean supportConstructors) {
+ final boolean supportHiddenConstructors, final boolean supportConstructors,
+ final int version) {
this.fieldOrdering = attributeOrder;
this.accessMode = accessMode;
this.supportHiddenConstructors = supportHiddenConstructors;
this.supportConstructors = supportConstructors;
+ this.version = version;
}
public <T> CollectionMapping findCollectionMapping(final ParameterizedType genericType) {
@@ -270,42 +286,133 @@ public class Mappings {
}
private ClassMapping createClassMapping(final Class<?> clazz) {
- final Map<String, Getter> getters = fieldOrdering != null ?
- new TreeMap<String, Getter>(fieldOrdering) : new HashMap<String, Getter>();
- final Map<String, Setter> setters = fieldOrdering != null ?
- new TreeMap<String, Setter>(fieldOrdering) : new HashMap<String, Setter>();
-
- for (final Map.Entry<String, AccessMode.Reader> reader : accessMode.findReaders(clazz).entrySet()) {
- final AccessMode.Reader value = reader.getValue();
- final JohnzonIgnore readIgnore = value.getAnnotation(JohnzonIgnore.class);
- if (readIgnore == null || readIgnore.minVersion() >= 0) {
- final Class<?> returnType = Class.class.isInstance(value.getType()) ? Class.class.cast(value.getType()) : null;
- final ParameterizedType pt = ParameterizedType.class.isInstance(value.getType()) ? ParameterizedType.class.cast(value.getType()) : null;
- getters.put(reader.getKey(), new Getter(value, isPrimitive(returnType),
- returnType != null && returnType.isArray(),
- (pt != null && Collection.class.isAssignableFrom(Class.class.cast(pt.getRawType())))
- || (returnType != null && Collection.class.isAssignableFrom(returnType)),
- (pt != null && Map.class.isAssignableFrom(Class.class.cast(pt.getRawType())))
- || (returnType != null && Map.class.isAssignableFrom(returnType)),
- findConverter(value),
- readIgnore != null ? readIgnore.minVersion() : -1));
+ final Map<String, Getter> getters = newOrderedMap();
+ final Map<String, Setter> setters = newOrderedMap();
+
+ final Map<String, AccessMode.Reader> readers = accessMode.findReaders(clazz);
+ final Map<String, AccessMode.Writer> writers = accessMode.findWriters(clazz);
+
+ final Collection<String> virtualFields = new HashSet<String>();
+ {
+ final JohnzonVirtualObjects virtualObjects = clazz.getAnnotation(JohnzonVirtualObjects.class);
+ if (virtualObjects != null) {
+ for (final JohnzonVirtualObject virtualObject : virtualObjects.value()) {
+ handleVirtualObject(virtualFields, virtualObject, getters, setters, readers, writers);
+ }
+ }
+
+ final JohnzonVirtualObject virtualObject = clazz.getAnnotation(JohnzonVirtualObject.class);
+ if (virtualObject != null) {
+ handleVirtualObject(virtualFields, virtualObject, getters, setters, readers, writers);
}
}
- for (final Map.Entry<String, AccessMode.Writer> writer : accessMode.findWriters(clazz).entrySet()) {
- final AccessMode.Writer value = writer.getValue();
- final JohnzonIgnore writeIgnore = value.getAnnotation(JohnzonIgnore.class);
- if (writeIgnore == null || writeIgnore.minVersion() >= 0) {
- final String key = writer.getKey();
- if (key.equals("metaClass")) {
- continue;
- }
- final Type param = value.getType();
- setters.put(key, new Setter(value, isPrimitive(param), param, findConverter(value), writeIgnore != null ? writeIgnore.minVersion() : -1));
+
+ for (final Map.Entry<String, AccessMode.Reader> reader : readers.entrySet()) {
+ final String key = reader.getKey();
+ if (virtualFields.contains(key)) {
+ continue;
+ }
+ addGetterIfNeeded(getters, key, reader.getValue());
+ }
+
+ for (final Map.Entry<String, AccessMode.Writer> writer : writers.entrySet()) {
+ final String key = writer.getKey();
+ if (virtualFields.contains(key)) {
+ continue;
}
+ addSetterIfNeeded(setters, key, writer.getValue());
}
return new ClassMapping(clazz, getters, setters, supportHiddenConstructors, supportConstructors);
}
+ private <T> Map<String, T> newOrderedMap() {
+ return fieldOrdering != null ? new TreeMap<String, T>(fieldOrdering) : new HashMap<String, T>();
+ }
+
+ private void addSetterIfNeeded(final Map<String, Setter> setters,
+ final String key,
+ final AccessMode.Writer value) {
+ final JohnzonIgnore writeIgnore = value.getAnnotation(JohnzonIgnore.class);
+ if (writeIgnore == null || writeIgnore.minVersion() >= 0) {
+ if (key.equals("metaClass")) {
+ return;
+ }
+ final Type param = value.getType();
+ final Class<?> returnType = Class.class.isInstance(param) ? Class.class.cast(param) : null;
+ final Setter setter = new Setter(
+ value, isPrimitive(param), returnType != null && returnType.isArray(), param,
+ findConverter(value), writeIgnore != null ? writeIgnore.minVersion() : -1);
+ setters.put(key, setter);
+ }
+ }
+
+ private void addGetterIfNeeded(final Map<String, Getter> getters,
+ final String key,
+ final AccessMode.Reader value) {
+ final JohnzonIgnore readIgnore = value.getAnnotation(JohnzonIgnore.class);
+ if (readIgnore == null || readIgnore.minVersion() >= 0) {
+ final Class<?> returnType = Class.class.isInstance(value.getType()) ? Class.class.cast(value.getType()) : null;
+ final ParameterizedType pt = ParameterizedType.class.isInstance(value.getType()) ? ParameterizedType.class.cast(value.getType()) : null;
+ final Getter getter = new Getter(value, isPrimitive(returnType),
+ returnType != null && returnType.isArray(),
+ (pt != null && Collection.class.isAssignableFrom(Class.class.cast(pt.getRawType())))
+ || (returnType != null && Collection.class.isAssignableFrom(returnType)),
+ (pt != null && Map.class.isAssignableFrom(Class.class.cast(pt.getRawType())))
+ || (returnType != null && Map.class.isAssignableFrom(returnType)),
+ findConverter(value),
+ readIgnore != null ? readIgnore.minVersion() : -1);
+ getters.put(key, getter);
+ }
+ }
+
+ // idea is quite trivial, simulate an object with a Map<String, Object>
+ private void handleVirtualObject(final Collection<String> virtualFields,
+ final JohnzonVirtualObject o,
+ final Map<String, Getter> getters,
+ final Map<String, Setter> setters,
+ final Map<String, AccessMode.Reader> readers,
+ final Map<String, AccessMode.Writer> writers) {
+ final String[] path = o.path();
+ if (path.length < 1) {
+ throw new IllegalArgumentException("@JohnzonVirtualObject need a path");
+ }
+
+ // add them to ignored fields
+ for (final JohnzonVirtualObject.Field f : o.fields()) {
+ virtualFields.add(f.value());
+ }
+
+ // build "this" model
+ final Map<String, Getter> objectGetters = newOrderedMap();
+ final Map<String, Setter> objectSetters = newOrderedMap();
+
+ for (final JohnzonVirtualObject.Field f : o.fields()) {
+ final String name = f.value();
+ if (f.read()) {
+ final AccessMode.Reader reader = readers.get(name);
+ if (reader != null) {
+ addGetterIfNeeded(objectGetters, name, reader);
+ }
+ }
+ if (f.write()) {
+ final AccessMode.Writer writer = writers.get(name);
+ if (writer != null) {
+ addSetterIfNeeded(objectSetters, name, writer);
+ }
+ }
+ }
+
+ final String key = path[0];
+
+ final Getter getter = getters.get(key);
+ final MapBuilderReader newReader = new MapBuilderReader(objectGetters, path, version);
+ getters.put(key, new Getter(getter == null ? newReader : new CompositeReader(getter.reader, newReader), false, false, false, true, null, -1));
+
+ final Setter newSetter = setters.get(key);
+ final MapUnwrapperWriter newWriter = new MapUnwrapperWriter(objectSetters, path);
+ setters.put(key, new Setter(newSetter == null ? newWriter : new CompositeWriter(newSetter.writer, newWriter), false, false, VIRTUAL_TYPE, null, -1));
+ }
+
private static Converter findConverter(final AccessMode.DecoratedType method) {
Converter converter = null;
if (method.getAnnotation(JohnzonConverter.class) != null) {
@@ -317,4 +424,195 @@ public class Mappings {
}
return converter;
}
+
+ private static class MapBuilderReader implements AccessMode.Reader {
+ private final Map<String, Getter> getters;
+ private final Map<String, Object> template;
+ private final String[] paths;
+ private final int version;
+
+ public MapBuilderReader(final Map<String, Getter> objectGetters, final String[] paths, final int version) {
+ this.getters = objectGetters;
+ this.paths = paths;
+ this.template = new LinkedHashMap<String, Object>();
+ this.version = version;
+
+ Map<String, Object> last = this.template;
+ for (int i = 1; i < paths.length; i++) {
+ final Map<String, Object> newLast = new LinkedHashMap<String, Object>();
+ last.put(paths[i], newLast);
+ last = newLast;
+ }
+ }
+
+ @Override
+ public Object read(final Object instance) {
+ final Map<String, Object> map = new LinkedHashMap<String, Object>(template);
+ Map<String, Object> nested = map;
+ for (int i = 1; i < paths.length; i++) {
+ nested = Map.class.cast(nested.get(paths[i]));
+ }
+ for (final Map.Entry<String, Getter> g : getters.entrySet()) {
+ final Mappings.Getter getter = g.getValue();
+ final Object value = getter.reader.read(instance);
+ final Object val = value == null || getter.converter == null ? value : getter.converter.toString(value);
+ if (val == null) {
+ continue;
+ }
+ if (getter.version >= 0 && version >= getter.version) {
+ continue;
+ }
+
+ nested.put(g.getKey(), val);
+ }
+ return map;
+ }
+
+ @Override
+ public Type getType() {
+ return VIRTUAL_TYPE;
+ }
+
+ @Override
+ public <T extends Annotation> T getAnnotation(final Class<T> clazz) {
+ throw new UnsupportedOperationException("getAnnotation shouldn't get called for virtual fields");
+ }
+ }
+
+ private static class MapUnwrapperWriter implements AccessMode.Writer {
+ private final Map<String, Setter> writers;
+ private final Map<String, Class<?>> componentTypes;
+ private final String[] paths;
+
+ public MapUnwrapperWriter(final Map<String, Setter> writers, final String[] paths) {
+ this.writers = writers;
+ this.paths = paths;
+ this.componentTypes = new HashMap<String, Class<?>>();
+
+ for (final Map.Entry<String, Setter> setter : writers.entrySet()) {
+ if (setter.getValue().array) {
+ componentTypes.put(setter.getKey(), Class.class.cast(setter.getValue().paramType).getComponentType());
+ }
+ }
+ }
+
+ @Override
+ public void write(final Object instance, final Object value) {
+ Map<String, Object> nested = null;
+ for (final String path : paths) {
+ nested = Map.class.cast(nested == null ? value : nested.get(path));
+ if (nested == null) {
+ return;
+ }
+ }
+
+ for (final Map.Entry<String, Setter> setter : writers.entrySet()) {
+ final Setter setterValue = setter.getValue();
+ final String key = setter.getKey();
+ final Object rawValue = nested.get(key);
+ Object val = value == null || setterValue.converter == null ?
+ rawValue : Converter.class.cast(setterValue.converter).toString(rawValue);
+ if (val == null) {
+ continue;
+ }
+
+ if (setterValue.array && Collection.class.isInstance(val)) {
+ final Collection<?> collection = Collection.class.cast(val);
+ final Object[] array = (Object[]) Array.newInstance(componentTypes.get(key), collection.size());
+ val = collection.toArray(array);
+ }
+
+ final AccessMode.Writer setterMethod = setterValue.writer;
+ setterMethod.write(instance, val);
+ }
+ }
+
+ @Override
+ public Type getType() {
+ return VIRTUAL_TYPE;
+ }
+
+ @Override
+ public <T extends Annotation> T getAnnotation(final Class<T> clazz) {
+ throw new UnsupportedOperationException("getAnnotation shouldn't get called for virtual fields");
+ }
+ }
+
+ private static class CompositeReader implements AccessMode.Reader {
+ private final AccessMode.Reader[] delegates;
+
+ public CompositeReader(final AccessMode.Reader... delegates) {
+ final Collection<AccessMode.Reader> all = new LinkedList<AccessMode.Reader>();
+ for (final AccessMode.Reader r : delegates) {
+ if (CompositeReader.class.isInstance(r)) {
+ all.addAll(asList(CompositeReader.class.cast(r).delegates));
+ } else {
+ all.add(r);
+ }
+ }
+ this.delegates = all.toArray(new AccessMode.Reader[all.size()]);
+ }
+
+ @Override
+ public Object read(final Object instance) {
+ final Map<String, Object> map = new LinkedHashMap<String, Object>();
+ for (final AccessMode.Reader reader : delegates) {
+ final Map<String, Object> readerMap = (Map<String, Object>) reader.read(instance);
+ for (final Map.Entry<String, Object> entry :readerMap.entrySet()) {
+ final Object o = map.get(entry.getKey());
+ if (o == null) {
+ map.put(entry.getKey(), entry.getValue());
+ } else if (Map.class.isInstance(o)) {
+ // TODO
+ } else {
+ throw new IllegalStateException(entry.getKey() + " is ambiguous");
+ }
+ }
+ }
+ return map;
+ }
+
+ @Override
+ public Type getType() {
+ return VIRTUAL_TYPE;
+ }
+
+ @Override
+ public <T extends Annotation> T getAnnotation(final Class<T> clazz) {
+ throw new UnsupportedOperationException("getAnnotation shouldn't get called for virtual fields");
+ }
+ }
+
+ private static class CompositeWriter implements AccessMode.Writer {
+ private final AccessMode.Writer[] delegates;
+
+ public CompositeWriter(final AccessMode.Writer... writers) {
+ final Collection<AccessMode.Writer> all = new LinkedList<AccessMode.Writer>();
+ for (final AccessMode.Writer r : writers) {
+ if (CompositeWriter.class.isInstance(r)) {
+ all.addAll(asList(CompositeWriter.class.cast(r).delegates));
+ } else {
+ all.add(r);
+ }
+ }
+ this.delegates = all.toArray(new AccessMode.Writer[all.size()]);
+ }
+
+ @Override
+ public void write(final Object instance, final Object value) {
+ for (final AccessMode.Writer w : delegates) {
+ w.write(instance, value);
+ }
+ }
+
+ @Override
+ public Type getType() {
+ return VIRTUAL_TYPE;
+ }
+
+ @Override
+ public <T extends Annotation> T getAnnotation(final Class<T> clazz) {
+ throw new UnsupportedOperationException("getAnnotation shouldn't get called for virtual fields");
+ }
+ }
}
http://git-wip-us.apache.org/repos/asf/incubator-johnzon/blob/5d656c82/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 4415c53..c39a9d5 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
@@ -18,6 +18,7 @@
*/
package org.apache.johnzon.mapper;
+import org.apache.johnzon.mapper.access.FieldAccessMode;
import org.apache.johnzon.mapper.reflection.JohnzonCollectionType;
import org.apache.johnzon.mapper.reflection.JohnzonParameterizedType;
import org.junit.Test;
@@ -37,6 +38,7 @@ import java.util.List;
import java.util.Map;
import static java.util.Arrays.asList;
+import static org.junit.Assert.assertArrayEquals;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
@@ -475,6 +477,31 @@ public class MapperTest {
}
}
+ @Test
+ public void fakedObject() {
+ final ChildOfFakedObject source = new ChildOfFakedObject();
+ source.a = 1;
+ source.b = 2;
+ source.c = new String[] { "3", "4" };
+ source.children = asList("5", "6");
+
+ final Mapper mapper = new MapperBuilder().setAttributeOrder(new Comparator<String>() {
+ @Override
+ public int compare(final String o1, final String o2) {
+ return o1.compareTo(o2);
+ }
+ }).setAccessMode(new FieldAccessMode()).build();
+
+ final String asString = mapper.writeObjectAsString(source);
+ assertEquals("{\"children\":[\"5\",\"6\"],\"nested\":{\"b\":2,\"sub\":{\"a\":1,\"c\":[\"3\",\"4\"]}}}", asString);
+
+ final ChildOfFakedObject childOfFakedObject = mapper.readObject(asString, ChildOfFakedObject.class);
+ assertEquals(source.a, childOfFakedObject.a);
+ assertEquals(source.b, childOfFakedObject.b);
+ assertArrayEquals(source.c, childOfFakedObject.c);
+ assertEquals(source.children, childOfFakedObject.children);
+ }
+
public static class NanHolder {
private Double nan = Double.NaN;
@@ -859,6 +886,28 @@ public class MapperTest {
}
}
}
+
+ public static class FakeNestedObject {
+ protected int a;
+ protected int b;
+ protected String[] c;
+ }
+
+ @JohnzonVirtualObjects({
+ @JohnzonVirtualObject(
+ path = "nested",
+ fields = @JohnzonVirtualObject.Field("b")
+ ),
+ @JohnzonVirtualObject(
+ path = { "nested", "sub" },
+ fields = {
+ @JohnzonVirtualObject.Field("a"), @JohnzonVirtualObject.Field("c")
+ }
+ )
+ })
+ public static class ChildOfFakedObject extends FakeNestedObject {
+ protected List<String> children;
+ }
/*public static class ByteArray {
Fwd: incubator-johnzon git commit: JOHNZON-40 virtual object support
for our mapper
Posted by Romain Manni-Bucau <rm...@gmail.com>.
Hi guys,
started to try to tackle "views". My idea is to have 2 features:
- the one of this commit (ie expand an object without defining any other
class)
- (TODO) support a explicit view. I'm not yet sure for this last one (ie
which technical solution: 1) interface model == filtering, 2) another
object taking "this" as constructor param and implementing as desired the
logic, 3) other).
I added with this commit @Experimental to mark it "under discuss", allow us
to push quickly feature and discuss them from code - I think for us it
would be better than discussing then pushing while we are not that big.
Any feedback on these 2 things (@Experimental and @JohnzonVirtualObject)
are very welcomed. Don't hesitate to hack as well on it.
Side note: I did my best to support merge of nested object, ie if 2 virtual
objects conflicts cause they have a common path one will not overwrite the
other.
Romain Manni-Bucau
@rmannibucau <https://twitter.com/rmannibucau> | Blog
<http://rmannibucau.wordpress.com> | Github <https://github.com/rmannibucau> |
LinkedIn <https://www.linkedin.com/in/rmannibucau> | Tomitriber
<http://www.tomitribe.com>
---------- Forwarded message ----------
From: <rm...@apache.org>
Date: 2015-03-15 20:26 GMT+01:00
Subject: incubator-johnzon git commit: JOHNZON-40 virtual object support
for our mapper
To: commits@johnzon.incubator.apache.org
Repository: incubator-johnzon
Updated Branches:
refs/heads/master dcc3a2c2a -> 5d656c825
JOHNZON-40 virtual object support for our mapper
Project: http://git-wip-us.apache.org/repos/asf/incubator-johnzon/repo
Commit:
http://git-wip-us.apache.org/repos/asf/incubator-johnzon/commit/5d656c82
Tree: http://git-wip-us.apache.org/repos/asf/incubator-johnzon/tree/5d656c82
Diff: http://git-wip-us.apache.org/repos/asf/incubator-johnzon/diff/5d656c82
Branch: refs/heads/master
Commit: 5d656c825e6351f26a93cbffcf10ef49b310ded9
Parents: dcc3a2c
Author: Romain Manni-Bucau <rm...@apache.org>
Authored: Sun Mar 15 20:25:35 2015 +0100
Committer: Romain Manni-Bucau <rm...@apache.org>
Committed: Sun Mar 15 20:25:35 2015 +0100
----------------------------------------------------------------------
.../org/apache/johnzon/mapper/Experimental.java | 33 ++
.../johnzon/mapper/JohnzonVirtualObject.java | 52 +++
.../johnzon/mapper/JohnzonVirtualObjects.java | 36 ++
.../java/org/apache/johnzon/mapper/Mapper.java | 17 +-
.../johnzon/mapper/reflection/Mappings.java | 360 +++++++++++++++++--
.../org/apache/johnzon/mapper/MapperTest.java | 49 +++
6 files changed, 510 insertions(+), 37 deletions(-)
----------------------------------------------------------------------
http://git-wip-us.apache.org/repos/asf/incubator-johnzon/blob/5d656c82/johnzon-mapper/src/main/java/org/apache/johnzon/mapper/Experimental.java
----------------------------------------------------------------------
diff --git
a/johnzon-mapper/src/main/java/org/apache/johnzon/mapper/Experimental.java
b/johnzon-mapper/src/main/java/org/apache/johnzon/mapper/Experimental.java
new file mode 100644
index 0000000..ba7fe01
--- /dev/null
+++
b/johnzon-mapper/src/main/java/org/apache/johnzon/mapper/Experimental.java
@@ -0,0 +1,33 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.johnzon.mapper;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.Target;
+
+import static java.lang.annotation.ElementType.TYPE;
+import static java.lang.annotation.RetentionPolicy.RUNTIME;
+
+/**
+ * Marker for experimental API.
+ */
+@Target(TYPE)
+@Retention(RUNTIME)
+public @interface Experimental {
+}
http://git-wip-us.apache.org/repos/asf/incubator-johnzon/blob/5d656c82/johnzon-mapper/src/main/java/org/apache/johnzon/mapper/JohnzonVirtualObject.java
----------------------------------------------------------------------
diff --git
a/johnzon-mapper/src/main/java/org/apache/johnzon/mapper/JohnzonVirtualObject.java
b/johnzon-mapper/src/main/java/org/apache/johnzon/mapper/JohnzonVirtualObject.java
new file mode 100644
index 0000000..cc256fd
--- /dev/null
+++
b/johnzon-mapper/src/main/java/org/apache/johnzon/mapper/JohnzonVirtualObject.java
@@ -0,0 +1,52 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.johnzon.mapper;
+
+import java.lang.annotation.Inherited;
+import java.lang.annotation.Retention;
+import java.lang.annotation.Target;
+
+import static java.lang.annotation.ElementType.TYPE;
+import static java.lang.annotation.RetentionPolicy.RUNTIME;
+
+/**
+ * Example: @JohnzonVirtualObject(path = {"nested", "nested-again"},
field = { "a", "b" })
+ * will generate {"nested":{"nested-again":{"a":"xxx", "b": "yyy"}}}
+ */
+@Target(TYPE)
+@Retention(RUNTIME)
+@Inherited
+@Experimental
+public @interface JohnzonVirtualObject {
+ /**
+ * @return the virtual object(s) path.
+ */
+ String[] path();
+
+ /**
+ * @return the list of fields to consider.
+ */
+ Field[] fields();
+
+ @interface Field {
+ String value();
+ boolean read() default true;
+ boolean write() default true;
+ }
+}
http://git-wip-us.apache.org/repos/asf/incubator-johnzon/blob/5d656c82/johnzon-mapper/src/main/java/org/apache/johnzon/mapper/JohnzonVirtualObjects.java
----------------------------------------------------------------------
diff --git
a/johnzon-mapper/src/main/java/org/apache/johnzon/mapper/JohnzonVirtualObjects.java
b/johnzon-mapper/src/main/java/org/apache/johnzon/mapper/JohnzonVirtualObjects.java
new file mode 100644
index 0000000..e539b10
--- /dev/null
+++
b/johnzon-mapper/src/main/java/org/apache/johnzon/mapper/JohnzonVirtualObjects.java
@@ -0,0 +1,36 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.johnzon.mapper;
+
+import java.lang.annotation.Inherited;
+import java.lang.annotation.Retention;
+import java.lang.annotation.Target;
+
+import static java.lang.annotation.ElementType.TYPE;
+import static java.lang.annotation.RetentionPolicy.RUNTIME;
+
+@Target(TYPE)
+@Retention(RUNTIME)
+@Inherited
+public @interface JohnzonVirtualObjects {
+ /**
+ * @return list of virtual objects for this class.
+ */
+ JohnzonVirtualObject[] value();
+}
http://git-wip-us.apache.org/repos/asf/incubator-johnzon/blob/5d656c82/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 6a92f5d..efe1868 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
@@ -24,6 +24,7 @@ import java.io.InputStream;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.Reader;
+import java.io.StringReader;
import java.io.StringWriter;
import java.io.Writer;
import java.lang.reflect.Array;
@@ -93,7 +94,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);
+ this.mappings = new Mappings(attributeOrder, accessMode,
hiddenConstructorSupported, useConstructors, version);
this.skipNull = skipNull;
this.skipEmptyArray = skipEmptyArray;
this.treatByteArrayAsBase64 = treatByteArrayAsBase64;
@@ -352,11 +353,13 @@ public class Mapper {
}
}
+ final Object val = getter.converter == null ? value :
getter.converter.toString(value);
+
generator = writeValue(generator, value.getClass(),
getter.primitive, getter.array,
getter.collection, getter.map,
getterEntry.getKey(),
- getter.converter == null ? value :
getter.converter.toString(value));
+ val);
}
return generator;
}
@@ -453,14 +456,16 @@ public class Mapper {
return newGen;
}
+ public <T> T readObject(final String string, final Type clazz) {
+ return readObject(new StringReader(string), clazz);
+ }
+
public <T> T readObject(final Reader stream, final Type clazz) {
- final JsonReader reader = readerFactory.createReader(stream);
- return mapObject(clazz, reader);
+ return mapObject(clazz, readerFactory.createReader(stream));
}
public <T> T readObject(final InputStream stream, final Type clazz) {
- final JsonReader reader = readerFactory.createReader(stream);
- return mapObject(clazz, reader);
+ return mapObject(clazz, readerFactory.createReader(stream));
}
private <T> T mapObject(final Type clazz, final JsonReader reader) {
http://git-wip-us.apache.org/repos/asf/incubator-johnzon/blob/5d656c82/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 1401d1f..77a433a 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
@@ -21,10 +21,13 @@ package org.apache.johnzon.mapper.reflection;
import org.apache.johnzon.mapper.Converter;
import org.apache.johnzon.mapper.JohnzonConverter;
import org.apache.johnzon.mapper.JohnzonIgnore;
+import org.apache.johnzon.mapper.JohnzonVirtualObject;
+import org.apache.johnzon.mapper.JohnzonVirtualObjects;
import org.apache.johnzon.mapper.access.AccessMode;
import java.beans.ConstructorProperties;
import java.lang.annotation.Annotation;
+import java.lang.reflect.Array;
import java.lang.reflect.Constructor;
import java.lang.reflect.Modifier;
import java.lang.reflect.ParameterizedType;
@@ -34,6 +37,9 @@ import java.math.BigInteger;
import java.util.Collection;
import java.util.Comparator;
import java.util.HashMap;
+import java.util.HashSet;
+import java.util.LinkedHashMap;
+import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Queue;
@@ -43,6 +49,8 @@ import java.util.TreeMap;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
+import static java.util.Arrays.asList;
+
public class Mappings {
public static class ClassMapping {
public final Class<?> clazz;
@@ -157,29 +165,37 @@ public class Mappings {
public final Type paramType;
public final Converter<?> converter;
public final boolean primitive;
+ public final boolean array;
- public Setter(final AccessMode.Writer writer, final boolean
primitive, final Type paramType, final Converter<?> converter, final int
version) {
+ public Setter(final AccessMode.Writer writer, final boolean
primitive, final boolean array,
+ 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;
}
}
+ 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;
private final boolean supportHiddenConstructors;
private final boolean supportConstructors;
private final AccessMode accessMode;
+ private final int version;
public Mappings(final Comparator<String> attributeOrder, final
AccessMode accessMode,
- final boolean supportHiddenConstructors, final boolean
supportConstructors) {
+ final boolean supportHiddenConstructors, final boolean
supportConstructors,
+ final int version) {
this.fieldOrdering = attributeOrder;
this.accessMode = accessMode;
this.supportHiddenConstructors = supportHiddenConstructors;
this.supportConstructors = supportConstructors;
+ this.version = version;
}
public <T> CollectionMapping findCollectionMapping(final
ParameterizedType genericType) {
@@ -270,42 +286,133 @@ public class Mappings {
}
private ClassMapping createClassMapping(final Class<?> clazz) {
- final Map<String, Getter> getters = fieldOrdering != null ?
- new TreeMap<String, Getter>(fieldOrdering) : new
HashMap<String, Getter>();
- final Map<String, Setter> setters = fieldOrdering != null ?
- new TreeMap<String, Setter>(fieldOrdering) : new
HashMap<String, Setter>();
-
- for (final Map.Entry<String, AccessMode.Reader> reader :
accessMode.findReaders(clazz).entrySet()) {
- final AccessMode.Reader value = reader.getValue();
- final JohnzonIgnore readIgnore =
value.getAnnotation(JohnzonIgnore.class);
- if (readIgnore == null || readIgnore.minVersion() >= 0) {
- final Class<?> returnType =
Class.class.isInstance(value.getType()) ? Class.class.cast(value.getType())
: null;
- final ParameterizedType pt =
ParameterizedType.class.isInstance(value.getType()) ?
ParameterizedType.class.cast(value.getType()) : null;
- getters.put(reader.getKey(), new Getter(value,
isPrimitive(returnType),
- returnType != null && returnType.isArray(),
- (pt != null &&
Collection.class.isAssignableFrom(Class.class.cast(pt.getRawType())))
- || (returnType != null &&
Collection.class.isAssignableFrom(returnType)),
- (pt != null &&
Map.class.isAssignableFrom(Class.class.cast(pt.getRawType())))
- || (returnType != null &&
Map.class.isAssignableFrom(returnType)),
- findConverter(value),
- readIgnore != null ? readIgnore.minVersion() :
-1));
+ final Map<String, Getter> getters = newOrderedMap();
+ final Map<String, Setter> setters = newOrderedMap();
+
+ final Map<String, AccessMode.Reader> readers =
accessMode.findReaders(clazz);
+ final Map<String, AccessMode.Writer> writers =
accessMode.findWriters(clazz);
+
+ final Collection<String> virtualFields = new HashSet<String>();
+ {
+ final JohnzonVirtualObjects virtualObjects =
clazz.getAnnotation(JohnzonVirtualObjects.class);
+ if (virtualObjects != null) {
+ for (final JohnzonVirtualObject virtualObject :
virtualObjects.value()) {
+ handleVirtualObject(virtualFields, virtualObject,
getters, setters, readers, writers);
+ }
+ }
+
+ final JohnzonVirtualObject virtualObject =
clazz.getAnnotation(JohnzonVirtualObject.class);
+ if (virtualObject != null) {
+ handleVirtualObject(virtualFields, virtualObject, getters,
setters, readers, writers);
}
}
- for (final Map.Entry<String, AccessMode.Writer> writer :
accessMode.findWriters(clazz).entrySet()) {
- final AccessMode.Writer value = writer.getValue();
- final JohnzonIgnore writeIgnore =
value.getAnnotation(JohnzonIgnore.class);
- if (writeIgnore == null || writeIgnore.minVersion() >= 0) {
- final String key = writer.getKey();
- if (key.equals("metaClass")) {
- continue;
- }
- final Type param = value.getType();
- setters.put(key, new Setter(value, isPrimitive(param),
param, findConverter(value), writeIgnore != null ? writeIgnore.minVersion()
: -1));
+
+ for (final Map.Entry<String, AccessMode.Reader> reader :
readers.entrySet()) {
+ final String key = reader.getKey();
+ if (virtualFields.contains(key)) {
+ continue;
+ }
+ addGetterIfNeeded(getters, key, reader.getValue());
+ }
+
+ for (final Map.Entry<String, AccessMode.Writer> writer :
writers.entrySet()) {
+ final String key = writer.getKey();
+ if (virtualFields.contains(key)) {
+ continue;
}
+ addSetterIfNeeded(setters, key, writer.getValue());
}
return new ClassMapping(clazz, getters, setters,
supportHiddenConstructors, supportConstructors);
}
+ private <T> Map<String, T> newOrderedMap() {
+ return fieldOrdering != null ? new TreeMap<String,
T>(fieldOrdering) : new HashMap<String, T>();
+ }
+
+ private void addSetterIfNeeded(final Map<String, Setter> setters,
+ final String key,
+ final AccessMode.Writer value) {
+ final JohnzonIgnore writeIgnore =
value.getAnnotation(JohnzonIgnore.class);
+ if (writeIgnore == null || writeIgnore.minVersion() >= 0) {
+ if (key.equals("metaClass")) {
+ return;
+ }
+ final Type param = value.getType();
+ final Class<?> returnType = Class.class.isInstance(param) ?
Class.class.cast(param) : null;
+ final Setter setter = new Setter(
+ value, isPrimitive(param), returnType != null &&
returnType.isArray(), param,
+ findConverter(value), writeIgnore != null ?
writeIgnore.minVersion() : -1);
+ setters.put(key, setter);
+ }
+ }
+
+ private void addGetterIfNeeded(final Map<String, Getter> getters,
+ final String key,
+ final AccessMode.Reader value) {
+ final JohnzonIgnore readIgnore =
value.getAnnotation(JohnzonIgnore.class);
+ if (readIgnore == null || readIgnore.minVersion() >= 0) {
+ final Class<?> returnType =
Class.class.isInstance(value.getType()) ? Class.class.cast(value.getType())
: null;
+ final ParameterizedType pt =
ParameterizedType.class.isInstance(value.getType()) ?
ParameterizedType.class.cast(value.getType()) : null;
+ final Getter getter = new Getter(value,
isPrimitive(returnType),
+ returnType != null && returnType.isArray(),
+ (pt != null &&
Collection.class.isAssignableFrom(Class.class.cast(pt.getRawType())))
+ || (returnType != null &&
Collection.class.isAssignableFrom(returnType)),
+ (pt != null &&
Map.class.isAssignableFrom(Class.class.cast(pt.getRawType())))
+ || (returnType != null &&
Map.class.isAssignableFrom(returnType)),
+ findConverter(value),
+ readIgnore != null ? readIgnore.minVersion() : -1);
+ getters.put(key, getter);
+ }
+ }
+
+ // idea is quite trivial, simulate an object with a Map<String, Object>
+ private void handleVirtualObject(final Collection<String>
virtualFields,
+ final JohnzonVirtualObject o,
+ final Map<String, Getter> getters,
+ final Map<String, Setter> setters,
+ final Map<String, AccessMode.Reader>
readers,
+ final Map<String, AccessMode.Writer>
writers) {
+ final String[] path = o.path();
+ if (path.length < 1) {
+ throw new IllegalArgumentException("@JohnzonVirtualObject need
a path");
+ }
+
+ // add them to ignored fields
+ for (final JohnzonVirtualObject.Field f : o.fields()) {
+ virtualFields.add(f.value());
+ }
+
+ // build "this" model
+ final Map<String, Getter> objectGetters = newOrderedMap();
+ final Map<String, Setter> objectSetters = newOrderedMap();
+
+ for (final JohnzonVirtualObject.Field f : o.fields()) {
+ final String name = f.value();
+ if (f.read()) {
+ final AccessMode.Reader reader = readers.get(name);
+ if (reader != null) {
+ addGetterIfNeeded(objectGetters, name, reader);
+ }
+ }
+ if (f.write()) {
+ final AccessMode.Writer writer = writers.get(name);
+ if (writer != null) {
+ addSetterIfNeeded(objectSetters, name, writer);
+ }
+ }
+ }
+
+ final String key = path[0];
+
+ final Getter getter = getters.get(key);
+ final MapBuilderReader newReader = new
MapBuilderReader(objectGetters, path, version);
+ getters.put(key, new Getter(getter == null ? newReader : new
CompositeReader(getter.reader, newReader), false, false, false, true, null,
-1));
+
+ final Setter newSetter = setters.get(key);
+ final MapUnwrapperWriter newWriter = new
MapUnwrapperWriter(objectSetters, path);
+ setters.put(key, new Setter(newSetter == null ? newWriter : new
CompositeWriter(newSetter.writer, newWriter), false, false, VIRTUAL_TYPE,
null, -1));
+ }
+
private static Converter findConverter(final AccessMode.DecoratedType
method) {
Converter converter = null;
if (method.getAnnotation(JohnzonConverter.class) != null) {
@@ -317,4 +424,195 @@ public class Mappings {
}
return converter;
}
+
+ private static class MapBuilderReader implements AccessMode.Reader {
+ private final Map<String, Getter> getters;
+ private final Map<String, Object> template;
+ private final String[] paths;
+ private final int version;
+
+ public MapBuilderReader(final Map<String, Getter> objectGetters,
final String[] paths, final int version) {
+ this.getters = objectGetters;
+ this.paths = paths;
+ this.template = new LinkedHashMap<String, Object>();
+ this.version = version;
+
+ Map<String, Object> last = this.template;
+ for (int i = 1; i < paths.length; i++) {
+ final Map<String, Object> newLast = new
LinkedHashMap<String, Object>();
+ last.put(paths[i], newLast);
+ last = newLast;
+ }
+ }
+
+ @Override
+ public Object read(final Object instance) {
+ final Map<String, Object> map = new LinkedHashMap<String,
Object>(template);
+ Map<String, Object> nested = map;
+ for (int i = 1; i < paths.length; i++) {
+ nested = Map.class.cast(nested.get(paths[i]));
+ }
+ for (final Map.Entry<String, Getter> g : getters.entrySet()) {
+ final Mappings.Getter getter = g.getValue();
+ final Object value = getter.reader.read(instance);
+ final Object val = value == null || getter.converter ==
null ? value : getter.converter.toString(value);
+ if (val == null) {
+ continue;
+ }
+ if (getter.version >= 0 && version >= getter.version) {
+ continue;
+ }
+
+ nested.put(g.getKey(), val);
+ }
+ return map;
+ }
+
+ @Override
+ public Type getType() {
+ return VIRTUAL_TYPE;
+ }
+
+ @Override
+ public <T extends Annotation> T getAnnotation(final Class<T>
clazz) {
+ throw new UnsupportedOperationException("getAnnotation
shouldn't get called for virtual fields");
+ }
+ }
+
+ private static class MapUnwrapperWriter implements AccessMode.Writer {
+ private final Map<String, Setter> writers;
+ private final Map<String, Class<?>> componentTypes;
+ private final String[] paths;
+
+ public MapUnwrapperWriter(final Map<String, Setter> writers, final
String[] paths) {
+ this.writers = writers;
+ this.paths = paths;
+ this.componentTypes = new HashMap<String, Class<?>>();
+
+ for (final Map.Entry<String, Setter> setter :
writers.entrySet()) {
+ if (setter.getValue().array) {
+ componentTypes.put(setter.getKey(),
Class.class.cast(setter.getValue().paramType).getComponentType());
+ }
+ }
+ }
+
+ @Override
+ public void write(final Object instance, final Object value) {
+ Map<String, Object> nested = null;
+ for (final String path : paths) {
+ nested = Map.class.cast(nested == null ? value :
nested.get(path));
+ if (nested == null) {
+ return;
+ }
+ }
+
+ for (final Map.Entry<String, Setter> setter :
writers.entrySet()) {
+ final Setter setterValue = setter.getValue();
+ final String key = setter.getKey();
+ final Object rawValue = nested.get(key);
+ Object val = value == null || setterValue.converter ==
null ?
+ rawValue :
Converter.class.cast(setterValue.converter).toString(rawValue);
+ if (val == null) {
+ continue;
+ }
+
+ if (setterValue.array && Collection.class.isInstance(val))
{
+ final Collection<?> collection =
Collection.class.cast(val);
+ final Object[] array = (Object[])
Array.newInstance(componentTypes.get(key), collection.size());
+ val = collection.toArray(array);
+ }
+
+ final AccessMode.Writer setterMethod = setterValue.writer;
+ setterMethod.write(instance, val);
+ }
+ }
+
+ @Override
+ public Type getType() {
+ return VIRTUAL_TYPE;
+ }
+
+ @Override
+ public <T extends Annotation> T getAnnotation(final Class<T>
clazz) {
+ throw new UnsupportedOperationException("getAnnotation
shouldn't get called for virtual fields");
+ }
+ }
+
+ private static class CompositeReader implements AccessMode.Reader {
+ private final AccessMode.Reader[] delegates;
+
+ public CompositeReader(final AccessMode.Reader... delegates) {
+ final Collection<AccessMode.Reader> all = new
LinkedList<AccessMode.Reader>();
+ for (final AccessMode.Reader r : delegates) {
+ if (CompositeReader.class.isInstance(r)) {
+
all.addAll(asList(CompositeReader.class.cast(r).delegates));
+ } else {
+ all.add(r);
+ }
+ }
+ this.delegates = all.toArray(new
AccessMode.Reader[all.size()]);
+ }
+
+ @Override
+ public Object read(final Object instance) {
+ final Map<String, Object> map = new LinkedHashMap<String,
Object>();
+ for (final AccessMode.Reader reader : delegates) {
+ final Map<String, Object> readerMap = (Map<String,
Object>) reader.read(instance);
+ for (final Map.Entry<String, Object> entry
:readerMap.entrySet()) {
+ final Object o = map.get(entry.getKey());
+ if (o == null) {
+ map.put(entry.getKey(), entry.getValue());
+ } else if (Map.class.isInstance(o)) {
+ // TODO
+ } else {
+ throw new IllegalStateException(entry.getKey() + "
is ambiguous");
+ }
+ }
+ }
+ return map;
+ }
+
+ @Override
+ public Type getType() {
+ return VIRTUAL_TYPE;
+ }
+
+ @Override
+ public <T extends Annotation> T getAnnotation(final Class<T>
clazz) {
+ throw new UnsupportedOperationException("getAnnotation
shouldn't get called for virtual fields");
+ }
+ }
+
+ private static class CompositeWriter implements AccessMode.Writer {
+ private final AccessMode.Writer[] delegates;
+
+ public CompositeWriter(final AccessMode.Writer... writers) {
+ final Collection<AccessMode.Writer> all = new
LinkedList<AccessMode.Writer>();
+ for (final AccessMode.Writer r : writers) {
+ if (CompositeWriter.class.isInstance(r)) {
+
all.addAll(asList(CompositeWriter.class.cast(r).delegates));
+ } else {
+ all.add(r);
+ }
+ }
+ this.delegates = all.toArray(new
AccessMode.Writer[all.size()]);
+ }
+
+ @Override
+ public void write(final Object instance, final Object value) {
+ for (final AccessMode.Writer w : delegates) {
+ w.write(instance, value);
+ }
+ }
+
+ @Override
+ public Type getType() {
+ return VIRTUAL_TYPE;
+ }
+
+ @Override
+ public <T extends Annotation> T getAnnotation(final Class<T>
clazz) {
+ throw new UnsupportedOperationException("getAnnotation
shouldn't get called for virtual fields");
+ }
+ }
}
http://git-wip-us.apache.org/repos/asf/incubator-johnzon/blob/5d656c82/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 4415c53..c39a9d5 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
@@ -18,6 +18,7 @@
*/
package org.apache.johnzon.mapper;
+import org.apache.johnzon.mapper.access.FieldAccessMode;
import org.apache.johnzon.mapper.reflection.JohnzonCollectionType;
import org.apache.johnzon.mapper.reflection.JohnzonParameterizedType;
import org.junit.Test;
@@ -37,6 +38,7 @@ import java.util.List;
import java.util.Map;
import static java.util.Arrays.asList;
+import static org.junit.Assert.assertArrayEquals;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
@@ -475,6 +477,31 @@ public class MapperTest {
}
}
+ @Test
+ public void fakedObject() {
+ final ChildOfFakedObject source = new ChildOfFakedObject();
+ source.a = 1;
+ source.b = 2;
+ source.c = new String[] { "3", "4" };
+ source.children = asList("5", "6");
+
+ final Mapper mapper = new MapperBuilder().setAttributeOrder(new
Comparator<String>() {
+ @Override
+ public int compare(final String o1, final String o2) {
+ return o1.compareTo(o2);
+ }
+ }).setAccessMode(new FieldAccessMode()).build();
+
+ final String asString = mapper.writeObjectAsString(source);
+
assertEquals("{\"children\":[\"5\",\"6\"],\"nested\":{\"b\":2,\"sub\":{\"a\":1,\"c\":[\"3\",\"4\"]}}}",
asString);
+
+ final ChildOfFakedObject childOfFakedObject =
mapper.readObject(asString, ChildOfFakedObject.class);
+ assertEquals(source.a, childOfFakedObject.a);
+ assertEquals(source.b, childOfFakedObject.b);
+ assertArrayEquals(source.c, childOfFakedObject.c);
+ assertEquals(source.children, childOfFakedObject.children);
+ }
+
public static class NanHolder {
private Double nan = Double.NaN;
@@ -859,6 +886,28 @@ public class MapperTest {
}
}
}
+
+ public static class FakeNestedObject {
+ protected int a;
+ protected int b;
+ protected String[] c;
+ }
+
+ @JohnzonVirtualObjects({
+ @JohnzonVirtualObject(
+ path = "nested",
+ fields = @JohnzonVirtualObject.Field("b")
+ ),
+ @JohnzonVirtualObject(
+ path = { "nested", "sub" },
+ fields = {
+ @JohnzonVirtualObject.Field("a"),
@JohnzonVirtualObject.Field("c")
+ }
+ )
+ })
+ public static class ChildOfFakedObject extends FakeNestedObject {
+ protected List<String> children;
+ }
/*public static class ByteArray {