From 78c4295e613d81ac87f75b7997dcb224d2196a46 Mon Sep 17 00:00:00 2001 From: lukaseder Date: Mon, 1 Apr 2019 10:56:38 +0200 Subject: [PATCH] [#8460] Regression calling POJO setters twice from DefaultRecordMapper.MutablePOJOMapper --- jOOQ/src/main/java/org/jooq/impl/Tools.java | 86 ++++++++++++++++++--- 1 file changed, 74 insertions(+), 12 deletions(-) diff --git a/jOOQ/src/main/java/org/jooq/impl/Tools.java b/jOOQ/src/main/java/org/jooq/impl/Tools.java index b6714d38e3..03fde6819d 100644 --- a/jOOQ/src/main/java/org/jooq/impl/Tools.java +++ b/jOOQ/src/main/java/org/jooq/impl/Tools.java @@ -179,6 +179,7 @@ import java.util.EnumSet; import java.util.HashSet; import java.util.Iterator; import java.util.LinkedHashMap; +import java.util.LinkedHashSet; import java.util.List; import java.util.Map; import java.util.Map.Entry; @@ -3309,7 +3310,7 @@ final class Tools { @Override public List call() { - List result = new ArrayList(); + Set set = new LinkedHashSet(); for (Method method : getInstanceMethods(type)) { Column column = method.getAnnotation(Column.class); @@ -3318,7 +3319,7 @@ final class Tools { // Annotated setter if (method.getParameterTypes().length == 1) { - result.add(accessible(method)); + set.add(new SourceMethod(accessible(method))); } // Annotated getter with matching setter @@ -3336,7 +3337,7 @@ final class Tools { // Setter annotation is more relevant if (setter.getAnnotation(Column.class) == null) - result.add(accessible(setter)); + set.add(new SourceMethod(accessible(setter))); } catch (NoSuchMethodException ignore) {} } @@ -3344,7 +3345,7 @@ final class Tools { } } - return result; + return SourceMethod.methods(set); } }, DATA_REFLECTION_CACHE_GET_ANNOTATED_SETTERS, Cache.key(type, name)); @@ -3409,7 +3410,9 @@ final class Tools { @Override public List call() { - List result = new ArrayList(); + + // [#8460] Prevent duplicate methods in the call hierarchy + Set set = new LinkedHashSet(); // [#1942] Caching these values before the method-loop significantly // accerates POJO mapping @@ -3421,16 +3424,16 @@ final class Tools { if (parameterTypes.length == 1) if (name.equals(method.getName())) - result.add(accessible(method)); + set.add(new SourceMethod(accessible(method))); else if (camelCaseLC.equals(method.getName())) - result.add(accessible(method)); + set.add(new SourceMethod(accessible(method))); else if (("set" + name).equals(method.getName())) - result.add(accessible(method)); + set.add(new SourceMethod(accessible(method))); else if (("set" + camelCase).equals(method.getName())) - result.add(accessible(method)); + set.add(new SourceMethod(accessible(method))); } - return result; + return SourceMethod.methods(set); } }, DATA_REFLECTION_CACHE_GET_MATCHING_SETTERS, Cache.key(type, name)); @@ -3471,8 +3474,67 @@ final class Tools { }, DATA_REFLECTION_CACHE_GET_MATCHING_GETTER, Cache.key(type, name)); } - private static final List getInstanceMethods(Class type) { - List result = new ArrayList(); + /** + * A wrapper class that re-implements {@link Method#equals(Object)} and + * {@link Method#hashCode()} based only on the "source signature" (name, + * parameter types), instead of the "binary signature" (declaring class, + * name, return type, parameter types). + */ + private static final class SourceMethod { + final Method method; + + SourceMethod(Method method) { + this.method = method; + } + + static List methods(Collection methods) { + List result = new ArrayList(methods.size()); + + for (SourceMethod s : methods) + result.add(s.method); + + return result; + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + ((method == null) ? 0 : method.getName().hashCode()); + return result; + } + + @Override + public boolean equals(Object obj) { + if (obj instanceof SourceMethod) { + Method other = ((SourceMethod) obj).method; + + if (method.getName().equals(other.getName())) { + Class[] p1 = method.getParameterTypes(); + Class[] p2 = other.getParameterTypes(); + + return Arrays.equals(p1, p2); + } + } + + return false; + } + + @Override + public String toString() { + return method.toString(); + } + } + + /** + * All the public and declared methods of a type. + *

+ * This method returns each method only once. Public methods are returned + * first in the resulting set while declared methods are returned + * afterwards, from lowest to highest type in the type hierarchy. + */ + private static final Set getInstanceMethods(Class type) { + Set result = new LinkedHashSet(); for (Method method : type.getMethods()) if ((method.getModifiers() & Modifier.STATIC) == 0)