This is an automated email from the ASF dual-hosted git repository.
rmannibucau pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/johnzon.git
The following commit(s) were added to refs/heads/master by this push:
new 43eff6c JOHNZON-314 support @JohnzonAny on a field
43eff6c is described below
commit 43eff6cb93efef0e04e75dc2e8a3f710a9265991
Author: Romain Manni-Bucau <[email protected]>
AuthorDate: Sun May 31 18:47:16 2020 +0200
JOHNZON-314 support @JohnzonAny on a field
---
.../org/apache/johnzon/jsonb/JsonbAccessMode.java | 5 ++
.../org/apache/johnzon/jsonb/AnySupportTest.java | 94 ++++++++++++++++++++++
.../java/org/apache/johnzon/mapper/JohnzonAny.java | 3 +-
.../apache/johnzon/mapper/MappingParserImpl.java | 15 +++-
.../java/org/apache/johnzon/mapper/Mappings.java | 12 ++-
.../apache/johnzon/mapper/access/AccessMode.java | 2 +
.../johnzon/mapper/access/BaseAccessMode.java | 23 +++++-
7 files changed, 149 insertions(+), 5 deletions(-)
diff --git
a/johnzon-jsonb/src/main/java/org/apache/johnzon/jsonb/JsonbAccessMode.java
b/johnzon-jsonb/src/main/java/org/apache/johnzon/jsonb/JsonbAccessMode.java
index 3ab3fd0..1130f67 100644
--- a/johnzon-jsonb/src/main/java/org/apache/johnzon/jsonb/JsonbAccessMode.java
+++ b/johnzon-jsonb/src/main/java/org/apache/johnzon/jsonb/JsonbAccessMode.java
@@ -728,6 +728,11 @@ public class JsonbAccessMode implements AccessMode,
Closeable {
}
@Override
+ public Field findAnyField(final Class<?> clazz) {
+ return partialDelegate.findAnyField(clazz);
+ }
+
+ @Override
public void afterParsed(final Class<?> clazz) {
parsingCache.remove(clazz);
partialDelegate.afterParsed(clazz);
diff --git
a/johnzon-jsonb/src/test/java/org/apache/johnzon/jsonb/AnySupportTest.java
b/johnzon-jsonb/src/test/java/org/apache/johnzon/jsonb/AnySupportTest.java
new file mode 100644
index 0000000..1cf040f
--- /dev/null
+++ b/johnzon-jsonb/src/test/java/org/apache/johnzon/jsonb/AnySupportTest.java
@@ -0,0 +1,94 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.johnzon.jsonb;
+
+import org.apache.johnzon.jsonb.test.JsonbRule;
+import org.apache.johnzon.mapper.JohnzonAny;
+import org.junit.Rule;
+import org.junit.Test;
+
+import java.util.Map;
+import java.util.Objects;
+
+import static java.util.Collections.singletonMap;
+import static org.junit.Assert.assertEquals;
+
+public class AnySupportTest {
+ @Rule
+ public final JsonbRule rule = new JsonbRule();
+
+ @Test
+ public void roundTrip() {
+ final Foo foo = rule.fromJson("{\"a\":\"b\"}", Foo.class);
+ assertEquals(singletonMap("a", "b"), foo.values);
+ assertEquals("{\"a\":\"b\"}", rule.toJson(foo));
+ }
+
+ @Test
+ public void subObject() {
+ final Bar object = rule.fromJson("{\"a\":{\"b\":\"c\"}}", Bar.class);
+ final Foo foo = new Foo();
+ foo.values = singletonMap("b", "c");
+ assertEquals(singletonMap("a", foo), object.values);
+ assertEquals("{\"a\":{\"b\":\"c\"}}", rule.toJson(object));
+ }
+
+ public static class Foo {
+ @JohnzonAny
+ private Map<String, String> values;
+
+ public Map<String, String> getValues() {
+ return values;
+ }
+
+ public void setValues(final Map<String, String> values) {
+ this.values = values;
+ }
+
+ @Override
+ public boolean equals(final Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (o == null || getClass() != o.getClass()) {
+ return false;
+ }
+ Foo foo = (Foo) o;
+ return values.equals(foo.values);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(values);
+ }
+ }
+
+ public static class Bar {
+ @JohnzonAny
+ private Map<String, Foo> values;
+
+ public Map<String, Foo> getValues() {
+ return values;
+ }
+
+ public void setValues(final Map<String, Foo> values) {
+ this.values = values;
+ }
+ }
+}
diff --git
a/johnzon-mapper/src/main/java/org/apache/johnzon/mapper/JohnzonAny.java
b/johnzon-mapper/src/main/java/org/apache/johnzon/mapper/JohnzonAny.java
index 16338e3..f33a583 100644
--- a/johnzon-mapper/src/main/java/org/apache/johnzon/mapper/JohnzonAny.java
+++ b/johnzon-mapper/src/main/java/org/apache/johnzon/mapper/JohnzonAny.java
@@ -22,10 +22,11 @@ import java.lang.annotation.Retention;
import java.lang.annotation.Target;
import static java.lang.annotation.ElementType.ANNOTATION_TYPE;
+import static java.lang.annotation.ElementType.FIELD;
import static java.lang.annotation.ElementType.METHOD;
import static java.lang.annotation.RetentionPolicy.RUNTIME;
@Retention(RUNTIME)
-@Target({METHOD, ANNOTATION_TYPE})
+@Target({METHOD, ANNOTATION_TYPE, FIELD})
public @interface JohnzonAny {
}
diff --git
a/johnzon-mapper/src/main/java/org/apache/johnzon/mapper/MappingParserImpl.java
b/johnzon-mapper/src/main/java/org/apache/johnzon/mapper/MappingParserImpl.java
index 26d3e12..1b7a3a9 100644
---
a/johnzon-mapper/src/main/java/org/apache/johnzon/mapper/MappingParserImpl.java
+++
b/johnzon-mapper/src/main/java/org/apache/johnzon/mapper/MappingParserImpl.java
@@ -78,6 +78,7 @@ import java.util.stream.Stream;
import static java.util.Arrays.asList;
import static java.util.stream.Collectors.toList;
+import static java.util.stream.Collectors.toMap;
import static javax.json.JsonValue.ValueType.ARRAY;
import static javax.json.JsonValue.ValueType.FALSE;
import static javax.json.JsonValue.ValueType.NULL;
@@ -424,7 +425,9 @@ public class MappingParserImpl implements MappingParser {
final String key = entry.getKey();
if (!classMapping.setters.containsKey(key)) {
try {
- classMapping.anySetter.invoke(t, key, toValue(null,
entry.getValue(), null, null, Object.class, null,
+ classMapping.anySetter.invoke(t, key,
+ toValue(null, entry.getValue(), null, null,
+
classMapping.anySetter.getGenericParameterTypes()[1], null,
isDeduplicateObjects ? new
JsonPointerTracker(jsonPointer, entry.getKey()) : null, type));
} catch (final IllegalAccessException e) {
throw new IllegalStateException(e);
@@ -433,6 +436,16 @@ public class MappingParserImpl implements MappingParser {
}
}
}
+ } else if (classMapping.anyField != null) {
+ final Type tRef = type;
+ try {
+ classMapping.anyField.set(t, object.entrySet().stream()
+ .collect(toMap(Map.Entry::getKey, e -> toValue(null,
e.getValue(), null, null,
+
ParameterizedType.class.cast(classMapping.anyField.getGenericType()).getActualTypeArguments()[1],
null,
+ isDeduplicateObjects ? new
JsonPointerTracker(jsonPointer, e.getKey()) : null, tRef))));
+ } catch (final IllegalAccessException e) {
+ throw new IllegalStateException(e);
+ }
}
if (classMapping.mapAdder != null) {
object.entrySet().stream()
diff --git
a/johnzon-mapper/src/main/java/org/apache/johnzon/mapper/Mappings.java
b/johnzon-mapper/src/main/java/org/apache/johnzon/mapper/Mappings.java
index 6991c9d..0d8adbb 100644
--- a/johnzon-mapper/src/main/java/org/apache/johnzon/mapper/Mappings.java
+++ b/johnzon-mapper/src/main/java/org/apache/johnzon/mapper/Mappings.java
@@ -25,6 +25,7 @@ import static
org.apache.johnzon.mapper.reflection.Generics.resolve;
import java.lang.annotation.Annotation;
import java.lang.reflect.Array;
+import java.lang.reflect.Field;
import java.lang.reflect.GenericArrayType;
import java.lang.reflect.Method;
import java.lang.reflect.ParameterizedType;
@@ -53,6 +54,7 @@ import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import org.apache.johnzon.mapper.access.AccessMode;
+import org.apache.johnzon.mapper.access.FieldAccessMode;
import org.apache.johnzon.mapper.access.MethodAccessMode;
import org.apache.johnzon.mapper.converter.DateWithCopyConverter;
import org.apache.johnzon.mapper.converter.EnumConverter;
@@ -72,6 +74,7 @@ public class Mappings {
public final ObjectConverter.Writer writer;
public final Getter anyGetter;
public final Method anySetter;
+ public final Field anyField;
public final Method mapAdder;
public final Class<?> mapAdderType;
@@ -83,7 +86,7 @@ public class Mappings {
final Map<String, Getter> getters, final
Map<String, Setter> setters,
final Adapter<?, ?> adapter,
final ObjectConverter.Reader<?> reader, final
ObjectConverter.Writer<?> writer,
- final Getter anyGetter, final Method anySetter,
+ final Getter anyGetter, final Method anySetter,
final Field anyField,
final Method mapAdder) {
this.clazz = clazz;
this.factory = factory;
@@ -94,6 +97,7 @@ public class Mappings {
this.reader = reader;
this.anyGetter = anyGetter;
this.anySetter = anySetter;
+ this.anyField = anyField;
this.mapAdder = mapAdder;
this.mapAdderType = mapAdder == null ? null :
mapAdder.getParameterTypes()[1];
}
@@ -479,6 +483,7 @@ public class Mappings {
}
final Method anyGetter = accessMode.findAnyGetter(clazz);
+ final Field anyField = accessMode.findAnyField(clazz);
final ClassMapping mapping = new ClassMapping(
clazz, accessMode.findFactory(clazz), getters, setters,
accessMode.findAdapter(clazz),
@@ -486,8 +491,11 @@ public class Mappings {
accessMode.findWriter(clazz),
anyGetter != null ? new Getter(
new MethodAccessMode.MethodReader(anyGetter,
anyGetter.getReturnType()),
- false,false, false, false, true, null, null, -1, null)
: null,
+ false,false, false, false, true, null, null, -1, null)
:
+ (anyField != null ? new Getter(new
FieldAccessMode.FieldReader(anyField, anyField.getGenericType()),
+ false,false, false, false, true, null, null, -1, null)
: null),
accessMode.findAnySetter(clazz),
+ anyField,
Map.class.isAssignableFrom(clazz) ?
accessMode.findMapAdder(clazz) : null);
accessMode.afterParsed(clazz);
diff --git
a/johnzon-mapper/src/main/java/org/apache/johnzon/mapper/access/AccessMode.java
b/johnzon-mapper/src/main/java/org/apache/johnzon/mapper/access/AccessMode.java
index 4f12fa2..dc7a462 100644
---
a/johnzon-mapper/src/main/java/org/apache/johnzon/mapper/access/AccessMode.java
+++
b/johnzon-mapper/src/main/java/org/apache/johnzon/mapper/access/AccessMode.java
@@ -20,6 +20,7 @@ package org.apache.johnzon.mapper.access;
import java.lang.annotation.Annotation;
import java.lang.reflect.AnnotatedElement;
+import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
@@ -113,6 +114,7 @@ public interface AccessMode {
Adapter<?, ?> findAdapter(Class<?> clazz);
Method findAnyGetter(Class<?> clazz);
Method findAnySetter(Class<?> clazz);
+ Field findAnyField(Class<?> clazz);
default Method findMapAdder(final Class<?> clazz) {
return MapHelper.find((name, type, param) -> type.getMethod("add" +
name, String.class, param), clazz);
diff --git
a/johnzon-mapper/src/main/java/org/apache/johnzon/mapper/access/BaseAccessMode.java
b/johnzon-mapper/src/main/java/org/apache/johnzon/mapper/access/BaseAccessMode.java
index 1a5931e..edbc379 100644
---
a/johnzon-mapper/src/main/java/org/apache/johnzon/mapper/access/BaseAccessMode.java
+++
b/johnzon-mapper/src/main/java/org/apache/johnzon/mapper/access/BaseAccessMode.java
@@ -30,15 +30,18 @@ import java.beans.ConstructorProperties;
import java.lang.annotation.Annotation;
import java.lang.reflect.AnnotatedElement;
import java.lang.reflect.Constructor;
+import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.lang.reflect.Type;
import java.util.Collection;
import java.util.Comparator;
+import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Objects;
+import java.util.Set;
import java.util.function.Function;
import java.util.stream.Stream;
@@ -302,7 +305,7 @@ public abstract class BaseAccessMode implements AccessMode {
for (final Method current : clazz.getMethods()) {
if (current.getAnnotation(JohnzonAny.class) != null) {
final Class<?>[] parameterTypes = current.getParameterTypes();
- if (parameterTypes.length == 2 && parameterTypes[0] ==
String.class && parameterTypes[1] == Object.class) {
+ if (parameterTypes.length == 2 && parameterTypes[0] ==
String.class) {
if (m != null) {
throw new IllegalArgumentException("Ambiguous
@JohnzonAny on " + m + " and " + current);
}
@@ -313,6 +316,24 @@ public abstract class BaseAccessMode implements AccessMode
{
return m;
}
+ @Override
+ public Field findAnyField(final Class<?> clazz) {
+ if (clazz.isInterface() || clazz.isEnum()) {
+ return null;
+ }
+ Class<?> current = clazz;
+ final Set<Class<?>> visited = new HashSet<>();
+ while (current != null && current != Object.class &&
visited.add(current)) {
+ for (final Field f : current.getDeclaredFields()) {
+ if (f.isAnnotationPresent(JohnzonAny.class)) { // todo:
validation? waiting for jsonb standard behavior
+ return f;
+ }
+ }
+ current = clazz.getSuperclass();
+ }
+ return null;
+ }
+
private <T> Map<String, T> sanitize(final Class<?> type, final Map<String,
T> delegate) {
for (final String field : fieldFilteringStrategy.select(type)) {
delegate.remove(field);