From e8bda1cb1a11b41af36f8c87da1c8edd40861ef3 Mon Sep 17 00:00:00 2001 From: "Henning P. Schmiedehausen" Date: Thu, 29 Sep 2022 16:18:33 -0700 Subject: [PATCH] Check for possible supertype matches in AbstractArgumentFactory The AbstractArgumentFactory needs to be able to deal with supertypes when being parameterized with Non-Classes (e.g. parameterized types). We had no tests to actually try that. This fixes #2026. --- .../argument/AbstractArgumentFactory.java | 4 +- .../jdbi/v3/core/generic/GenericTypes.java | 15 +++++- .../argument/TestAbstractArgumentFactory.java | 53 +++++++++++++++++++ 3 files changed, 69 insertions(+), 3 deletions(-) diff --git a/core/src/main/java/org/jdbi/v3/core/argument/AbstractArgumentFactory.java b/core/src/main/java/org/jdbi/v3/core/argument/AbstractArgumentFactory.java index 32287ef5ae..0acf2ad525 100644 --- a/core/src/main/java/org/jdbi/v3/core/argument/AbstractArgumentFactory.java +++ b/core/src/main/java/org/jdbi/v3/core/argument/AbstractArgumentFactory.java @@ -24,6 +24,7 @@ import static org.jdbi.v3.core.generic.GenericTypes.findGenericParameter; import static org.jdbi.v3.core.generic.GenericTypes.getErasedType; +import static org.jdbi.v3.core.generic.GenericTypes.isSuperType; /** * An {@link ArgumentFactory} base class for arguments of type {@code T}. For values of type {@code T}, factories @@ -71,7 +72,8 @@ protected AbstractArgumentFactory(int sqlType) { this.isInstance = (type, value) -> argumentClass.isAssignableFrom(getErasedType(type)) || argumentClass.isInstance(value); } else { - this.isInstance = (type, value) -> argumentType.equals(type); + this.isInstance = (type, value) -> + argumentType.equals(type) || isSuperType(argumentType, type); } } diff --git a/core/src/main/java/org/jdbi/v3/core/generic/GenericTypes.java b/core/src/main/java/org/jdbi/v3/core/generic/GenericTypes.java index 4a0078ce24..eb5445225a 100644 --- a/core/src/main/java/org/jdbi/v3/core/generic/GenericTypes.java +++ b/core/src/main/java/org/jdbi/v3/core/generic/GenericTypes.java @@ -174,12 +174,23 @@ public static Type parameterizeClass(Class clazz, Type... arguments) { } /** - * Perform boxing conversion on a {@code Type}, if it is a primitive type. - * Otherwise return the input argument. + * Perform boxing conversion on a {@code Type}, if it is a primitive type. Otherwise return the input argument. + * * @param type the type to box * @return the boxed type, or the input type if it is not a primitive */ public static Type box(Type type) { return GenericTypeReflector.box(type); } + + /** + * Tests whether a given type is a supertype of another type + * + * @param superType The supertype to check. + * @param subType The subtype to check. + * @return True if supertype is a supertype of subtype. + */ + public static boolean isSuperType(Type superType, Type subType) { + return GenericTypeReflector.isSuperType(superType, subType); + } } diff --git a/core/src/test/java/org/jdbi/v3/core/argument/TestAbstractArgumentFactory.java b/core/src/test/java/org/jdbi/v3/core/argument/TestAbstractArgumentFactory.java index d9d7095243..92bb8370b7 100644 --- a/core/src/test/java/org/jdbi/v3/core/argument/TestAbstractArgumentFactory.java +++ b/core/src/test/java/org/jdbi/v3/core/argument/TestAbstractArgumentFactory.java @@ -17,9 +17,12 @@ import java.sql.PreparedStatement; import java.sql.SQLException; import java.sql.Types; +import java.util.Optional; +import java.util.function.Function; import org.jdbi.v3.core.config.ConfigRegistry; import org.jdbi.v3.core.generic.GenericType; +import org.jdbi.v3.core.qualifier.QualifiedType; import org.jdbi.v3.core.statement.StatementContext; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; @@ -140,4 +143,54 @@ public void testNullOfExpectedGenericType() throws SQLException { argument.apply(2, statement, ctx); verify(statement).setNull(2, Types.VARCHAR); } + + static class StringBox extends Box { + + StringBox(String value) { + super(value); + } + } + + static class IntegerBox extends Box { + + IntegerBox(Integer value) { + super(value); + } + } + + @Test + public void testConcreteClassMatches() { + ArgumentFactory.Preparable preparable = new BoxOfStringArgumentFactory(); + Optional> argument = preparable.prepare(StringBox.class, new ConfigRegistry()); + assertThat(argument).isPresent(); + + argument = preparable.prepare(IntegerBox.class, new ConfigRegistry()); + assertThat(argument).isNotPresent(); + } + + @Test + public void testGenericMatches() { + ConfigRegistry registry = new ConfigRegistry(); + Arguments arguments = new Arguments(registry); + arguments.register(new BoxOfStringArgumentFactory()); + Box genericBoxString = new Box<>("foo"); + StringBox stringBox = new StringBox("bar"); + + assertThat(arguments.findFor(QualifiedType.of(new GenericType>() {}), genericBoxString)).isPresent(); + assertThat(arguments.findFor(QualifiedType.of(new GenericType>() {}), stringBox)).isPresent(); + assertThat(arguments.findFor(StringBox.class, stringBox)).isPresent(); + } + + @Test + public void testGenericNonMatches() { + ConfigRegistry registry = new ConfigRegistry(); + Arguments arguments = new Arguments(registry); + arguments.register(new BoxOfStringArgumentFactory()); + Box genericBoxInteger = new Box<>(10); + IntegerBox integerBox = new IntegerBox(20); + + assertThat(arguments.findFor(QualifiedType.of(new GenericType>() {}), genericBoxInteger)).isNotPresent(); + assertThat(arguments.findFor(QualifiedType.of(new GenericType>() {}), integerBox)).isNotPresent(); + assertThat(arguments.findFor(IntegerBox.class, integerBox)).isNotPresent(); + } }