diff --git a/tooling/metamodel-generator/src/jakartaData/java/org/hibernate/processor/test/data/generic/MyActualEntity.java b/tooling/metamodel-generator/src/jakartaData/java/org/hibernate/processor/test/data/generic/MyActualEntity.java new file mode 100644 index 000000000000..d6e5db8c8eeb --- /dev/null +++ b/tooling/metamodel-generator/src/jakartaData/java/org/hibernate/processor/test/data/generic/MyActualEntity.java @@ -0,0 +1,12 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * Copyright Red Hat Inc. and Hibernate Authors + */ +package org.hibernate.processor.test.data.generic; + +import jakarta.persistence.Entity; + +@Entity +public class MyActualEntity extends MyMappedSuperclass { + public String myString; +} diff --git a/tooling/metamodel-generator/src/jakartaData/java/org/hibernate/processor/test/data/generic/MyActualEntityRepository.java b/tooling/metamodel-generator/src/jakartaData/java/org/hibernate/processor/test/data/generic/MyActualEntityRepository.java new file mode 100644 index 000000000000..83001da8657b --- /dev/null +++ b/tooling/metamodel-generator/src/jakartaData/java/org/hibernate/processor/test/data/generic/MyActualEntityRepository.java @@ -0,0 +1,12 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * Copyright Red Hat Inc. and Hibernate Authors + */ +package org.hibernate.processor.test.data.generic; + +import jakarta.data.repository.CrudRepository; +import jakarta.data.repository.Repository; + +@Repository +public interface MyActualEntityRepository extends CrudRepository { +} diff --git a/tooling/metamodel-generator/src/jakartaData/java/org/hibernate/processor/test/data/generic/MyMappedSuperclass.java b/tooling/metamodel-generator/src/jakartaData/java/org/hibernate/processor/test/data/generic/MyMappedSuperclass.java new file mode 100644 index 000000000000..74c3bc9754b1 --- /dev/null +++ b/tooling/metamodel-generator/src/jakartaData/java/org/hibernate/processor/test/data/generic/MyMappedSuperclass.java @@ -0,0 +1,17 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * Copyright Red Hat Inc. and Hibernate Authors + */ +package org.hibernate.processor.test.data.generic; + +import jakarta.persistence.Id; +import jakarta.persistence.MappedSuperclass; + +import java.io.Serializable; + +@MappedSuperclass +public class MyMappedSuperclass { + @Id + private ID id; + +} diff --git a/tooling/metamodel-generator/src/jakartaData/java/org/hibernate/processor/test/data/generic/MyMappedSuperclassTest.java b/tooling/metamodel-generator/src/jakartaData/java/org/hibernate/processor/test/data/generic/MyMappedSuperclassTest.java new file mode 100644 index 000000000000..967b7ef7ed38 --- /dev/null +++ b/tooling/metamodel-generator/src/jakartaData/java/org/hibernate/processor/test/data/generic/MyMappedSuperclassTest.java @@ -0,0 +1,25 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * Copyright Red Hat Inc. and Hibernate Authors + */ +package org.hibernate.processor.test.data.generic; + +import org.hibernate.processor.test.util.CompilationTest; +import org.hibernate.processor.test.util.TestUtil; +import org.hibernate.processor.test.util.WithClasses; +import org.junit.jupiter.api.Test; + +import static org.hibernate.processor.test.util.TestUtil.assertMetamodelClassGeneratedFor; + +@CompilationTest +public class MyMappedSuperclassTest { + + @Test + @WithClasses({MyActualEntity.class, MyActualEntityRepository.class, MyMappedSuperclass.class}) + void smoke() { + System.out.println( TestUtil.getMetaModelSourceAsString( MyMappedSuperclass.class ) ); + System.out.println( TestUtil.getMetaModelSourceAsString( MyActualEntity.class ) ); + assertMetamodelClassGeneratedFor( MyMappedSuperclass.class ); + assertMetamodelClassGeneratedFor( MyActualEntity.class ); + } +} diff --git a/tooling/metamodel-generator/src/main/java/org/hibernate/processor/annotation/AnnotationMetaEntity.java b/tooling/metamodel-generator/src/main/java/org/hibernate/processor/annotation/AnnotationMetaEntity.java index f578c353464a..d10b371b2a28 100644 --- a/tooling/metamodel-generator/src/main/java/org/hibernate/processor/annotation/AnnotationMetaEntity.java +++ b/tooling/metamodel-generator/src/main/java/org/hibernate/processor/annotation/AnnotationMetaEntity.java @@ -67,6 +67,7 @@ import static java.beans.Introspector.decapitalize; import static java.lang.Boolean.FALSE; import static java.util.Collections.emptyList; +import static java.util.Objects.requireNonNullElse; import static java.util.stream.Collectors.toList; import static javax.lang.model.util.ElementFilter.fieldsIn; import static javax.lang.model.util.ElementFilter.methodsIn; @@ -100,6 +101,7 @@ import static org.hibernate.processor.util.TypeUtils.implementsInterface; import static org.hibernate.processor.util.TypeUtils.primitiveClassMatchesKind; import static org.hibernate.processor.util.TypeUtils.propertyName; +import static org.hibernate.processor.util.TypeUtils.resolveTypeMirror; /** * Class used to collect meta information about an annotated type (entity, embeddable or mapped superclass). @@ -2526,7 +2528,15 @@ enum FieldType { return null; } - if ( checkParameterType( entityType, param, memberType( member ) ) ) { + final var memberType = memberType( member ); + final var attributeType = requireNonNullElse( + resolveTypeMirror( + entityType, + member.getEnclosingElement(), + memberType.toString() + ), memberType + ); + if ( checkParameterType( entityType, param, attributeType ) ) { return FieldType.MULTIVALUED; } else if ( containsAnnotation( param, PATTERN ) ) { diff --git a/tooling/metamodel-generator/src/main/java/org/hibernate/processor/util/TypeUtils.java b/tooling/metamodel-generator/src/main/java/org/hibernate/processor/util/TypeUtils.java index 654763143291..a97a3061bc24 100644 --- a/tooling/metamodel-generator/src/main/java/org/hibernate/processor/util/TypeUtils.java +++ b/tooling/metamodel-generator/src/main/java/org/hibernate/processor/util/TypeUtils.java @@ -30,13 +30,17 @@ import javax.lang.model.util.Elements; import javax.lang.model.util.SimpleTypeVisitor8; import javax.tools.Diagnostic; +import java.util.Collection; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; +import java.util.Objects; import java.util.Set; +import java.util.stream.Stream; import static java.beans.Introspector.decapitalize; +import static java.util.stream.Stream.concat; import static org.hibernate.internal.util.StringHelper.split; import static org.hibernate.processor.util.AccessTypeInformation.DEFAULT_ACCESS_TYPE; import static org.hibernate.processor.util.Constants.ACCESS; @@ -779,4 +783,41 @@ public static boolean isPrimitive(String paramType) { public static final Set PRIMITIVE_TYPES = Set.of("boolean", "char", "long", "int", "short", "byte", "double", "float"); + + public static TypeMirror resolveTypeMirror(TypeElement typeElement, Element element, String name) { + final var mirrorMap = resolveTypeParameters( typeElement.asType(), element, Map.of(), new HashSet<>() ); + return mirrorMap == null ? null : mirrorMap.get( name ); + } + + private static Map resolveTypeParameters(TypeMirror type, Element element, Map parametersMap, Collection visited) { + if ( !(type instanceof DeclaredType declaredType + && declaredType.asElement() instanceof TypeElement typeElement) ) { + return null; + } + if ( !visited.add( typeElement ) ) { + return null; + } + final var generic = typeElement.getTypeParameters(); + final var map = new HashMap(); + var typeArguments = declaredType.getTypeArguments(); + if ( !(typeArguments.isEmpty() || generic.size() == typeArguments.size()) ) { + return null; + } + for ( var n = 0; n < generic.size(); ++n ) { + final var mirror = typeArguments.isEmpty() + ? generic.get( 0 ).getBounds().get( 0 ) + : typeArguments.get( n ); + final var value = mirror.toString(); + map.put( generic.get( n ).toString(), parametersMap.getOrDefault( value, mirror ) ); + } + if ( typeElement.equals( element ) ) { + return map; + } + return concat( + Stream.of( typeElement.getSuperclass() ), + typeElement.getInterfaces().stream() + ).map( tm -> resolveTypeParameters( tm, element, map, visited ) ) + .filter( Objects::nonNull ) + .findFirst().orElse( null ); + } }