From 86fb0a6ccf6d09d0bee93df2ca449cf9a4446720 Mon Sep 17 00:00:00 2001 From: Jesse Gallagher Date: Mon, 30 Dec 2024 14:29:27 -0500 Subject: [PATCH] fix: improve handling of collection subclass columns --- .../DefaultCollectionFieldMetadata.java | 6 ++- .../reflection/DefaultMapFieldMetadata.java | 14 +++---- .../mapping/reflection/Reflections.java | 37 ++++++++++++++++++ .../ReflectionClassConverterTest.java | 7 ++++ .../reflection/entities/JsonContainer.java | 38 +++++++++++++++++++ 5 files changed, 93 insertions(+), 9 deletions(-) create mode 100644 jnosql-mapping/jnosql-mapping-reflection/src/test/java/org/eclipse/jnosql/mapping/reflection/entities/JsonContainer.java diff --git a/jnosql-mapping/jnosql-mapping-reflection/src/main/java/org/eclipse/jnosql/mapping/reflection/DefaultCollectionFieldMetadata.java b/jnosql-mapping/jnosql-mapping-reflection/src/main/java/org/eclipse/jnosql/mapping/reflection/DefaultCollectionFieldMetadata.java index 04ecf9bef..be92a1d09 100644 --- a/jnosql-mapping/jnosql-mapping-reflection/src/main/java/org/eclipse/jnosql/mapping/reflection/DefaultCollectionFieldMetadata.java +++ b/jnosql-mapping/jnosql-mapping-reflection/src/main/java/org/eclipse/jnosql/mapping/reflection/DefaultCollectionFieldMetadata.java @@ -25,6 +25,7 @@ import java.lang.reflect.Field; import java.lang.reflect.ParameterizedType; +import java.text.MessageFormat; import java.util.Collection; import java.util.Collections; import java.util.Objects; @@ -89,8 +90,9 @@ private boolean isEmbeddableField() { } private boolean hasFieldAnnotation(Class annotation) { - return ((Class) ((ParameterizedType) this.field - .getGenericType()) + ParameterizedType collectionType = Reflections.findParameterizedType(this.field.getGenericType(), Collection.class) + .orElseThrow(() -> new IllegalStateException(MessageFormat.format("Unable to find parameterized Collection implementation for {0}", this.field))); + return ((Class) collectionType .getActualTypeArguments()[0]) .getAnnotation(annotation) != null; } diff --git a/jnosql-mapping/jnosql-mapping-reflection/src/main/java/org/eclipse/jnosql/mapping/reflection/DefaultMapFieldMetadata.java b/jnosql-mapping/jnosql-mapping-reflection/src/main/java/org/eclipse/jnosql/mapping/reflection/DefaultMapFieldMetadata.java index 5dd1b2a9d..d3a0d6150 100644 --- a/jnosql-mapping/jnosql-mapping-reflection/src/main/java/org/eclipse/jnosql/mapping/reflection/DefaultMapFieldMetadata.java +++ b/jnosql-mapping/jnosql-mapping-reflection/src/main/java/org/eclipse/jnosql/mapping/reflection/DefaultMapFieldMetadata.java @@ -22,6 +22,8 @@ import java.lang.reflect.Field; import java.lang.reflect.ParameterizedType; +import java.text.MessageFormat; +import java.util.Map; import java.util.Objects; final class DefaultMapFieldMetadata extends AbstractFieldMetadata implements MapFieldMetadata { @@ -29,7 +31,7 @@ final class DefaultMapFieldMetadata extends AbstractFieldMetadata implements Map private final TypeSupplier typeSupplier; private final Class keyType; - + private final Class valueType; DefaultMapFieldMetadata(MappingType type, Field field, String name, TypeSupplier typeSupplier, @@ -37,12 +39,10 @@ final class DefaultMapFieldMetadata extends AbstractFieldMetadata implements Map FieldReader reader, FieldWriter writer, String udt) { super(type, field, name, converter, reader, writer, udt); this.typeSupplier = typeSupplier; - this.keyType = (Class) ((ParameterizedType) this.field - .getGenericType()) - .getActualTypeArguments()[0]; - this.valueType = (Class) ((ParameterizedType) this.field - .getGenericType()) - .getActualTypeArguments()[1]; + ParameterizedType mapType = Reflections.findParameterizedType(this.field.getGenericType(), Map.class) + .orElseThrow(() -> new IllegalStateException(MessageFormat.format("Unable to find parameterized Map implementation for {0}", this.field))); + this.keyType = (Class) mapType.getActualTypeArguments()[0]; + this.valueType = (Class) mapType.getActualTypeArguments()[1]; } @Override diff --git a/jnosql-mapping/jnosql-mapping-reflection/src/main/java/org/eclipse/jnosql/mapping/reflection/Reflections.java b/jnosql-mapping/jnosql-mapping-reflection/src/main/java/org/eclipse/jnosql/mapping/reflection/Reflections.java index b777b4f43..ba339abc9 100644 --- a/jnosql-mapping/jnosql-mapping-reflection/src/main/java/org/eclipse/jnosql/mapping/reflection/Reflections.java +++ b/jnosql-mapping/jnosql-mapping-reflection/src/main/java/org/eclipse/jnosql/mapping/reflection/Reflections.java @@ -32,6 +32,8 @@ import java.lang.reflect.Field; import java.lang.reflect.Modifier; import java.lang.reflect.Parameter; +import java.lang.reflect.ParameterizedType; +import java.lang.reflect.Type; import java.util.ArrayList; import java.util.Arrays; import java.util.List; @@ -364,6 +366,41 @@ public String getUDTName(Field field) { .orElse(null); } + /** + * Attempts to locate the specific generic declaration of the desired type, + * walking the interface and superclass hierarchy to locate it. + * + * @param type the type to scan, such as a field's generic type + * @param parentType the type to search for, such as {@code Map} + * @return an {@link Optional} describing the found declaration, or an + * empty one if it cannot be found + * @since 1.1.5 + */ + public static Optional findParameterizedType(Type type, Class parentType) { + if(type instanceof ParameterizedType parameterizedType && parameterizedType.getRawType() instanceof Class rawClass) { + if(parentType.isAssignableFrom(rawClass)) { + return Optional.of(parameterizedType); + } + } + if(type instanceof Class classType) { + Type superType = classType.getGenericSuperclass(); + if(superType != null) { + Optional superResult = findParameterizedType(superType, parentType); + if(superResult.isPresent()) { + return superResult; + } + } + for(Type superInterface : classType.getGenericInterfaces()) { + Optional superResult = findParameterizedType(superInterface, parentType); + if(superResult.isPresent()) { + return superResult; + } + } + } + + return Optional.empty(); + } + private String getDiscriminatorColumn(Class parent) { return Optional diff --git a/jnosql-mapping/jnosql-mapping-reflection/src/test/java/org/eclipse/jnosql/mapping/reflection/ReflectionClassConverterTest.java b/jnosql-mapping/jnosql-mapping-reflection/src/test/java/org/eclipse/jnosql/mapping/reflection/ReflectionClassConverterTest.java index 982b497c7..a89075f84 100644 --- a/jnosql-mapping/jnosql-mapping-reflection/src/test/java/org/eclipse/jnosql/mapping/reflection/ReflectionClassConverterTest.java +++ b/jnosql-mapping/jnosql-mapping-reflection/src/test/java/org/eclipse/jnosql/mapping/reflection/ReflectionClassConverterTest.java @@ -22,6 +22,7 @@ import org.eclipse.jnosql.mapping.metadata.MappingType; import org.eclipse.jnosql.mapping.reflection.entities.Actor; import org.eclipse.jnosql.mapping.reflection.entities.Director; +import org.eclipse.jnosql.mapping.reflection.entities.JsonContainer; import org.eclipse.jnosql.mapping.reflection.entities.Machine; import org.eclipse.jnosql.mapping.reflection.entities.NoConstructorEntity; import org.eclipse.jnosql.mapping.reflection.entities.Person; @@ -204,4 +205,10 @@ void shouldCreateEntityMetadataWithConstructor() { assertEquals(5, constructor.parameters().size()); } + + @Test + void shouldHandleCollectionInterfaceChildren() { + ClassConverter converter = new ReflectionClassConverter(); + assertDoesNotThrow(() -> converter.apply(JsonContainer.class)); + } } \ No newline at end of file diff --git a/jnosql-mapping/jnosql-mapping-reflection/src/test/java/org/eclipse/jnosql/mapping/reflection/entities/JsonContainer.java b/jnosql-mapping/jnosql-mapping-reflection/src/test/java/org/eclipse/jnosql/mapping/reflection/entities/JsonContainer.java new file mode 100644 index 000000000..d234e1e32 --- /dev/null +++ b/jnosql-mapping/jnosql-mapping-reflection/src/test/java/org/eclipse/jnosql/mapping/reflection/entities/JsonContainer.java @@ -0,0 +1,38 @@ +/* + * Copyright (c) 2024 Contributors to the Eclipse Foundation + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * and Apache License v2.0 which accompanies this distribution. + * The Eclipse Public License is available at http://www.eclipse.org/legal/epl-v10.html + * and the Apache License v2.0 is available at http://www.opensource.org/licenses/apache2.0.php. + * + * You may elect to redistribute this code under either of these licenses. + * + * Contributors: + * + * Jesse Gallagher + */ +package org.eclipse.jnosql.mapping.reflection.entities; + +import jakarta.json.JsonArray; +import jakarta.json.JsonObject; +import jakarta.nosql.Column; + +/** + * This class is intended to test the behavior of Map- and + * Collection-compatible members where the type does not + * directly contain the generic parameters. + */ +public class JsonContainer { + public interface JsonObjectChild extends JsonObject {} + public static abstract class JsonArrayChild implements JsonArray {} + + @Column + private JsonObject body; + @Column + private JsonArray tags; + @Column + private JsonObjectChild childBody; + @Column + private JsonArrayChild childTags; +}