Repository: johnzon Updated Branches: refs/heads/master a25adfaee -> fea9ac9f0
JOHNZON-155 better support of TypeVariables Project: http://git-wip-us.apache.org/repos/asf/johnzon/repo Commit: http://git-wip-us.apache.org/repos/asf/johnzon/commit/fea9ac9f Tree: http://git-wip-us.apache.org/repos/asf/johnzon/tree/fea9ac9f Diff: http://git-wip-us.apache.org/repos/asf/johnzon/diff/fea9ac9f Branch: refs/heads/master Commit: fea9ac9f0b4e017378f67f57985a9621ce94457a Parents: a25adfa Author: Romain Manni-Bucau <[email protected]> Authored: Sun Feb 4 17:15:12 2018 +0100 Committer: Romain Manni-Bucau <[email protected]> Committed: Sun Feb 4 17:15:31 2018 +0100 ---------------------------------------------------------------------- johnzon-mapper/pom.xml | 5 + .../org/apache/johnzon/mapper/Mappings.java | 28 +++--- .../johnzon/mapper/reflection/Generics.java | 98 ++++++++++++++++++++ .../org/apache/johnzon/mapper/GenericsTest.java | 43 +++++++++ .../src/test/java/org/superbiz/Model.java | 29 ++++++ .../src/test/java/org/superbiz/ModelBase.java | 34 +++++++ .../test/java/org/superbiz/ModelSuperBase.java | 53 +++++++++++ .../java/org/superbiz/ModelSuperSuperBase.java | 48 ++++++++++ 8 files changed, 326 insertions(+), 12 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/johnzon/blob/fea9ac9f/johnzon-mapper/pom.xml ---------------------------------------------------------------------- diff --git a/johnzon-mapper/pom.xml b/johnzon-mapper/pom.xml index 2104a3f..d4f0391 100644 --- a/johnzon-mapper/pom.xml +++ b/johnzon-mapper/pom.xml @@ -34,6 +34,11 @@ <artifactId>johnzon-core</artifactId> <version>${project.version}</version> </dependency> + <dependency> + <groupId>org.apache.commons</groupId> + <artifactId>commons-lang3</artifactId> + <version>3.6</version> + </dependency> <dependency> <groupId>com.github.stefanbirkner</groupId> http://git-wip-us.apache.org/repos/asf/johnzon/blob/fea9ac9f/johnzon-mapper/src/main/java/org/apache/johnzon/mapper/Mappings.java ---------------------------------------------------------------------- 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 44e13f1..8a04515 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 @@ -52,6 +52,7 @@ import java.util.concurrent.ConcurrentMap; import static java.util.Arrays.asList; import static org.apache.johnzon.mapper.reflection.Converters.matches; +import static org.apache.johnzon.mapper.reflection.Generics.resolve; public class Mappings { public static class ClassMapping { @@ -361,8 +362,8 @@ public class Mappings { Comparator<String> fieldComparator = accessMode.fieldComparator(inClazz); fieldComparator = fieldComparator == null ? config.getAttributeOrder() : fieldComparator; - final Map<String, Getter> getters = fieldComparator == null ? newOrderedMap(Getter.class) : new TreeMap<String, Getter>(fieldComparator); - final Map<String, Setter> setters = fieldComparator == null ? newOrderedMap(Setter.class) : new TreeMap<String, Setter>(fieldComparator); + final Map<String, Getter> getters = fieldComparator == null ? newOrderedMap(Getter.class) : new TreeMap<>(fieldComparator); + final Map<String, Setter> setters = fieldComparator == null ? newOrderedMap(Setter.class) : new TreeMap<>(fieldComparator); final Map<String, AccessMode.Reader> readers = accessMode.findReaders(clazz); final Map<String, AccessMode.Writer> writers = accessMode.findWriters(clazz); @@ -372,13 +373,13 @@ public class Mappings { final JohnzonVirtualObjects virtualObjects = clazz.getAnnotation(JohnzonVirtualObjects.class); if (virtualObjects != null) { for (final JohnzonVirtualObject virtualObject : virtualObjects.value()) { - handleVirtualObject(virtualFields, virtualObject, getters, setters, readers, writers, copyDate); + handleVirtualObject(virtualFields, virtualObject, getters, setters, readers, writers, copyDate, clazz); } } final JohnzonVirtualObject virtualObject = clazz.getAnnotation(JohnzonVirtualObject.class); if (virtualObject != null) { - handleVirtualObject(virtualFields, virtualObject, getters, setters, readers, writers, copyDate); + handleVirtualObject(virtualFields, virtualObject, getters, setters, readers, writers, copyDate, clazz); } } @@ -387,7 +388,7 @@ public class Mappings { if (virtualFields.contains(key)) { continue; } - addGetterIfNeeded(getters, key, reader.getValue(), copyDate); + addGetterIfNeeded(getters, key, reader.getValue(), copyDate, clazz); } for (final Map.Entry<String, AccessMode.Writer> writer : writers.entrySet()) { @@ -395,7 +396,7 @@ public class Mappings { if (virtualFields.contains(key)) { continue; } - addSetterIfNeeded(setters, key, writer.getValue(), copyDate); + addSetterIfNeeded(setters, key, writer.getValue(), copyDate, clazz); } final Method anyGetter = accessMode.findAnyGetter(clazz); @@ -435,7 +436,8 @@ public class Mappings { private void addSetterIfNeeded(final Map<String, Setter> setters, final String key, final AccessMode.Writer value, - final boolean copyDate) { + final boolean copyDate, + final Class<?> rootClass) { final JohnzonIgnore writeIgnore = value.getAnnotation(JohnzonIgnore.class); if (writeIgnore == null || writeIgnore.minVersion() >= 0) { if (key.equals("metaClass")) { @@ -444,7 +446,7 @@ public class Mappings { 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, + value, isPrimitive(param), returnType != null && returnType.isArray(), resolve(param, rootClass), findConverter(copyDate, value), value.findObjectConverterReader(), writeIgnore != null ? writeIgnore.minVersion() : -1); setters.put(key, setter); @@ -454,7 +456,8 @@ public class Mappings { private void addGetterIfNeeded(final Map<String, Getter> getters, final String key, final AccessMode.Reader value, - final boolean copyDate) { + final boolean copyDate, + final Class<?> rootClass) { final JohnzonIgnore readIgnore = value.getAnnotation(JohnzonIgnore.class); final JohnzonIgnoreNested ignoreNested = value.getAnnotation(JohnzonIgnoreNested.class); if (readIgnore == null || readIgnore.minVersion() >= 0) { @@ -480,7 +483,8 @@ public class Mappings { final Map<String, Setter> setters, final Map<String, AccessMode.Reader> readers, final Map<String, AccessMode.Writer> writers, - final boolean copyDate) { + final boolean copyDate, + final Class<?> rootClazz) { final String[] path = o.path(); if (path.length < 1) { throw new IllegalArgumentException("@JohnzonVirtualObject need a path"); @@ -500,13 +504,13 @@ public class Mappings { if (f.read()) { final AccessMode.Reader reader = readers.get(name); if (reader != null) { - addGetterIfNeeded(objectGetters, name, reader, copyDate); + addGetterIfNeeded(objectGetters, name, reader, copyDate, rootClazz); } } if (f.write()) { final AccessMode.Writer writer = writers.get(name); if (writer != null) { - addSetterIfNeeded(objectSetters, name, writer, copyDate); + addSetterIfNeeded(objectSetters, name, writer, copyDate, rootClazz); } } } http://git-wip-us.apache.org/repos/asf/johnzon/blob/fea9ac9f/johnzon-mapper/src/main/java/org/apache/johnzon/mapper/reflection/Generics.java ---------------------------------------------------------------------- diff --git a/johnzon-mapper/src/main/java/org/apache/johnzon/mapper/reflection/Generics.java b/johnzon-mapper/src/main/java/org/apache/johnzon/mapper/reflection/Generics.java new file mode 100644 index 0000000..31ad7f2 --- /dev/null +++ b/johnzon-mapper/src/main/java/org/apache/johnzon/mapper/reflection/Generics.java @@ -0,0 +1,98 @@ +/* + * 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.reflection; + +import static java.util.Arrays.asList; + +import java.lang.reflect.ParameterizedType; +import java.lang.reflect.Type; +import java.lang.reflect.TypeVariable; +import java.util.ArrayList; +import java.util.Collection; + +public final class Generics { + private Generics() { + // no-op + } + + // todo: this piece of code needs to be enhanced a lot: + // - better handling of the hierarchy + // - wildcard support? + // - cycle handling (Foo<Foo>) + // - .... + public static Type resolve(final Type value, final Class<?> rootClass) { + if (TypeVariable.class.isInstance(value)) { + return resolveTypeVariable(value, rootClass); + } + if (ParameterizedType.class.isInstance(value)) { + return resolveParameterizedType(value, rootClass); + } + return value; + } + + private static Type resolveParameterizedType(final Type value, final Class<?> rootClass) { + Collection<Type> args = null; + final ParameterizedType parameterizedType = ParameterizedType.class.cast(value); + int index = 0; + for (final Type arg : parameterizedType.getActualTypeArguments()) { + final Type type = resolve(arg, rootClass); + if (type != arg) { + if (args == null) { + args = new ArrayList<>(); + if (index > 0) { + args.addAll(asList(parameterizedType.getActualTypeArguments()).subList(0, index + 1)); + } + } + } + if (args != null) { + args.add(arg); + } + index++; + } + if (args != null) { + return new JohnzonParameterizedType(parameterizedType.getRawType(), args.toArray(new Type[args.size()])); + } + return value; + } + + // for now the level is hardcoded to 2 with generic > concrete + private static Type resolveTypeVariable(final Type value, final Class<?> rootClass) { + final TypeVariable<?> tv = TypeVariable.class.cast(value); + final Type parent = rootClass.getGenericSuperclass(); + if (ParameterizedType.class.isInstance(parent)) { + final ParameterizedType parentPt = ParameterizedType.class.cast(parent); + if (Class.class.isInstance(parentPt.getRawType())) { + final Type grandParent = Class.class.cast(parentPt.getRawType()).getGenericSuperclass(); + if (ParameterizedType.class.isInstance(grandParent)) { + final ParameterizedType grandParentPt = ParameterizedType.class.cast(grandParent); + final Type[] grandParentArgs = grandParentPt.getActualTypeArguments(); + int index = 0; + final String name = tv.getName(); + for (final Type t : grandParentArgs) { + if (TypeVariable.class.isInstance(t) && TypeVariable.class.cast(t).getName().equals(name)) { + return parentPt.getActualTypeArguments()[index]; + } + index++; + } + } + } + } + return value; + } +} http://git-wip-us.apache.org/repos/asf/johnzon/blob/fea9ac9f/johnzon-mapper/src/test/java/org/apache/johnzon/mapper/GenericsTest.java ---------------------------------------------------------------------- diff --git a/johnzon-mapper/src/test/java/org/apache/johnzon/mapper/GenericsTest.java b/johnzon-mapper/src/test/java/org/apache/johnzon/mapper/GenericsTest.java new file mode 100644 index 0000000..a5b0577 --- /dev/null +++ b/johnzon-mapper/src/test/java/org/apache/johnzon/mapper/GenericsTest.java @@ -0,0 +1,43 @@ +/* + * 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 static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; + +import org.junit.Test; +import org.superbiz.Model; + +public class GenericsTest { + @Test + public void typeVariableMultiLevel() { + final String input = "{\"aalist\":[{\"detail\":\"something2\",\"name\":\"Na2\"}]," + + "\"childA\":{\"detail\":\"something\",\"name\":\"Na\"},\"childB\":{}}"; + final Mapper mapper = new MapperBuilder().setAttributeOrder(String::compareTo).build(); + final Model model = mapper.readObject(input, Model.class); + assertNotNull(model.getChildA()); + assertNotNull(model.getChildB()); + assertNotNull(model.getAalist()); + assertEquals("something", model.getChildA().detail); + assertEquals("Na", model.getChildA().name); + assertEquals(1, model.getAalist().size()); + assertEquals("something2", model.getAalist().iterator().next().detail); + assertEquals(input, mapper.writeObjectAsString(model)); + } +} http://git-wip-us.apache.org/repos/asf/johnzon/blob/fea9ac9f/johnzon-mapper/src/test/java/org/superbiz/Model.java ---------------------------------------------------------------------- diff --git a/johnzon-mapper/src/test/java/org/superbiz/Model.java b/johnzon-mapper/src/test/java/org/superbiz/Model.java new file mode 100644 index 0000000..1b3a077 --- /dev/null +++ b/johnzon-mapper/src/test/java/org/superbiz/Model.java @@ -0,0 +1,29 @@ +/* + * 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.superbiz; + +public class Model extends ModelBase<Model.ChildA, Model.ChildB> { + public static class ChildA extends ModelSuperBase.ChildA { + public String detail; + } + + public static class ChildB extends ModelSuperBase.ChildB { + + } +} http://git-wip-us.apache.org/repos/asf/johnzon/blob/fea9ac9f/johnzon-mapper/src/test/java/org/superbiz/ModelBase.java ---------------------------------------------------------------------- diff --git a/johnzon-mapper/src/test/java/org/superbiz/ModelBase.java b/johnzon-mapper/src/test/java/org/superbiz/ModelBase.java new file mode 100644 index 0000000..d7668df --- /dev/null +++ b/johnzon-mapper/src/test/java/org/superbiz/ModelBase.java @@ -0,0 +1,34 @@ +/* + * 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.superbiz; + +import java.util.Collection; + +public abstract class ModelBase<A extends ModelSuperBase.ChildA, B extends ModelSuperBase.ChildB> + extends ModelSuperBase<A, B> { + private Collection<A> aalist; + + public Collection<A> getAalist() { + return aalist; + } + + public void setAalist(final Collection<A> aalist) { + this.aalist = aalist; + } +} http://git-wip-us.apache.org/repos/asf/johnzon/blob/fea9ac9f/johnzon-mapper/src/test/java/org/superbiz/ModelSuperBase.java ---------------------------------------------------------------------- diff --git a/johnzon-mapper/src/test/java/org/superbiz/ModelSuperBase.java b/johnzon-mapper/src/test/java/org/superbiz/ModelSuperBase.java new file mode 100644 index 0000000..c39d80b --- /dev/null +++ b/johnzon-mapper/src/test/java/org/superbiz/ModelSuperBase.java @@ -0,0 +1,53 @@ +/* + * 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.superbiz; + +public abstract class ModelSuperBase<A extends ModelSuperBase.ChildA, B extends ModelSuperBase.ChildB> + extends ModelSuperSuperBase<A, B> { + public static class ChildA extends ModelSuperSuperBase.ChildA { + + } + + public static class ChildB extends ModelSuperSuperBase.ChildB { + + } + + private A childA; + private B childB; + + @Override + public A getChildA() { + return childA; + } + + @Override + public void setChildA(A childA) { + this.childA = childA; + } + + @Override + public B getChildB() { + return childB; + } + + @Override + public void setChildB(B childB) { + this.childB = childB; + } +} http://git-wip-us.apache.org/repos/asf/johnzon/blob/fea9ac9f/johnzon-mapper/src/test/java/org/superbiz/ModelSuperSuperBase.java ---------------------------------------------------------------------- diff --git a/johnzon-mapper/src/test/java/org/superbiz/ModelSuperSuperBase.java b/johnzon-mapper/src/test/java/org/superbiz/ModelSuperSuperBase.java new file mode 100644 index 0000000..72980b6 --- /dev/null +++ b/johnzon-mapper/src/test/java/org/superbiz/ModelSuperSuperBase.java @@ -0,0 +1,48 @@ +/* + * 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.superbiz; + +public abstract class ModelSuperSuperBase<A extends ModelSuperSuperBase.ChildA, B extends ModelSuperSuperBase.ChildB> { + public static class ChildA { + public String name; + } + + public static class ChildB { + + } + + private A childA; + private B childB; + + public A getChildA() { + return childA; + } + + public void setChildA(A childA) { + this.childA = childA; + } + + public B getChildB() { + return childB; + } + + public void setChildB(B childB) { + this.childB = childB; + } +}
