diff --git a/hibernate-core/src/main/java/org/hibernate/metamodel/model/domain/JpaMetamodel.java b/hibernate-core/src/main/java/org/hibernate/metamodel/model/domain/JpaMetamodel.java index 206eb6cc4de1..ab841b268b26 100644 --- a/hibernate-core/src/main/java/org/hibernate/metamodel/model/domain/JpaMetamodel.java +++ b/hibernate-core/src/main/java/org/hibernate/metamodel/model/domain/JpaMetamodel.java @@ -77,16 +77,40 @@ public interface JpaMetamodel extends Metamodel { EntityDomainType resolveHqlEntityReference(String entityName); /** - * Same as {@link #managedType} except {@code null} is returned rather + * Same as {@link #managedType(Class)} except {@code null} is returned rather * than throwing an exception */ - ManagedDomainType findManagedType(Class cls); + @Nullable ManagedDomainType findManagedType(Class cls); /** - * Same as {@link #entity} except {@code null} is returned rather + * Same as {@link #entity(Class)} except {@code null} is returned rather * than throwing an exception */ - EntityDomainType findEntityType(Class cls); + @Nullable EntityDomainType findEntityType(Class cls); + + /** + * Same as {@link #embeddable(Class)} except {@code null} is returned rather + * than throwing an exception + */ + @Nullable EmbeddableDomainType findEmbeddableType(Class cls); + + /** + * Same as {@link #managedType(String)} except {@code null} is returned rather + * than throwing an exception + */ + @Nullable ManagedDomainType findManagedType(@Nullable String typeName); + + /** + * Same as {@link #entity(String)} except {@code null} is returned rather + * than throwing an exception + */ + @Nullable EntityDomainType findEntityType(@Nullable String entityName); + + /** + * Same as {@link #embeddable(String)} except {@code null} is returned rather + * than throwing an exception + */ + @Nullable EmbeddableDomainType findEmbeddableType(@Nullable String embeddableName); String qualifyImportableName(String queryName); diff --git a/hibernate-core/src/main/java/org/hibernate/metamodel/model/domain/internal/JpaMetamodelImpl.java b/hibernate-core/src/main/java/org/hibernate/metamodel/model/domain/internal/JpaMetamodelImpl.java index 4367821420c9..23e036f02eea 100644 --- a/hibernate-core/src/main/java/org/hibernate/metamodel/model/domain/internal/JpaMetamodelImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/metamodel/model/domain/internal/JpaMetamodelImpl.java @@ -132,35 +132,62 @@ public JpaCompliance getJpaCompliance() { } @Override - public ManagedDomainType managedType(String typeName) { + public @Nullable ManagedDomainType findManagedType(@Nullable String typeName) { //noinspection unchecked return typeName == null ? null : (ManagedDomainType) managedTypeByName.get( typeName ); } @Override - public EntityDomainType entity(String entityName) { + public ManagedDomainType managedType(String typeName) { + final ManagedDomainType managedType = findManagedType( typeName ); + if ( managedType == null ) { + throw new IllegalArgumentException("Not a managed type: " + typeName); + } + return managedType; + } + + @Override + @Nullable public EntityDomainType findEntityType(@Nullable String entityName) { if ( entityName == null ) { return null; } final ManagedDomainType managedType = managedTypeByName.get( entityName ); - if ( !( managedType instanceof EntityDomainType ) ) { + if ( !(managedType instanceof EntityDomainType) ) { return null; } //noinspection unchecked - return (EntityDomainType) managedType; + return (EntityDomainType) managedType; } @Override - public EmbeddableDomainType embeddable(String embeddableName) { + public EntityDomainType entity(String entityName) { + final EntityDomainType entityType = findEntityType( entityName ); + if ( entityType == null ) { + // per JPA + throw new IllegalArgumentException("Not an entity: " + entityName); + } + return (EntityDomainType) entityType; + } + + @Override + @Nullable public EmbeddableDomainType findEmbeddableType(@Nullable String embeddableName) { if ( embeddableName == null ) { return null; } final ManagedDomainType managedType = managedTypeByName.get( embeddableName ); - if ( !( managedType instanceof EmbeddableDomainType ) ) { + if ( !(managedType instanceof EmbeddableDomainType) ) { return null; } - //noinspection unchecked - return (EmbeddableDomainType) managedType; + return (EmbeddableDomainType) managedType; + } + + @Override + public EmbeddableDomainType embeddable(String embeddableName) { + final EmbeddableDomainType embeddableType = findEmbeddableType( embeddableName ); + if ( embeddableType == null ) { + throw new IllegalArgumentException("Not an embeddable: " + embeddableName); + } + return (EmbeddableDomainType) embeddableType; } @Override @@ -172,9 +199,9 @@ public EntityDomainType getHqlEntityReference(String entityName) { entityName = importInfo.importedName; } - final EntityDomainType entityDescriptor = entity( entityName ); + final EntityDomainType entityDescriptor = findEntityType( entityName ); if ( entityDescriptor != null ) { - return entityDescriptor; + return (EntityDomainType) entityDescriptor; } if ( loadedClass == null ) { @@ -200,13 +227,23 @@ public EntityDomainType resolveHqlEntityReference(String entityName) { } @Override - public ManagedDomainType findManagedType(Class cls) { + @Nullable public ManagedDomainType findManagedType(Class cls) { //noinspection unchecked return (ManagedDomainType) managedTypeByClass.get( cls ); } @Override - public EntityDomainType findEntityType(Class cls) { + public ManagedDomainType managedType(Class cls) { + final ManagedDomainType type = findManagedType( cls ); + if ( type == null ) { + // per JPA + throw new IllegalArgumentException( "Not a managed type: " + cls ); + } + return type; + } + + @Override + @Nullable public EntityDomainType findEntityType(Class cls) { final ManagedType type = managedTypeByClass.get( cls ); if ( !( type instanceof EntityDomainType ) ) { return null; @@ -216,35 +253,31 @@ public EntityDomainType findEntityType(Class cls) { } @Override - public ManagedDomainType managedType(Class cls) { - final ManagedType type = managedTypeByClass.get( cls ); - if ( type == null ) { - // per JPA - throw new IllegalArgumentException( "Not a managed type: " + cls ); + public EntityDomainType entity(Class cls) { + final EntityDomainType entityType = findEntityType( cls ); + if ( entityType == null ) { + throw new IllegalArgumentException( "Not an entity: " + cls.getName() ); } - - //noinspection unchecked - return (ManagedDomainType) type; + return entityType; } @Override - public EntityDomainType entity(Class cls) { + public @Nullable EmbeddableDomainType findEmbeddableType(Class cls) { final ManagedType type = managedTypeByClass.get( cls ); - if ( !( type instanceof EntityDomainType ) ) { - throw new IllegalArgumentException( "Not an entity: " + cls.getName() ); + if ( !( type instanceof EmbeddableDomainType ) ) { + return null; } //noinspection unchecked - return (EntityDomainType) type; + return (EmbeddableDomainType) type; } @Override public EmbeddableDomainType embeddable(Class cls) { - final ManagedType type = managedTypeByClass.get( cls ); - if ( !( type instanceof EmbeddableDomainType ) ) { + final EmbeddableDomainType embeddableType = findEmbeddableType( cls ); + if ( embeddableType == null ) { throw new IllegalArgumentException( "Not an embeddable: " + cls.getName() ); } - //noinspection unchecked - return (EmbeddableDomainType) type; + return embeddableType; } private Collection> getAllManagedTypes() { @@ -428,7 +461,7 @@ private void applyNamedEntityGraphs(Collection named definition.getEntityName(), definition.getJpaEntityName() ); - final EntityDomainType entityType = entity( definition.getEntityName() ); + final EntityDomainType entityType = (EntityDomainType) findEntityType( definition.getEntityName() ); if ( entityType == null ) { throw new IllegalArgumentException( "Attempted to register named entity graph [" + definition.getRegisteredName() @@ -442,7 +475,7 @@ private void applyNamedEntityGraphs(Collection named final NamedEntityGraph namedEntityGraph = definition.getAnnotation(); if ( namedEntityGraph.includeAllAttributes() ) { - for ( Attribute attribute : entityType.getAttributes() ) { + for ( Attribute attribute : entityType.getAttributes() ) { entityGraph.addAttributeNodes( attribute ); } } diff --git a/hibernate-core/src/main/java/org/hibernate/metamodel/model/domain/internal/MappingMetamodelImpl.java b/hibernate-core/src/main/java/org/hibernate/metamodel/model/domain/internal/MappingMetamodelImpl.java index 05f3d784bab4..3062c3f27a45 100644 --- a/hibernate-core/src/main/java/org/hibernate/metamodel/model/domain/internal/MappingMetamodelImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/metamodel/model/domain/internal/MappingMetamodelImpl.java @@ -19,6 +19,7 @@ import java.util.function.Function; import java.util.stream.Stream; +import org.checkerframework.checker.nullness.qual.Nullable; import org.hibernate.EntityNameResolver; import org.hibernate.HibernateException; import org.hibernate.MappingException; @@ -490,16 +491,31 @@ public Set> getEmbeddables() { return jpaMetamodel.getEmbeddables(); } + @Override + public @Nullable ManagedDomainType findManagedType(@Nullable String typeName) { + return jpaMetamodel.findManagedType( typeName ); + } + @Override public ManagedDomainType managedType(String typeName) { return jpaMetamodel.managedType( typeName ); } + @Override + public @Nullable EntityDomainType findEntityType(@Nullable String entityName) { + return jpaMetamodel.findEntityType( entityName ); + } + @Override public EntityDomainType entity(String entityName) { return jpaMetamodel.entity( entityName ); } + @Override + public @Nullable EmbeddableDomainType findEmbeddableType(@Nullable String embeddableName) { + return jpaMetamodel.findEmbeddableType( embeddableName ); + } + @Override public EmbeddableDomainType embeddable(String embeddableName) { return jpaMetamodel.embeddable( embeddableName ); @@ -525,6 +541,11 @@ public EntityDomainType findEntityType(Class cls) { return jpaMetamodel.findEntityType( cls ); } + @Override + public @Nullable EmbeddableDomainType findEmbeddableType(Class cls) { + return jpaMetamodel.findEmbeddableType( cls ); + } + @Override public String qualifyImportableName(String queryName) { return jpaMetamodel.qualifyImportableName( queryName ); diff --git a/hibernate-core/src/main/java/org/hibernate/query/hql/internal/FullyQualifiedReflectivePathTerminal.java b/hibernate-core/src/main/java/org/hibernate/query/hql/internal/FullyQualifiedReflectivePathTerminal.java index dc7e79be1dd4..5165ef1ee90a 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/hql/internal/FullyQualifiedReflectivePathTerminal.java +++ b/hibernate-core/src/main/java/org/hibernate/query/hql/internal/FullyQualifiedReflectivePathTerminal.java @@ -75,7 +75,7 @@ public FullyQualifiedReflectivePathTerminal copy(SqmCopyContext context) { // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // See if it is an entity-type literal - final EntityDomainType entityDescriptor = creationContext.getJpaMetamodel().entity( fullPath ); + final EntityDomainType entityDescriptor = creationContext.getJpaMetamodel().findEntityType( fullPath ); if ( entityDescriptor != null ) { return new SqmLiteralEntityType<>( entityDescriptor, creationContext.getNodeBuilder() ); } diff --git a/hibernate-core/src/main/java/org/hibernate/query/hql/internal/SemanticQueryBuilder.java b/hibernate-core/src/main/java/org/hibernate/query/hql/internal/SemanticQueryBuilder.java index 17b152e5642f..e182d8f3681b 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/hql/internal/SemanticQueryBuilder.java +++ b/hibernate-core/src/main/java/org/hibernate/query/hql/internal/SemanticQueryBuilder.java @@ -1298,12 +1298,13 @@ private SqmFromClause buildInferredFromClause(HqlParser.SelectClauseContext sele private EntityDomainType getResultEntity() { final JpaMetamodelImplementor jpaMetamodel = creationContext.getJpaMetamodel(); if ( expectedResultEntity != null ) { - final EntityDomainType entityDescriptor = jpaMetamodel.entity( expectedResultEntity ); + final EntityDomainType entityDescriptor = jpaMetamodel.findEntityType( expectedResultEntity ); if ( entityDescriptor == null ) { throw new SemanticException( "Query has no 'from' clause, and the result type '" + expectedResultEntity + "' is not an entity type", query ); } - return entityDescriptor; + //noinspection unchecked + return (EntityDomainType) entityDescriptor; } else if ( expectedResultType != null ) { final EntityDomainType entityDescriptor = jpaMetamodel.findEntityType( expectedResultType ); diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/sql/BaseSqmToSqlAstConverter.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/sql/BaseSqmToSqlAstConverter.java index 60c28bae449e..a0b528e40931 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/sql/BaseSqmToSqlAstConverter.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/sql/BaseSqmToSqlAstConverter.java @@ -3084,9 +3084,8 @@ private void registerEntityNameUsage( if ( tableGroup.getModelPart() instanceof EmbeddableValuedModelPart ) { persister = null; final EmbeddableDomainType embeddableDomainType = creationContext.getSessionFactory() - .getRuntimeMetamodels() .getJpaMetamodel() - .embeddable( treatTargetTypeName ); + .findEmbeddableType( treatTargetTypeName ); if ( embeddableDomainType == null || !embeddableDomainType.isPolymorphic() ) { return; } diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/collection/bag/PersistentBagContainsTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/collection/bag/PersistentBagContainsTest.java index edaeb9a7d3c5..c0df886a817b 100644 --- a/hibernate-core/src/test/java/org/hibernate/orm/test/collection/bag/PersistentBagContainsTest.java +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/collection/bag/PersistentBagContainsTest.java @@ -16,6 +16,7 @@ import jakarta.persistence.ManyToOne; import jakarta.persistence.OneToMany; +import org.hibernate.annotations.processing.Exclude; import org.hibernate.testing.TestForIssue; import org.hibernate.testing.orm.junit.DomainModel; import org.hibernate.testing.orm.junit.SessionFactory; @@ -36,6 +37,7 @@ } ) @SessionFactory +@Exclude public class PersistentBagContainsTest { /** diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/event/entity/MergeListPreAndPostPersistTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/event/entity/MergeListPreAndPostPersistTest.java index 71f5182194c0..ddf530ac65c2 100644 --- a/hibernate-core/src/test/java/org/hibernate/orm/test/event/entity/MergeListPreAndPostPersistTest.java +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/event/entity/MergeListPreAndPostPersistTest.java @@ -15,6 +15,7 @@ import jakarta.persistence.OneToMany; import org.hibernate.Session; +import org.hibernate.annotations.processing.Exclude; import org.hibernate.event.service.spi.EventListenerRegistry; import org.hibernate.event.spi.EventType; import org.hibernate.event.spi.PostInsertEvent; @@ -33,6 +34,7 @@ * @author Gail Badner */ @TestForIssue( jiraKey = "HHH-9979") +@Exclude public class MergeListPreAndPostPersistTest extends BaseCoreFunctionalTestCase { protected Class[] getAnnotatedClasses() { diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/hql/QuotedIdentifierTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/hql/QuotedIdentifierTest.java index 1c74f13068e5..5b5cdf1c463d 100644 --- a/hibernate-core/src/test/java/org/hibernate/orm/test/hql/QuotedIdentifierTest.java +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/hql/QuotedIdentifierTest.java @@ -8,6 +8,7 @@ import java.util.List; +import org.hibernate.annotations.processing.Exclude; import org.hibernate.testing.junit4.BaseCoreFunctionalTestCase; import org.junit.After; import org.junit.Before; @@ -26,6 +27,7 @@ /** * @author Christian Beikov */ +@Exclude public class QuotedIdentifierTest extends BaseCoreFunctionalTestCase { private Person person; diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/jpa/compliance/GetJavaTypeTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/jpa/compliance/GetJavaTypeTest.java deleted file mode 100644 index 7a2c13865256..000000000000 --- a/hibernate-core/src/test/java/org/hibernate/orm/test/jpa/compliance/GetJavaTypeTest.java +++ /dev/null @@ -1,55 +0,0 @@ -/* - * Hibernate, Relational Persistence for Idiomatic Java - * - * License: GNU Lesser General Public License (LGPL), version 2.1 or later. - * See the lgpl.txt file in the root directory or . - */ -package org.hibernate.orm.test.jpa.compliance; - -import org.hibernate.testing.orm.junit.EntityManagerFactoryScope; -import org.hibernate.testing.orm.junit.Jpa; -import org.junit.jupiter.api.Test; - -import jakarta.persistence.Entity; -import jakarta.persistence.Id; -import jakarta.persistence.Table; -import jakarta.persistence.metamodel.Attribute; -import jakarta.persistence.metamodel.ManagedType; -import jakarta.persistence.metamodel.Metamodel; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertNotNull; - -@Jpa( - annotatedClasses = GetJavaTypeTest.Person.class -) -public class GetJavaTypeTest { - - @Test - public void getJavaType(EntityManagerFactoryScope scope) { - scope.inEntityManager( - entityManager -> { - Metamodel metaModel = entityManager.getMetamodel(); - if ( metaModel != null ) { - ManagedType mTypeOrder = metaModel.managedType( Person.class ); - assertNotNull( mTypeOrder ); - Attribute attrib = mTypeOrder.getDeclaredAttribute( "age" ); - assertNotNull( attrib ); - Class pAttribJavaType = attrib.getJavaType(); - assertEquals( "int", pAttribJavaType.getName() ); - } - } - ); - } - - @Entity(name = "Person") - @Table(name = "PERSON_TABLE") - public static class Person { - @Id - private int id; - - private String name; - - private int age; - } -} diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/jpa/compliance/MetamodelTypesTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/jpa/compliance/MetamodelTypesTest.java new file mode 100644 index 000000000000..b615ae248c44 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/jpa/compliance/MetamodelTypesTest.java @@ -0,0 +1,174 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later + * See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html + */ +package org.hibernate.orm.test.jpa.compliance; + +import jakarta.persistence.Embeddable; +import jakarta.persistence.Embedded; +import jakarta.persistence.Entity; +import jakarta.persistence.Id; +import jakarta.persistence.MappedSuperclass; +import jakarta.persistence.metamodel.Attribute; +import jakarta.persistence.metamodel.ManagedType; +import jakarta.persistence.metamodel.MappedSuperclassType; +import jakarta.persistence.metamodel.Metamodel; +import org.hibernate.engine.spi.SessionFactoryImplementor; +import org.hibernate.metamodel.model.domain.EmbeddableDomainType; +import org.hibernate.metamodel.model.domain.EntityDomainType; +import org.hibernate.metamodel.model.domain.JpaMetamodel; +import org.hibernate.metamodel.model.domain.ManagedDomainType; +import org.hibernate.testing.orm.junit.EntityManagerFactoryScope; +import org.hibernate.testing.orm.junit.Jira; +import org.hibernate.testing.orm.junit.Jpa; +import org.junit.jupiter.api.Test; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.fail; + +@Jpa(annotatedClasses = { + MetamodelTypesTest.BaseEntity.class, + MetamodelTypesTest.Person.class, + MetamodelTypesTest.Order.class, +}) +@Jira( "https://hibernate.atlassian.net/browse/HHH-18683" ) +public class MetamodelTypesTest { + @Test + public void getJavaType(EntityManagerFactoryScope scope) { + scope.inEntityManager( entityManager -> { + final Metamodel metaModel = entityManager.getMetamodel(); + final ManagedType mTypeOrder = metaModel.managedType( Person.class ); + assertNotNull( mTypeOrder ); + final Attribute attrib = mTypeOrder.getDeclaredAttribute( "age" ); + assertNotNull( attrib ); + final Class pAttribJavaType = attrib.getJavaType(); + assertEquals( "int", pAttribJavaType.getName() ); + } ); + } + + @Test + public void entityTypeTest(EntityManagerFactoryScope scope) { + scope.inEntityManager( entityManager -> { + final JpaMetamodel jpaMetamodel = entityManager.getEntityManagerFactory() + .unwrap( SessionFactoryImplementor.class ).getJpaMetamodel(); + + // Existing entity type + final EntityDomainType personType = jpaMetamodel.entity( Person.class ); + assertThat( personType ).isNotNull().isSameAs( jpaMetamodel.findEntityType( Person.class ) ) + .extracting( EntityDomainType::getName ).isEqualTo( "Person" ); + assertThat( jpaMetamodel.entity( Person.class.getName() ) ).isSameAs( + jpaMetamodel.findEntityType( Person.class.getName() ) ).isSameAs( personType ); + + // Nonexistent entity type + assertThat( jpaMetamodel.findEntityType( Order.class ) ).isNull(); + assertThat( jpaMetamodel.findEntityType( "AnotherEntity" ) ).isNull(); + try { + jpaMetamodel.entity( "AnotherEntity" ); + fail( "Expected IllegalArgumentException for nonexistent entity type" ); + } + catch (IllegalArgumentException e) { + assertThat( e ).hasMessageContaining( "Not an entity" ); + } + try { + jpaMetamodel.entity( Order.class.getName() ); + fail( "Expected IllegalArgumentException for embeddable type requested as entity" ); + } + catch (IllegalArgumentException e) { + assertThat( e ).hasMessageContaining( "Not an entity" ); + } + } ); + } + + @Test + public void mappedSuperclassTypeTest(EntityManagerFactoryScope scope) { + scope.inEntityManager( entityManager -> { + final JpaMetamodel jpaMetamodel = entityManager.getEntityManagerFactory() + .unwrap( SessionFactoryImplementor.class ).getJpaMetamodel(); + + // Existing mapped superclass type + final ManagedDomainType baseEntityType = jpaMetamodel.managedType( BaseEntity.class ); + assertThat( baseEntityType ).isInstanceOf( MappedSuperclassType.class ) + .isSameAs( jpaMetamodel.findManagedType( BaseEntity.class ) ) + .extracting( ManagedDomainType::getTypeName ).isEqualTo( BaseEntity.class.getName() ); + assertThat( jpaMetamodel.managedType( BaseEntity.class.getName() ) ).isSameAs( + jpaMetamodel.findManagedType( BaseEntity.class.getName() ) ).isSameAs( baseEntityType ); + + // Nonexistent mapped superclass type + assertThat( jpaMetamodel.findManagedType( MetamodelTypesTest.class ) ).isNull(); + assertThat( jpaMetamodel.findManagedType( "AnotherEntity" ) ).isNull(); + try { + jpaMetamodel.managedType( "AnotherEntity" ); + fail( "Expected IllegalArgumentException for nonexistent entity type" ); + } + catch (IllegalArgumentException e) { + assertThat( e ).hasMessageContaining( "Not a managed type" ); + } + try { + jpaMetamodel.managedType( MetamodelTypesTest.class ); + fail( "Expected IllegalArgumentException for embeddable type requested as entity" ); + } + catch (IllegalArgumentException e) { + assertThat( e ).hasMessageContaining( "Not a managed type" ); + } + } ); + } + + @Test + public void embeddableTypeTest(EntityManagerFactoryScope scope) { + scope.inEntityManager( entityManager -> { + final JpaMetamodel jpaMetamodel = entityManager.getEntityManagerFactory() + .unwrap( SessionFactoryImplementor.class ).getJpaMetamodel(); + + // Existing mapped superclass type + final EmbeddableDomainType embeddableType = jpaMetamodel.embeddable( Order.class ); + assertThat( embeddableType ).isNotNull().isSameAs( jpaMetamodel.findEmbeddableType( Order.class ) ) + .extracting( ManagedDomainType::getTypeName ).isEqualTo( Order.class.getName() ); + assertThat( jpaMetamodel.embeddable( Order.class.getName() ) ).isSameAs( + jpaMetamodel.findEmbeddableType( Order.class.getName() ) ).isSameAs( embeddableType ); + + // Nonexistent mapped superclass type + assertThat( jpaMetamodel.findEmbeddableType( BaseEntity.class ) ).isNull(); + assertThat( jpaMetamodel.findEmbeddableType( "AnotherEmbeddable" ) ).isNull(); + try { + jpaMetamodel.embeddable( "AnotherEmbeddable" ); + fail( "Expected IllegalArgumentException for nonexistent entity type" ); + } + catch (IllegalArgumentException e) { + assertThat( e ).hasMessageContaining( "Not an embeddable" ); + } + try { + jpaMetamodel.embeddable( Person.class ); + fail( "Expected IllegalArgumentException for embeddable type requested as entity" ); + } + catch (IllegalArgumentException e) { + assertThat( e ).hasMessageContaining( "Not an embeddable" ); + } + } ); + } + + @MappedSuperclass + public static abstract class BaseEntity { + @Id + private int id; + } + + @Entity(name = "Person") + public static class Person extends BaseEntity { + private String name; + + private int age; + + @Embedded + private Order order; + } + + @Embeddable + public static class Order { + private Integer number; + private String item; + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/schemaupdate/SchemaUpdateWithUseJdbcMetadataDefaultsSettingToFalseAndQuotedNameTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/schemaupdate/SchemaUpdateWithUseJdbcMetadataDefaultsSettingToFalseAndQuotedNameTest.java index ca240bd17485..7d4b4851bbf3 100644 --- a/hibernate-core/src/test/java/org/hibernate/orm/test/schemaupdate/SchemaUpdateWithUseJdbcMetadataDefaultsSettingToFalseAndQuotedNameTest.java +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/schemaupdate/SchemaUpdateWithUseJdbcMetadataDefaultsSettingToFalseAndQuotedNameTest.java @@ -16,6 +16,7 @@ import jakarta.persistence.Entity; import jakarta.persistence.Id; +import org.hibernate.annotations.processing.Exclude; import org.hibernate.boot.MetadataSources; import org.hibernate.boot.registry.StandardServiceRegistry; import org.hibernate.boot.registry.StandardServiceRegistryBuilder; @@ -38,6 +39,7 @@ import static org.junit.Assert.assertThat; @TestForIssue(jiraKey = "HHH-13788") +@Exclude public class SchemaUpdateWithUseJdbcMetadataDefaultsSettingToFalseAndQuotedNameTest { private File updateOutputFile; diff --git a/tooling/metamodel-generator/src/jakartaData/java/org/hibernate/processor/test/data/innerclass/Dummy.java b/tooling/metamodel-generator/src/jakartaData/java/org/hibernate/processor/test/data/innerclass/Dummy.java new file mode 100644 index 000000000000..9668406bb864 --- /dev/null +++ b/tooling/metamodel-generator/src/jakartaData/java/org/hibernate/processor/test/data/innerclass/Dummy.java @@ -0,0 +1,84 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later. + * See the lgpl.txt file in the root directory or . + */ +package org.hibernate.processor.test.data.innerclass; + +import jakarta.persistence.Embeddable; +import jakarta.persistence.Entity; +import jakarta.persistence.Id; +import jakarta.persistence.MappedSuperclass; +import jakarta.persistence.NamedQuery; + +public class Dummy { + @Entity(name = "Inner") + @NamedQuery(name = "allInner", query = "from Inner") + public static class Inner extends Persona { + @Id + Integer id; + + String name; + + public Integer getId() { + return id; + } + + @Override + public void setId(Integer id) { + this.id = id; + } + + @Override + public String getName() { + return name; + } + + @Override + public void setName(String name) { + this.name = name; + } + } + + @Embeddable + public static class DummyEmbeddable { + private String name; + private int value; + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public int getValue() { + return value; + } + + public void setValue(int value) { + this.value = value; + } + } + + @MappedSuperclass + public abstract static class Persona { + private String city; + + public String getCity() { + return city; + } + + public void setCity(String city) { + this.city = city; + } + + public abstract void setId(Integer id); + + public abstract String getName(); + + public abstract void setName(String name); + } +} diff --git a/tooling/metamodel-generator/src/jakartaData/java/org/hibernate/processor/test/data/innerclass/InnerClassTest.java b/tooling/metamodel-generator/src/jakartaData/java/org/hibernate/processor/test/data/innerclass/InnerClassTest.java new file mode 100644 index 000000000000..c6322d321606 --- /dev/null +++ b/tooling/metamodel-generator/src/jakartaData/java/org/hibernate/processor/test/data/innerclass/InnerClassTest.java @@ -0,0 +1,88 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later. + * See the lgpl.txt file in the root directory or . + */ +package org.hibernate.processor.test.data.innerclass; + +import jakarta.persistence.Entity; +import jakarta.persistence.Id; +import jakarta.persistence.NamedQuery; +import org.hibernate.processor.test.data.innerclass.InnerClassTest.One.Two; +import org.hibernate.processor.test.util.CompilationTest; +import org.hibernate.processor.test.util.WithClasses; +import org.junit.Test; + +import static org.hibernate.processor.test.util.TestUtil.assertMetamodelClassGeneratedFor; +import static org.hibernate.processor.test.util.TestUtil.assertNoMetamodelClassGeneratedFor; +import static org.hibernate.processor.test.util.TestUtil.getMetaModelSourceAsString; +import static org.junit.Assert.assertEquals; + +public class InnerClassTest extends CompilationTest { + + @WithClasses({Person.class, Dummy.class, Inner.class, Two.class}) + @Test + public void test() { + System.out.println( getMetaModelSourceAsString( InnerClassTest.class ) ); + System.out.println( getMetaModelSourceAsString( Dummy.class ) ); + System.out.println( getMetaModelSourceAsString( Person.class ) ); + System.out.println( getMetaModelSourceAsString( InnerClassTest.class, true ) ); + System.out.println( getMetaModelSourceAsString( Dummy.class, true ) ); + System.out.println( getMetaModelSourceAsString( Person.class, true ) ); + assertEquals( + getMetaModelSourceAsString( Inner.class ), + getMetaModelSourceAsString( Two.class ) + ); + assertEquals( + getMetaModelSourceAsString( Inner.class, true ), + getMetaModelSourceAsString( Two.class, true ) + ); + assertMetamodelClassGeneratedFor( Inner.class ); + assertMetamodelClassGeneratedFor( Inner.class, true ); + assertMetamodelClassGeneratedFor( Two.class ); + assertMetamodelClassGeneratedFor( Two.class, true ); + assertMetamodelClassGeneratedFor( Dummy.Inner.class ); + assertMetamodelClassGeneratedFor( Dummy.Inner.class, true ); + assertMetamodelClassGeneratedFor( Person.class ); + assertMetamodelClassGeneratedFor( Person.class, true ); + assertMetamodelClassGeneratedFor( Person.PersonId.class ); + assertNoMetamodelClassGeneratedFor( Dummy.class ); + assertMetamodelClassGeneratedFor( Dummy.DummyEmbeddable.class ); + /*assertNoMetamodelClassGeneratedFor( Dummy.class );*/ + } + + @Entity(name = "Inner") + @NamedQuery(name = "allInner", query = "from Inner") + public static class Inner { + @Id + Integer id; + + String address; + + public Integer getId() { + return id; + } + + public void setId(Integer id) { + this.id = id; + } + + public String getAddress() { + return address; + } + + public void setAddress(String address) { + this.address = address; + } + } + + static class One { + @Entity + static class Two { + @Id + Integer id; + String value; + } + } +} diff --git a/tooling/metamodel-generator/src/jakartaData/java/org/hibernate/processor/test/data/innerclass/Person.java b/tooling/metamodel-generator/src/jakartaData/java/org/hibernate/processor/test/data/innerclass/Person.java new file mode 100644 index 000000000000..480c9b3ea752 --- /dev/null +++ b/tooling/metamodel-generator/src/jakartaData/java/org/hibernate/processor/test/data/innerclass/Person.java @@ -0,0 +1,46 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later. + * See the lgpl.txt file in the root directory or . + */ +package org.hibernate.processor.test.data.innerclass; + +import jakarta.persistence.Embeddable; +import jakarta.persistence.EmbeddedId; +import jakarta.persistence.Entity; + +/** + * @author Hardy Ferentschik + */ +@Entity +public class Person { + @EmbeddedId + private PersonId id; + + private String address; + + @Embeddable + public static class PersonId { + private String name; + private String snn; + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public String getSnn() { + return snn; + } + + public void setSnn(String snn) { + this.snn = snn; + } + } + + +} diff --git a/tooling/metamodel-generator/src/main/java/org/hibernate/processor/ClassWriter.java b/tooling/metamodel-generator/src/main/java/org/hibernate/processor/ClassWriter.java index fc24d625f027..74a814ff80df 100644 --- a/tooling/metamodel-generator/src/main/java/org/hibernate/processor/ClassWriter.java +++ b/tooling/metamodel-generator/src/main/java/org/hibernate/processor/ClassWriter.java @@ -6,11 +6,16 @@ */ package org.hibernate.processor; +import org.hibernate.processor.annotation.InnerClassMetaAttribute; import org.hibernate.processor.model.MetaAttribute; import org.hibernate.processor.model.Metamodel; +import org.hibernate.processor.util.StringUtil; import javax.annotation.processing.FilerException; +import javax.lang.model.element.Element; import javax.lang.model.element.ElementKind; +import javax.lang.model.element.Modifier; +import javax.lang.model.element.PackageElement; import javax.lang.model.element.TypeElement; import javax.tools.Diagnostic; import javax.tools.FileObject; @@ -20,7 +25,12 @@ import java.io.StringWriter; import java.time.OffsetDateTime; import java.time.format.DateTimeFormatter; +import java.util.Arrays; import java.util.List; +import java.util.Set; +import java.util.stream.Collectors; + +import static org.hibernate.processor.util.TypeUtils.isMemberType; /** * Helper class to write the actual metamodel class using the {@link javax.annotation.processing.Filer} API. @@ -41,7 +51,7 @@ public static void writeFile(Metamodel entity, Context context) { String body = generateBody( entity, context ).toString(); FileObject fo = context.getProcessingEnvironment().getFiler().createSourceFile( - getFullyQualifiedClassName( entity, metaModelPackage ), + getFullyQualifiedClassName( entity ), entity.getElement() ); OutputStream os = fo.openOutputStream(); @@ -82,6 +92,8 @@ private static StringBuffer generateBody(Metamodel entity, Context context) { final StringWriter sw = new StringWriter(); try ( PrintWriter pw = new PrintWriter(sw) ) { + pw.println( entity.javadoc() ); + if ( context.addDependentAnnotation() && entity.isInjectable() ) { pw.println( writeScopeAnnotation( entity ) ); } @@ -101,6 +113,16 @@ private static StringBuffer generateBody(Metamodel entity, Context context) { pw.println(); final List members = entity.getMembers(); + for ( MetaAttribute metaMember : members ) { + if (metaMember instanceof InnerClassMetaAttribute) { + InnerClassMetaAttribute innerClass = (InnerClassMetaAttribute) metaMember; + generateBody( innerClass.getMetaEntity(), context ) + .toString().lines() + .forEach(line -> pw.println('\t' + line)); + context.markGenerated( innerClass.getMetaEntity() ); + } + } + for ( MetaAttribute metaMember : members ) { if ( metaMember.hasStringAttribute() ) { pw.println( '\t' + metaMember.getAttributeNameDeclarationString() ); @@ -132,16 +154,29 @@ private static StringBuffer generateBody(Metamodel entity, Context context) { } private static void printClassDeclaration(Metamodel entity, PrintWriter pw) { - pw.print( "public " ); + if ( isMemberType( entity.getElement() ) ) { + final Set modifiers = entity.getElement().getModifiers(); + if ( modifiers.contains( Modifier.PUBLIC ) ) { + pw.print( "public " ); + } + else if ( modifiers.contains( Modifier.PROTECTED ) ) { + pw.print( "protected " ); + } + pw.print( "static " ); + } + else { + pw.print( "public " ); + } if ( !entity.isImplementation() && !entity.isJakartaDataStyle() ) { pw.print( "abstract " ); } pw.print( entity.isJakartaDataStyle() ? "interface " : "class " ); pw.print( getGeneratedClassName(entity) ); - String superClassName = entity.getSupertypeName(); - if ( superClassName != null ) { - pw.print( " extends " + getGeneratedSuperclassName(entity, superClassName) ); + final Element superTypeElement = entity.getSuperTypeElement(); + if ( superTypeElement != null ) { + pw.print( " extends " + + entity.importType(getGeneratedSuperclassName( superTypeElement, entity.isJakartaDataStyle() )) ); } if ( entity.isImplementation() ) { pw.print( entity.getElement().getKind() == ElementKind.CLASS ? " extends " : " implements " ); @@ -151,13 +186,21 @@ private static void printClassDeclaration(Metamodel entity, PrintWriter pw) { pw.println( " {" ); } - private static String getFullyQualifiedClassName(Metamodel entity, String metaModelPackage) { - String fullyQualifiedClassName = ""; - if ( !metaModelPackage.isEmpty() ) { - fullyQualifiedClassName = fullyQualifiedClassName + metaModelPackage + "."; + private static String getFullyQualifiedClassName(Metamodel entity) { + final String metaModelPackage = entity.getPackageName(); + final String packageNamePrefix = !metaModelPackage.isEmpty() ? metaModelPackage + "." : ""; + final String className; + if ( entity.getElement().getKind() == ElementKind.PACKAGE ) { + className = getGeneratedClassName( entity ); } - fullyQualifiedClassName = fullyQualifiedClassName + getGeneratedClassName( entity ); - return fullyQualifiedClassName; + else { + className = Arrays.stream( + entity.getQualifiedName().substring( packageNamePrefix.length() ).split( "\\." ) ) + .map( StringUtil::removeDollar ) + .map( part -> entity.isJakartaDataStyle() ? '_' + part : part + '_' ) + .collect( Collectors.joining( "." ) ); + } + return packageNamePrefix + className; } private static String getGeneratedClassName(Metamodel entity) { @@ -165,20 +208,14 @@ private static String getGeneratedClassName(Metamodel entity) { return entity.isJakartaDataStyle() ? '_' + className : className + '_'; } - private static String getGeneratedSuperclassName(Metamodel entity, String superClassName) { - if ( entity.isJakartaDataStyle() ) { - int lastDot = superClassName.lastIndexOf('.'); - if ( lastDot<0 ) { - return '_' + superClassName; - } - else { - return superClassName.substring(0,lastDot+1) - + '_' + superClassName.substring(lastDot+1); - } - } - else { - return superClassName + '_'; - } + private static String getGeneratedSuperclassName(Element superClassElement, boolean jakartaDataStyle) { + final TypeElement typeElement = (TypeElement) superClassElement; + final String simpleName = typeElement.getSimpleName().toString(); + final Element enclosingElement = typeElement.getEnclosingElement(); + return (enclosingElement instanceof TypeElement + ? getGeneratedSuperclassName( enclosingElement, jakartaDataStyle ) + : ((PackageElement) enclosingElement).getQualifiedName().toString()) + + "." + (jakartaDataStyle ? '_' + simpleName : simpleName + '_'); } private static String writeGeneratedAnnotation(Metamodel entity, Context context) { @@ -226,6 +263,7 @@ private static String writeStaticMetaModelAnnotation(Metamodel entity) { final String annotation = entity.isJakartaDataStyle() ? "jakarta.data.metamodel.StaticMetamodel" : "jakarta.persistence.metamodel.StaticMetamodel"; - return "@" + entity.importType( annotation ) + "(" + entity.getSimpleName() + ".class)"; + final String simpleName = entity.importType( entity.getQualifiedName() ); + return "@" + entity.importType( annotation ) + "(" + simpleName + ".class)"; } } diff --git a/tooling/metamodel-generator/src/main/java/org/hibernate/processor/Context.java b/tooling/metamodel-generator/src/main/java/org/hibernate/processor/Context.java index fce3c353c38b..e748adfbea72 100644 --- a/tooling/metamodel-generator/src/main/java/org/hibernate/processor/Context.java +++ b/tooling/metamodel-generator/src/main/java/org/hibernate/processor/Context.java @@ -304,6 +304,15 @@ public Collection getDataMetaEmbeddables() { return dataMetaEmbeddables.values(); } + public @Nullable Metamodel getMetamodel(String qualifiedName) { + if ( metaEntities.containsKey( qualifiedName ) ) { + return metaEntities.get( qualifiedName ); + } + else { + return metaEmbeddables.get( qualifiedName ); + } + } + public @Nullable Metamodel getMetaAuxiliary(String qualifiedName) { return metaAuxiliaries.get( qualifiedName ); } diff --git a/tooling/metamodel-generator/src/main/java/org/hibernate/processor/HibernateProcessor.java b/tooling/metamodel-generator/src/main/java/org/hibernate/processor/HibernateProcessor.java index d5269487769f..39614378d685 100644 --- a/tooling/metamodel-generator/src/main/java/org/hibernate/processor/HibernateProcessor.java +++ b/tooling/metamodel-generator/src/main/java/org/hibernate/processor/HibernateProcessor.java @@ -9,6 +9,7 @@ import org.checkerframework.checker.nullness.qual.Nullable; import org.hibernate.processor.annotation.AnnotationMetaEntity; import org.hibernate.processor.annotation.AnnotationMetaPackage; +import org.hibernate.processor.annotation.NonManagedMetamodel; import org.hibernate.processor.model.Metamodel; import org.hibernate.processor.util.Constants; import org.hibernate.processor.xml.JpaDescriptorParser; @@ -24,6 +25,7 @@ import javax.lang.model.element.Element; import javax.lang.model.element.ElementKind; import javax.lang.model.element.ExecutableElement; +import javax.lang.model.element.Modifier; import javax.lang.model.element.PackageElement; import javax.lang.model.element.QualifiedNameable; import javax.lang.model.element.TypeElement; @@ -84,6 +86,7 @@ import static org.hibernate.processor.util.TypeUtils.getAnnotationValue; import static org.hibernate.processor.util.TypeUtils.hasAnnotation; import static org.hibernate.processor.util.TypeUtils.isClassOrRecordType; +import static org.hibernate.processor.util.TypeUtils.isMemberType; /** * Main annotation processor. @@ -364,60 +367,102 @@ private void processClasses(RoundEnvironment roundEnvironment) { } for ( Element element : roundEnvironment.getRootElements() ) { - try { - if ( !included( element ) - || hasAnnotation( element, Constants.EXCLUDE ) - || hasPackageAnnotation( element, Constants.EXCLUDE ) ) { - // skip it completely - } - else if ( isEntityOrEmbeddable( element ) ) { - context.logMessage( Diagnostic.Kind.OTHER, "Processing annotated entity class '" + element + "'" ); - handleRootElementAnnotationMirrors( element ); - } - else if ( hasAuxiliaryAnnotations( element ) ) { - context.logMessage( Diagnostic.Kind.OTHER, "Processing annotated class '" + element + "'" ); - handleRootElementAuxiliaryAnnotationMirrors( element ); + processElement( element, null ); + } + } + + private void processElement(Element element, @Nullable Element parent) { + try { + if ( !included( element ) + || hasAnnotation( element, Constants.EXCLUDE ) + || hasPackageAnnotation( element, Constants.EXCLUDE ) + || element.getModifiers().contains( Modifier.PRIVATE ) ) { + // skip it completely + return; + } + else if ( isEntityOrEmbeddable( element ) && !element.getModifiers().contains( Modifier.PRIVATE )) { + context.logMessage( Diagnostic.Kind.OTHER, "Processing annotated entity class '" + element + "'" ); + handleRootElementAnnotationMirrors( element, parent ); + } + else if ( hasAuxiliaryAnnotations( element ) ) { + context.logMessage( Diagnostic.Kind.OTHER, "Processing annotated class '" + element + "'" ); + handleRootElementAuxiliaryAnnotationMirrors( element ); + } + else if (element instanceof TypeElement) { + TypeElement typeElement = (TypeElement) element; + final AnnotationMirror repository = getAnnotationMirror( element, JD_REPOSITORY ); + if ( repository != null ) { + final AnnotationValue provider = getAnnotationValue( repository, "provider" ); + if ( provider == null + || provider.getValue().toString().isEmpty() + || provider.getValue().toString().equalsIgnoreCase("hibernate") ) { + context.logMessage( Diagnostic.Kind.OTHER, "Processing repository class '" + element + "'" ); + final AnnotationMetaEntity metaEntity = + AnnotationMetaEntity.create( typeElement, context ); + if ( metaEntity.isInitialized() ) { + context.addMetaAuxiliary( metaEntity.getQualifiedName(), metaEntity ); + } + // otherwise discard it (assume it has query by magical method name stuff) + } } - else if ( element instanceof TypeElement ) { - final TypeElement typeElement = (TypeElement) element; - final AnnotationMirror repository = getAnnotationMirror( element, JD_REPOSITORY ); - if ( repository != null ) { - final AnnotationValue provider = getAnnotationValue( repository, "provider" ); - if ( provider == null - || provider.getValue().toString().isEmpty() - || provider.getValue().toString().equalsIgnoreCase("hibernate") ) { - context.logMessage( Diagnostic.Kind.OTHER, "Processing repository class '" + element + "'" ); + else { + for ( Element member : typeElement.getEnclosedElements() ) { + if ( hasAnnotation( member, HQL, SQL, FIND ) ) { + context.logMessage( Diagnostic.Kind.OTHER, "Processing annotated class '" + element + "'" ); final AnnotationMetaEntity metaEntity = AnnotationMetaEntity.create( typeElement, context ); - if ( metaEntity.isInitialized() ) { - context.addMetaAuxiliary( metaEntity.getQualifiedName(), metaEntity ); - } - // otherwise discard it (assume it has query by magical method name stuff) + context.addMetaAuxiliary( metaEntity.getQualifiedName(), metaEntity ); + break; } } - else { - for ( Element member : typeElement.getEnclosedElements() ) { - if ( hasAnnotation( member, HQL, SQL, FIND ) ) { - context.logMessage( Diagnostic.Kind.OTHER, "Processing annotated class '" + element + "'" ); - final AnnotationMetaEntity metaEntity = - AnnotationMetaEntity.create( typeElement, context ); - context.addMetaAuxiliary( metaEntity.getQualifiedName(), metaEntity ); - break; + if ( enclosesEntityOrEmbeddable( element ) ) { + AnnotationMetaEntity parentMeta = null; + if (parent instanceof TypeElement) { + final String key = ((TypeElement) parent).getQualifiedName().toString(); + if (context.getMetamodel(key) instanceof AnnotationMetaEntity) { + parentMeta = (AnnotationMetaEntity) context.getMetamodel(key); + } + } + final NonManagedMetamodel metaEntity = + NonManagedMetamodel .create( + typeElement, context, + false, parentMeta ); + context.addMetaEntity( metaEntity.getQualifiedName(), metaEntity ); + if ( context.generateJakartaDataStaticMetamodel()) { + AnnotationMetaEntity parentDataMeta = null; + if (parent instanceof TypeElement) { + final String key = ((TypeElement) parent).getQualifiedName().toString(); + if (context.getDataMetaEntity(key) instanceof AnnotationMetaEntity) { + parentDataMeta = (AnnotationMetaEntity) context.getDataMetaEntity(key); + } } + final NonManagedMetamodel dataMetaEntity = + NonManagedMetamodel .create( + typeElement, context, + true, parentDataMeta ); + context.addDataMetaEntity( dataMetaEntity.getQualifiedName(), dataMetaEntity ); } + } } } - catch ( ProcessLaterException processLaterException ) { - if ( element instanceof TypeElement ) { - context.logMessage( - Diagnostic.Kind.OTHER, - "Could not process '" + element + "' (will redo in next round)" - ); - context.addElementToRedo( ( (TypeElement) element).getQualifiedName() ); + if ( isClassOrRecordType( element ) ) { + for ( final Element child : element.getEnclosedElements() ) { + if ( isClassOrRecordType( child ) ) { + processElement( child, element ); + } } } } + catch ( ProcessLaterException processLaterException ) { + if ( element instanceof TypeElement ) { + context.logMessage( + Diagnostic.Kind.OTHER, + "Could not process '" + element + "' (will redo in next round)" + ); + context.addElementToRedo( ( (TypeElement) element ).getQualifiedName() ); + } + } } private boolean hasPackageAnnotation(Element element, String annotation) { @@ -437,7 +482,7 @@ private void createMetaModelClasses() { } for ( Metamodel entity : context.getMetaEntities() ) { - if ( !context.isAlreadyGenerated(entity) ) { + if ( !context.isAlreadyGenerated( entity ) && !isMemberType( entity.getElement() ) ) { context.logMessage( Diagnostic.Kind.OTHER, "Writing Jakarta Persistence metamodel for entity '" + entity + "'" ); ClassWriter.writeFile( entity, context ); @@ -446,7 +491,7 @@ private void createMetaModelClasses() { } for ( Metamodel entity : context.getDataMetaEntities() ) { - if ( !context.isAlreadyGenerated(entity) ) { + if ( !context.isAlreadyGenerated( entity ) && !isMemberType( entity.getElement() ) ) { context.logMessage( Diagnostic.Kind.OTHER, "Writing Jakarta Data metamodel for entity '" + entity + "'" ); ClassWriter.writeFile( entity, context ); @@ -523,6 +568,18 @@ private boolean modelGenerationNeedsToBeDeferred(Collection entities, return false; } + private static boolean enclosesEntityOrEmbeddable(Element element) { + if ( !(element instanceof TypeElement) ) { + return false; + } + for ( final Element enclosedElement : element.getEnclosedElements() ) { + if ( isEntityOrEmbeddable( enclosedElement ) || enclosesEntityOrEmbeddable( enclosedElement ) ) { + return true; + } + } + return false; + } + private static boolean isEntityOrEmbeddable(Element element) { return hasAnnotation( element, @@ -554,7 +611,7 @@ private boolean hasAuxiliaryAnnotations(Element element) { ); } - private void handleRootElementAnnotationMirrors(final Element element) { + private void handleRootElementAnnotationMirrors(final Element element, @Nullable Element parent) { if ( isClassOrRecordType( element ) ) { if ( isEntityOrEmbeddable( element ) ) { final TypeElement typeElement = (TypeElement) element; @@ -571,12 +628,21 @@ private void handleRootElementAnnotationMirrors(final Element element) { + "' since XML configuration is metadata complete."); } else { + AnnotationMetaEntity parentMetaEntity = null; + if (parent instanceof TypeElement) { + if (context.getMetamodel( + ((TypeElement) parent).getQualifiedName().toString()) + instanceof AnnotationMetaEntity) { + parentMetaEntity = (AnnotationMetaEntity) context.getMetamodel( + ((TypeElement) parent).getQualifiedName().toString()); + } + } final boolean requiresLazyMemberInitialization = hasAnnotation( element, EMBEDDABLE, MAPPED_SUPERCLASS ); final AnnotationMetaEntity metaEntity = AnnotationMetaEntity.create( typeElement, context, requiresLazyMemberInitialization, - true, false ); + true, false, parentMetaEntity ); if ( alreadyExistingMetaEntity != null ) { metaEntity.mergeInMembers( alreadyExistingMetaEntity ); } @@ -589,13 +655,18 @@ && hasAnnotation( element, ENTITY, MAPPED_SUPERCLASS ) && alreadyExistingMetaEntity == null // let a handwritten metamodel "override" the generated one // (this is used in the Jakarta Data TCK) - && element.getEnclosingElement().getEnclosedElements() - .stream().noneMatch(e -> e.getSimpleName() - .contentEquals('_' + element.getSimpleName().toString()))) { + && !hasHandwrittenMetamodel(element) ) { + AnnotationMetaEntity parentDataEntity = null; + if (parent instanceof TypeElement) { + if (context.getDataMetaEntity(((TypeElement) parent).getQualifiedName().toString()) + instanceof AnnotationMetaEntity) { + parentDataEntity = (AnnotationMetaEntity) context.getDataMetaEntity(((TypeElement) parent).getQualifiedName().toString()); + } + } final AnnotationMetaEntity dataMetaEntity = AnnotationMetaEntity.create( typeElement, context, requiresLazyMemberInitialization, - true, true ); + true, true, parentDataEntity ); // final Metamodel alreadyExistingDataMetaEntity = // tryGettingExistingDataEntityFromContext( mirror, '_' + qualifiedName ); // if ( alreadyExistingDataMetaEntity != null ) { @@ -608,6 +679,12 @@ && hasAnnotation( element, ENTITY, MAPPED_SUPERCLASS ) } } + private static boolean hasHandwrittenMetamodel(Element element) { + return element.getEnclosingElement().getEnclosedElements() + .stream().anyMatch(e -> e.getSimpleName() + .contentEquals('_' + element.getSimpleName().toString())); + } + private void indexEntityName(TypeElement typeElement) { final AnnotationMirror mirror = getAnnotationMirror( typeElement, ENTITY ); if ( mirror != null ) { 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 dcd8c1482225..fb04454e4480 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 @@ -81,11 +81,12 @@ import static org.hibernate.processor.annotation.QueryMethod.isPageParam; import static org.hibernate.processor.util.Constants.*; import static org.hibernate.processor.util.NullnessUtil.castNonNull; +import static org.hibernate.processor.util.StringUtil.removeDollar; import static org.hibernate.processor.util.TypeUtils.containsAnnotation; import static org.hibernate.processor.util.TypeUtils.determineAccessTypeForHierarchy; import static org.hibernate.processor.util.TypeUtils.determineAnnotationSpecifiedAccessType; import static org.hibernate.processor.util.TypeUtils.extendsClass; -import static org.hibernate.processor.util.TypeUtils.findMappedSuperClass; +import static org.hibernate.processor.util.TypeUtils.findMappedSuperElement; import static org.hibernate.processor.util.TypeUtils.getAnnotationMirror; import static org.hibernate.processor.util.TypeUtils.getAnnotationValue; import static org.hibernate.processor.util.TypeUtils.hasAnnotation; @@ -111,6 +112,7 @@ public class AnnotationMetaEntity extends AnnotationMeta { private final ImportContext importContext; private final TypeElement element; private final Map members; + private TypeElement parentElement; private final Context context; private final boolean managed; private boolean jakartaDataRepository; @@ -164,26 +166,32 @@ public class AnnotationMetaEntity extends AnnotationMeta { public AnnotationMetaEntity( TypeElement element, Context context, boolean managed, - boolean jakartaDataStaticMetamodel) { + boolean jakartaDataStaticMetamodel, + @Nullable AnnotationMeta parent) { this.element = element; this.context = context; this.managed = managed; this.members = new HashMap<>(); this.quarkusInjection = context.isQuarkusInjection(); - this.importContext = new ImportContextImpl( getPackageName( context, element ) ); + this.importContext = parent != null ? parent : new ImportContextImpl( getPackageName( context, element ) ); jakartaDataStaticModel = jakartaDataStaticMetamodel; } public static AnnotationMetaEntity create(TypeElement element, Context context) { - return create( element,context, false, false, false ); + return create( element,context, false, false, false, null ); } public static AnnotationMetaEntity create( TypeElement element, Context context, boolean lazilyInitialised, boolean managed, - boolean jakartaData) { + boolean jakartaData, + @Nullable AnnotationMetaEntity parent) { final AnnotationMetaEntity annotationMetaEntity = - new AnnotationMetaEntity( element, context, managed, jakartaData ); + new AnnotationMetaEntity( element, context, managed, jakartaData, parent ); + if ( parent != null ) { + annotationMetaEntity.setParentElement( parent.element ); + parent.addInnerClass( annotationMetaEntity ); + } if ( !lazilyInitialised ) { annotationMetaEntity.init(); } @@ -226,18 +234,6 @@ private String getConstructorName() { return getSimpleName() + '_'; } - /** - * If this is an "intermediate" class providing {@code @Query} - * annotations for the query by magical method name crap, then - * by convention it will be named with a trailing $ sign. Strip - * that off, so we get the standard constructor. - */ - private static String removeDollar(String simpleName) { - return simpleName.endsWith("$") - ? simpleName.substring(0, simpleName.length()-1) - : simpleName; - } - @Override public final String getQualifiedName() { if ( qualifiedName == null ) { @@ -247,13 +243,8 @@ public final String getQualifiedName() { } @Override - public @Nullable String getSupertypeName() { - if ( repository ) { - return null; - } - else { - return findMappedSuperClass( this, context ); - } + public @Nullable Element getSuperTypeElement() { + return repository ? null : findMappedSuperElement( this, context ); } @Override @@ -277,6 +268,14 @@ public List getMembers() { return new ArrayList<>( members.values() ); } + public void addInnerClass(AnnotationMetaEntity metaEntity) { + putMember( "INNER_" + metaEntity.getQualifiedName(), new InnerClassMetaAttribute( metaEntity ) ); + } + + public void setParentElement(TypeElement parentElement) { + this.parentElement = parentElement; + } + @Override public boolean isMetaComplete() { return false; @@ -371,7 +370,7 @@ public String toString() { .toString(); } - protected final void init() { + protected void init() { getContext().logMessage( Diagnostic.Kind.OTHER, "Initializing type '" + getQualifiedName() + "'" ); setupSession(); @@ -3145,4 +3144,20 @@ public List inheritedAnnotations() { return emptyList(); } } + + @Override + public String javadoc() { + if ( jakartaDataRepository ) { + return "/**\n * Implements Jakarta Data repository {@link " + qualifiedName + "}\n **/"; + } + else if ( repository ) { + return "/**\n * Implements repository {@link " + qualifiedName + "}\n **/"; + } + else if ( jakartaDataStaticModel ) { + return "/**\n * Jakarta Data static metamodel for {@link " + qualifiedName + "}\n **/"; + } + else { + return "/**\n * Static metamodel for {@link " + qualifiedName + "}\n **/"; + } + } } diff --git a/tooling/metamodel-generator/src/main/java/org/hibernate/processor/annotation/AnnotationMetaPackage.java b/tooling/metamodel-generator/src/main/java/org/hibernate/processor/annotation/AnnotationMetaPackage.java index 07a2da3b27bd..ef5281c681b5 100644 --- a/tooling/metamodel-generator/src/main/java/org/hibernate/processor/annotation/AnnotationMetaPackage.java +++ b/tooling/metamodel-generator/src/main/java/org/hibernate/processor/annotation/AnnotationMetaPackage.java @@ -13,6 +13,7 @@ import org.hibernate.processor.model.MetaAttribute; import javax.lang.model.element.AnnotationMirror; +import javax.lang.model.element.Element; import javax.lang.model.element.PackageElement; import javax.tools.Diagnostic; import java.util.ArrayList; @@ -71,7 +72,7 @@ public final String getQualifiedName() { } @Override - public @Nullable String getSupertypeName() { + public @Nullable Element getSuperTypeElement() { return null; } @@ -172,4 +173,9 @@ public boolean isJakartaDataStyle() { public List inheritedAnnotations() { return emptyList(); } + + @Override + public String javadoc() { + return "/**\n * Static metamodel package {@link " + element.getQualifiedName() + "}\n **/"; + } } diff --git a/tooling/metamodel-generator/src/main/java/org/hibernate/processor/annotation/DefaultConstructor.java b/tooling/metamodel-generator/src/main/java/org/hibernate/processor/annotation/DefaultConstructor.java index c41b5c574186..db49f907567d 100644 --- a/tooling/metamodel-generator/src/main/java/org/hibernate/processor/annotation/DefaultConstructor.java +++ b/tooling/metamodel-generator/src/main/java/org/hibernate/processor/annotation/DefaultConstructor.java @@ -68,7 +68,7 @@ public String getAttributeDeclarationString() { final StringBuilder declaration = new StringBuilder(); declaration .append('\n'); - if ( annotationMetaEntity.getSupertypeName() == null ) { + if ( annotationMetaEntity.getSuperTypeElement() == null ) { declaration .append("@") .append(annotationMetaEntity.importType("jakarta.persistence.PersistenceUnit")); diff --git a/tooling/metamodel-generator/src/main/java/org/hibernate/processor/annotation/InnerClassMetaAttribute.java b/tooling/metamodel-generator/src/main/java/org/hibernate/processor/annotation/InnerClassMetaAttribute.java new file mode 100644 index 000000000000..4ba9f705ca21 --- /dev/null +++ b/tooling/metamodel-generator/src/main/java/org/hibernate/processor/annotation/InnerClassMetaAttribute.java @@ -0,0 +1,75 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later. + * See the lgpl.txt file in the root directory or . + */ +package org.hibernate.processor.annotation; + +import org.hibernate.processor.model.MetaAttribute; +import org.hibernate.processor.model.Metamodel; + +public class InnerClassMetaAttribute implements MetaAttribute { + + private final AnnotationMeta metaEntity; + + public InnerClassMetaAttribute(AnnotationMeta parent) { + this.metaEntity = parent; + } + + public AnnotationMeta getMetaEntity() { + return metaEntity; + } + + @Override + public boolean hasTypedAttribute() { + return true; + } + + @Override + public boolean hasStringAttribute() { + return false; + } + + @Override + public String getAttributeDeclarationString() { +// final StringBuilder decl = new StringBuilder() +// .append("\n/**\n * Static ID class for {@link ") +// .append( parent.getQualifiedName() ) +// .append( "}\n **/\n" ) +// .append( "public record Id" ); +// String delimiter = "("; +// for ( MetaAttribute component : components ) { +// decl.append( delimiter ).append( parent.importType( component.getTypeDeclaration() ) ) +// .append( ' ' ).append( component.getPropertyName() ); +// delimiter = ", "; +// } +// return decl.append( ") {}" ).toString(); + return ""; + } + + @Override + public String getAttributeNameDeclarationString() { + return ""; + } + + @Override + public String getMetaType() { + return ""; + } + + @Override + public String getPropertyName() { + return ""; + } + + @Override + public String getTypeDeclaration() { + return ""; + } + + @Override + public Metamodel getHostingEntity() { + return metaEntity; + } +} diff --git a/tooling/metamodel-generator/src/main/java/org/hibernate/processor/annotation/NonManagedMetamodel.java b/tooling/metamodel-generator/src/main/java/org/hibernate/processor/annotation/NonManagedMetamodel.java new file mode 100644 index 000000000000..82ac4536f22d --- /dev/null +++ b/tooling/metamodel-generator/src/main/java/org/hibernate/processor/annotation/NonManagedMetamodel.java @@ -0,0 +1,41 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later. + * See the lgpl.txt file in the root directory or . + */ +package org.hibernate.processor.annotation; + +import org.checkerframework.checker.nullness.qual.Nullable; +import org.hibernate.processor.Context; + +import javax.lang.model.element.TypeElement; + +public class NonManagedMetamodel extends AnnotationMetaEntity { + + public NonManagedMetamodel(TypeElement element, Context context, boolean jakartaDataStaticMetamodel, @Nullable AnnotationMeta parent) { + super( element, context, false, jakartaDataStaticMetamodel, parent ); + } + + public static NonManagedMetamodel create( + TypeElement element, Context context, + boolean jakartaDataStaticMetamodel, + @Nullable AnnotationMetaEntity parent) { + final NonManagedMetamodel metamodel = + new NonManagedMetamodel( element, context, jakartaDataStaticMetamodel, parent ); + if ( parent != null ) { + metamodel.setParentElement( parent.getElement() ); + parent.addInnerClass( metamodel ); + } + return metamodel; + } + + protected void init() { + // Initialization is not needed when non-managed class + } + + @Override + public String javadoc() { + return ""; + } +} diff --git a/tooling/metamodel-generator/src/main/java/org/hibernate/processor/annotation/RepositoryConstructor.java b/tooling/metamodel-generator/src/main/java/org/hibernate/processor/annotation/RepositoryConstructor.java index cf285b697d5b..f926b66ea908 100644 --- a/tooling/metamodel-generator/src/main/java/org/hibernate/processor/annotation/RepositoryConstructor.java +++ b/tooling/metamodel-generator/src/main/java/org/hibernate/processor/annotation/RepositoryConstructor.java @@ -69,7 +69,7 @@ public String getAttributeDeclarationString() { final StringBuilder declaration = new StringBuilder(); declaration .append('\n'); - if ( annotationMetaEntity.getSupertypeName() == null ) { + if ( annotationMetaEntity.getSuperTypeElement() == null ) { declaration .append("protected "); if ( !dataRepository ) { @@ -98,7 +98,7 @@ public String getAttributeDeclarationString() { .append(" ") .append(sessionVariableName) .append(") {\n"); - if ( annotationMetaEntity.getSupertypeName() != null ) { + if ( annotationMetaEntity.getSuperTypeElement() != null ) { declaration .append("\tsuper(") .append(sessionVariableName) @@ -114,7 +114,7 @@ public String getAttributeDeclarationString() { } declaration .append("}"); - if ( annotationMetaEntity.getSupertypeName() == null ) { + if ( annotationMetaEntity.getSuperTypeElement() == null ) { declaration .append("\n\n"); if (addOverrideAnnotation) { diff --git a/tooling/metamodel-generator/src/main/java/org/hibernate/processor/model/Metamodel.java b/tooling/metamodel-generator/src/main/java/org/hibernate/processor/model/Metamodel.java index 31c04f6f9898..af851f96cecc 100644 --- a/tooling/metamodel-generator/src/main/java/org/hibernate/processor/model/Metamodel.java +++ b/tooling/metamodel-generator/src/main/java/org/hibernate/processor/model/Metamodel.java @@ -21,7 +21,7 @@ public interface Metamodel extends ImportContext { String getQualifiedName(); - @Nullable String getSupertypeName(); + @Nullable Element getSuperTypeElement(); String getPackageName(); @@ -60,4 +60,6 @@ public interface Metamodel extends ImportContext { boolean isJakartaDataStyle(); List inheritedAnnotations(); + + String javadoc(); } diff --git a/tooling/metamodel-generator/src/main/java/org/hibernate/processor/util/StringUtil.java b/tooling/metamodel-generator/src/main/java/org/hibernate/processor/util/StringUtil.java index 994857eec775..7843ac75726d 100644 --- a/tooling/metamodel-generator/src/main/java/org/hibernate/processor/util/StringUtil.java +++ b/tooling/metamodel-generator/src/main/java/org/hibernate/processor/util/StringUtil.java @@ -107,6 +107,9 @@ public static String nameToMethodName(String name) { } public static String getUpperUnderscoreCaseFromLowerCamelCase(String lowerCamelCaseString) { + if ( lowerCamelCaseString.length() == 1 && isUpperCase( lowerCamelCaseString.charAt( 0 ) ) ) { + return "_" + lowerCamelCaseString; + } final StringBuilder result = new StringBuilder(); int position = 0; boolean wasLowerCase = false; @@ -131,4 +134,16 @@ private static boolean startsWithSeveralUpperCaseLetters(String string) { Character.isUpperCase( string.charAt( 0 ) ) && Character.isUpperCase( string.charAt( 1 ) ); } + + /** + * If this is an "intermediate" class providing {@code @Query} + * annotations for the query by magical method name crap, then + * by convention it will be named with a trailing $ sign. Strip + * that off, so we get the standard constructor. + */ + public static String removeDollar(String simpleName) { + return simpleName.endsWith("$") + ? simpleName.substring(0, simpleName.length()-1) + : simpleName; + } } 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 567cc886ed3b..a99d981c77de 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 @@ -610,7 +610,7 @@ else if ( name.startsWith( "is" ) ) { } } - public static @Nullable String findMappedSuperClass(Metamodel entity, Context context) { + public static @Nullable Element findMappedSuperElement(Metamodel entity, Context context) { final Element element = entity.getElement(); if ( element instanceof TypeElement ) { final TypeElement typeElement = (TypeElement) element; @@ -620,7 +620,7 @@ else if ( name.startsWith( "is" ) ) { final DeclaredType declaredType = (DeclaredType) superClass; final TypeElement superClassElement = (TypeElement) declaredType.asElement(); if ( extendsSuperMetaModel( superClassElement, entity.isMetaComplete(), context ) ) { - return superClassElement.getQualifiedName().toString(); + return superClassElement; } superClass = superClassElement.getSuperclass(); } @@ -680,6 +680,10 @@ public static boolean extendsClass(TypeElement type, String className) { return false; } + public static boolean isMemberType(Element element) { + return element.getEnclosingElement() instanceof TypeElement; + } + static class EmbeddedAttributeVisitor extends SimpleTypeVisitor8<@Nullable TypeElement, Element> { private final Context context; diff --git a/tooling/metamodel-generator/src/main/java/org/hibernate/processor/validation/MockSessionFactory.java b/tooling/metamodel-generator/src/main/java/org/hibernate/processor/validation/MockSessionFactory.java index 1bbefeae2383..8e02da7fa842 100644 --- a/tooling/metamodel-generator/src/main/java/org/hibernate/processor/validation/MockSessionFactory.java +++ b/tooling/metamodel-generator/src/main/java/org/hibernate/processor/validation/MockSessionFactory.java @@ -117,6 +117,8 @@ import org.hibernate.type.SetType; import org.hibernate.type.SqlTypes; import org.hibernate.type.Type; +import org.hibernate.type.descriptor.WrapperOptions; +import org.hibernate.type.descriptor.java.BasicJavaType; import org.hibernate.type.descriptor.java.EnumJavaType; import org.hibernate.type.descriptor.java.JavaType; import org.hibernate.type.descriptor.java.spi.UnknownBasicJavaType; @@ -124,6 +126,7 @@ import org.hibernate.type.descriptor.jdbc.ObjectJdbcType; import org.hibernate.type.spi.TypeConfiguration; +import javax.lang.model.element.TypeElement; import java.util.HashMap; import java.util.List; import java.util.Map; @@ -286,7 +289,7 @@ static CollectionType createCollectionType(String role, String name) { abstract boolean isEntityDefined(String entityName); - abstract String findEntityName(String typeName); + abstract TypeElement findEntityClass(String entityName); abstract String qualifyName(String entityName); @@ -697,6 +700,39 @@ public int getPreferredSqlTypeCodeForArray() { return SqlTypes.ARRAY; } + private static class MockJavaType implements BasicJavaType { + private final String typeName; + + public MockJavaType(String typeName) { + this.typeName = typeName; + } + + @Override + public X1 unwrap(X value, Class type, WrapperOptions options) { + return null; + } + + @Override + public X wrap(X1 value, WrapperOptions options) { + return null; + } + + @Override + public String getTypeName() { + return typeName; + } + + @Override + public Class getJavaTypeClass() { + try { + return (Class) Class.forName( typeName ); + } + catch (ClassNotFoundException e) { + return null; + } + } + } + private class MockMappingMetamodelImpl extends MappingMetamodelImpl { public MockMappingMetamodelImpl() { super(typeConfiguration, serviceRegistry); @@ -806,9 +842,11 @@ public MockJpaMetamodelImpl() { } @Override - public EntityDomainType entity(String entityName) { + public EntityDomainType entity(String entityName) { if ( isEntityDefined(entityName) ) { - return new MockEntityDomainType<>(entityName); + final TypeElement entityClass = findEntityClass( entityName ); + final String entityTypeName = entityClass == null ? entityName : entityClass.getQualifiedName().toString(); + return new MockEntityDomainType<>(entityName, new MockJavaType<>( entityTypeName )); } else { return null; @@ -816,9 +854,8 @@ public EntityDomainType entity(String entityName) { } @Override - public ManagedDomainType managedType(String typeName) { - final String entityName = findEntityName( typeName ); - return entityName == null ? null : entity( entityName ); + public @Nullable EntityDomainType findEntityType(@Nullable String entityName) { + return entity( entityName ); } @Override @@ -832,6 +869,22 @@ else if (isEntityDefined(queryName)) { return null; } + @Override + public ManagedDomainType managedType(String typeName) { + final ManagedDomainType managedType = findManagedType( typeName ); + if ( managedType == null ) { + throw new IllegalArgumentException("Not a managed type: " + typeName); + } + return managedType; + } + + @Override + public @Nullable ManagedDomainType findManagedType(@Nullable String typeName) { + final String entityName = qualifyName( typeName ); + //noinspection unchecked + return entityName == null ? null : (ManagedDomainType) findEntityType( entityName ); + } + @Override public ManagedDomainType findManagedType(Class cls) { throw new UnsupportedOperationException("operation not supported"); @@ -840,7 +893,7 @@ public ManagedDomainType findManagedType(Class cls) { @Override public EntityDomainType findEntityType(Class cls) { if ( isEntityDefined( cls.getName() ) ) { - return new MockEntityDomainType<>( cls.getName() ); + return new MockEntityDomainType<>( cls.getName(), new MockJavaType( cls.getName() )); } else { return null; @@ -924,8 +977,8 @@ public PersistentAttribute findDeclaredAttribute(String name) { class MockEntityDomainType extends EntityTypeImpl { - public MockEntityDomainType(String entityName) { - super(entityName, entityName, false, true, false, null, null, + public MockEntityDomainType(String entityName, JavaType javaType) { + super(entityName, entityName, false, true, false, javaType, null, metamodel.getJpaMetamodel()); } @@ -1008,8 +1061,8 @@ public SqmPathSource findSubPathSource(String name, JpaMetamodelImplementor m for (Map.Entry entry : entityPersistersByName.entrySet()) { if (!entry.getValue().getEntityName().equals(getHibernateEntityName()) && isSubtype(entry.getValue().getEntityName(), getHibernateEntityName())) { - PersistentAttribute subattribute - = new MockEntityDomainType<>(entry.getValue().getEntityName()).findAttribute(name); + final PersistentAttribute subattribute + = new MockEntityDomainType<>(entry.getValue().getEntityName(), new MockJavaType<>(entry.getValue().getEntityName()) ).findAttribute(name); if (subattribute != null) { return (SqmPathSource) subattribute; } @@ -1055,7 +1108,7 @@ else if ( type.isEntityType() ) { owner, name, AttributeClassification.MANY_TO_ONE, - new MockEntityDomainType<>(type.getName()), + new MockEntityDomainType<>(type.getName(), new MockJavaType<>(type.getName())), null, null, false, @@ -1112,8 +1165,10 @@ private DomainType getMapKeyDomainType(String entityName, CollectionType coll private DomainType getDomainType(String entityName, CollectionType collectionType, ManagedDomainType owner, Type elementType) { if ( elementType.isEntityType() ) { - String associatedEntityName = collectionType.getAssociatedEntityName(MockSessionFactory.this); - return new MockEntityDomainType<>(associatedEntityName); + final String associatedEntityName = collectionType.getAssociatedEntityName(MockSessionFactory.this); + final TypeElement associatedEntityEntityClass = findEntityClass( associatedEntityName ); + final String associatedEntityTypeName = associatedEntityEntityClass == null ? associatedEntityName : associatedEntityEntityClass.getQualifiedName().toString(); + return new MockEntityDomainType<>(associatedEntityName, new MockJavaType<>(associatedEntityTypeName)); } else if ( elementType.isComponentType() ) { CompositeType compositeType = (CompositeType) elementType; diff --git a/tooling/metamodel-generator/src/main/java/org/hibernate/processor/validation/ProcessorSessionFactory.java b/tooling/metamodel-generator/src/main/java/org/hibernate/processor/validation/ProcessorSessionFactory.java index 1925e82af4c5..be13a4e0d4fc 100644 --- a/tooling/metamodel-generator/src/main/java/org/hibernate/processor/validation/ProcessorSessionFactory.java +++ b/tooling/metamodel-generator/src/main/java/org/hibernate/processor/validation/ProcessorSessionFactory.java @@ -461,16 +461,6 @@ boolean isEntityDefined(String entityName) { return findEntityClass(entityName) != null; } - @Override - String findEntityName(String typeName) { - for ( final Map.Entry e : entityNameMappings.entrySet() ) { - if ( typeName.equals( e.getKey() ) || typeName.equals( e.getValue() ) ) { - return e.getKey(); - } - } - return null; - } - @Override String qualifyName(String entityName) { TypeElement entityClass = findEntityClass(entityName); @@ -484,7 +474,8 @@ boolean isAttributeDefined(String entityName, String fieldName) { && findPropertyByPath(entityClass, fieldName, getDefaultAccessType(entityClass)) != null; } - private TypeElement findEntityClass(String entityName) { + @Override + public TypeElement findEntityClass(String entityName) { if (entityName == null) { return null; } diff --git a/tooling/metamodel-generator/src/main/java/org/hibernate/processor/xml/XmlMetaEntity.java b/tooling/metamodel-generator/src/main/java/org/hibernate/processor/xml/XmlMetaEntity.java index 452f1f424e73..1b4e4e3964b8 100644 --- a/tooling/metamodel-generator/src/main/java/org/hibernate/processor/xml/XmlMetaEntity.java +++ b/tooling/metamodel-generator/src/main/java/org/hibernate/processor/xml/XmlMetaEntity.java @@ -50,7 +50,7 @@ import static java.util.Collections.emptyList; import static org.hibernate.processor.util.StringUtil.determineFullyQualifiedClassName; import static org.hibernate.processor.util.TypeUtils.extractClosestRealTypeAsString; -import static org.hibernate.processor.util.TypeUtils.findMappedSuperClass; +import static org.hibernate.processor.util.TypeUtils.findMappedSuperElement; import static org.hibernate.processor.util.TypeUtils.getElementKindForAccessType; import static org.hibernate.processor.xml.jaxb.AccessType.*; @@ -163,8 +163,8 @@ public String getPackageName() { } @Override - public @Nullable String getSupertypeName() { - return findMappedSuperClass( this, context ); + public @Nullable Element getSuperTypeElement() { + return findMappedSuperElement( this, context ); } public List getMembers() { @@ -651,4 +651,9 @@ public boolean isJakartaDataStyle() { public List inheritedAnnotations() { return emptyList(); } + + @Override + public String javadoc() { + return "/**\n * Static metamodel for {@link " + clazzName + "}\n **/"; + } } diff --git a/tooling/metamodel-generator/src/test/java/org/hibernate/processor/test/innerclass/Dummy.java b/tooling/metamodel-generator/src/test/java/org/hibernate/processor/test/innerclass/Dummy.java new file mode 100644 index 000000000000..19fc23274401 --- /dev/null +++ b/tooling/metamodel-generator/src/test/java/org/hibernate/processor/test/innerclass/Dummy.java @@ -0,0 +1,84 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later. + * See the lgpl.txt file in the root directory or . + */ +package org.hibernate.processor.test.innerclass; + +import jakarta.persistence.Embeddable; +import jakarta.persistence.Entity; +import jakarta.persistence.Id; +import jakarta.persistence.MappedSuperclass; +import jakarta.persistence.NamedQuery; + +public class Dummy { + @Entity(name = "Inner") + @NamedQuery(name = "allInner", query = "from Inner") + public static class Inner extends Persona { + @Id + Integer id; + + String name; + + public Integer getId() { + return id; + } + + @Override + public void setId(Integer id) { + this.id = id; + } + + @Override + public String getName() { + return name; + } + + @Override + public void setName(String name) { + this.name = name; + } + } + + @Embeddable + public static class DummyEmbeddable { + private String name; + private int value; + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public int getValue() { + return value; + } + + public void setValue(int value) { + this.value = value; + } + } + + @MappedSuperclass + public abstract static class Persona { + private String city; + + public String getCity() { + return city; + } + + public void setCity(String city) { + this.city = city; + } + + public abstract void setId(Integer id); + + public abstract String getName(); + + public abstract void setName(String name); + } +} diff --git a/tooling/metamodel-generator/src/test/java/org/hibernate/processor/test/innerclass/InnerClassTest.java b/tooling/metamodel-generator/src/test/java/org/hibernate/processor/test/innerclass/InnerClassTest.java new file mode 100644 index 000000000000..455e2906ef00 --- /dev/null +++ b/tooling/metamodel-generator/src/test/java/org/hibernate/processor/test/innerclass/InnerClassTest.java @@ -0,0 +1,77 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later. + * See the lgpl.txt file in the root directory or . + */ +package org.hibernate.processor.test.innerclass; + +import jakarta.persistence.Entity; +import jakarta.persistence.Id; +import jakarta.persistence.NamedQuery; +import org.hibernate.processor.test.innerclass.InnerClassTest.One.Two; +import org.hibernate.processor.test.util.CompilationTest; +import org.hibernate.processor.test.util.WithClasses; +import org.junit.Test; + +import static org.hibernate.processor.test.util.TestUtil.assertMetamodelClassGeneratedFor; +import static org.hibernate.processor.test.util.TestUtil.assertNoMetamodelClassGeneratedFor; +import static org.hibernate.processor.test.util.TestUtil.getMetaModelSourceAsString; +import static org.junit.Assert.assertEquals; + +public class InnerClassTest extends CompilationTest { + + @WithClasses({Person.class, Dummy.class, Inner.class, Two.class}) + @Test + public void test() { + System.out.println( getMetaModelSourceAsString( InnerClassTest.class ) ); + System.out.println( getMetaModelSourceAsString( Dummy.class ) ); + System.out.println( getMetaModelSourceAsString( Person.class ) ); + assertEquals( + getMetaModelSourceAsString( Inner.class ), + getMetaModelSourceAsString( Two.class ) + ); + assertMetamodelClassGeneratedFor( Inner.class ); + assertMetamodelClassGeneratedFor( Two.class ); + assertMetamodelClassGeneratedFor( Dummy.Inner.class ); + assertMetamodelClassGeneratedFor( Person.class ); + assertMetamodelClassGeneratedFor( Person.PersonId.class ); + assertNoMetamodelClassGeneratedFor( Dummy.class ); + assertMetamodelClassGeneratedFor( Dummy.DummyEmbeddable.class ); + System.out.println( getMetaModelSourceAsString( Dummy.DummyEmbeddable.class ) ); + } + + @Entity(name = "Inner") + @NamedQuery(name = "allInner", query = "from Inner") + public static class Inner { + @Id + Integer id; + + String address; + + public Integer getId() { + return id; + } + + public void setId(Integer id) { + this.id = id; + } + + public String getAddress() { + return address; + } + + public void setAddress(String address) { + this.address = address; + } + } + + static class One { + @Entity + static class Two { + @Id + Integer id; + String value; + } + } +} diff --git a/tooling/metamodel-generator/src/test/java/org/hibernate/processor/test/innerclass/Person.java b/tooling/metamodel-generator/src/test/java/org/hibernate/processor/test/innerclass/Person.java new file mode 100644 index 000000000000..278ab7e5b10b --- /dev/null +++ b/tooling/metamodel-generator/src/test/java/org/hibernate/processor/test/innerclass/Person.java @@ -0,0 +1,46 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later. + * See the lgpl.txt file in the root directory or . + */ +package org.hibernate.processor.test.innerclass; + +import jakarta.persistence.Embeddable; +import jakarta.persistence.EmbeddedId; +import jakarta.persistence.Entity; + +/** + * @author Hardy Ferentschik + */ +@Entity +public class Person { + @EmbeddedId + private PersonId id; + + private String address; + + @Embeddable + public static class PersonId { + private String name; + private String snn; + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public String getSnn() { + return snn; + } + + public void setSnn(String snn) { + this.snn = snn; + } + } + + +} diff --git a/tooling/metamodel-generator/src/test/java/org/hibernate/processor/test/separatecompilationunits/SeparateCompilationUnitsTest.java b/tooling/metamodel-generator/src/test/java/org/hibernate/processor/test/separatecompilationunits/SeparateCompilationUnitsTest.java index 8a13817c3f65..293e13281d17 100644 --- a/tooling/metamodel-generator/src/test/java/org/hibernate/processor/test/separatecompilationunits/SeparateCompilationUnitsTest.java +++ b/tooling/metamodel-generator/src/test/java/org/hibernate/processor/test/separatecompilationunits/SeparateCompilationUnitsTest.java @@ -30,7 +30,12 @@ public void testInheritance() throws Exception { String entityMetaModel = getMetaModelSourceAsString( Entity.class ); assertTrue( entityMetaModel.contains( - "extends org.hibernate.processor.test.separatecompilationunits.superclass.MappedSuperclass" + "import org.hibernate.processor.test.separatecompilationunits.superclass.MappedSuperclass_;" + ) + ); + assertTrue( + entityMetaModel.contains( + "extends MappedSuperclass_" ) ); } diff --git a/tooling/metamodel-generator/src/test/java/org/hibernate/processor/test/util/CompilationStatement.java b/tooling/metamodel-generator/src/test/java/org/hibernate/processor/test/util/CompilationStatement.java index 087e56cf3b2d..92811c35fb6c 100644 --- a/tooling/metamodel-generator/src/test/java/org/hibernate/processor/test/util/CompilationStatement.java +++ b/tooling/metamodel-generator/src/test/java/org/hibernate/processor/test/util/CompilationStatement.java @@ -103,6 +103,9 @@ private List getCompilationUnits(List> classesToCompile, List testClass) { + if ( testClass.isMemberClass() ) { + return getPathToSource( testClass.getDeclaringClass() ); + } return TestUtil.getSourceBaseDir( testClass ).getAbsolutePath() + File.separator + testClass.getName() .replace( PACKAGE_SEPARATOR, File.separator ) + ".java"; } diff --git a/tooling/metamodel-generator/src/test/java/org/hibernate/processor/test/util/TestUtil.java b/tooling/metamodel-generator/src/test/java/org/hibernate/processor/test/util/TestUtil.java index ce282c9c5574..fb9d5461927c 100644 --- a/tooling/metamodel-generator/src/test/java/org/hibernate/processor/test/util/TestUtil.java +++ b/tooling/metamodel-generator/src/test/java/org/hibernate/processor/test/util/TestUtil.java @@ -337,6 +337,9 @@ public static Class getMetamodelClassFor(String className) { } public static File getMetaModelSourceFileFor(Class clazz, boolean prefix) { + if ( clazz.isMemberClass() ) { + return getMetaModelSourceFileFor( clazz.getEnclosingClass(), prefix ); + } String metaModelClassName = getMetaModelClassName(clazz, prefix); // generate the file name String fileName = metaModelClassName.replace( PACKAGE_SEPARATOR, PATH_SEPARATOR ); @@ -353,13 +356,17 @@ public static File getMetaModelSourceFileFor(String className) { } private static String getMetaModelClassName(Class clazz, boolean prefix) { - return prefix - ? clazz.getPackageName() + '.' + META_MODEL_CLASS_POSTFIX + clazz.getSimpleName() - : clazz.getName() + META_MODEL_CLASS_POSTFIX; + final String packageName = clazz.getPackageName(); + return prefix ? packageName + '.' + META_MODEL_CLASS_POSTFIX + clazz.getName().substring( packageName.length() + 1 ) + .replaceAll( "\\$", "\\$_" ) + : packageName + clazz.getName().substring( packageName.length() ) + .replaceAll( "\\$", "_\\$" ) + META_MODEL_CLASS_POSTFIX; } private static String getMetaModelClassName(String className) { - return className + META_MODEL_CLASS_POSTFIX; + final int index = className.lastIndexOf( '.' ); + final String packageName = className.substring( 0, index + 1 ); + return packageName + className.substring( packageName.length() ).replaceAll( "\\$", "_\\$" ) + META_MODEL_CLASS_POSTFIX; } public static String getMetaModelSourceAsString(Class clazz) {