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 <rmannibu...@gmail.com> 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);