From 5a889f7d5647ba8d3c475c1797d2f4851dab4c9e Mon Sep 17 00:00:00 2001 From: Christian Beikov Date: Tue, 2 Apr 2024 20:29:07 +0200 Subject: [PATCH] HHH-17782, HHH-17901 Support enum literals in annotation processor HQL validation --- .../metamodel/model/domain/JpaMetamodel.java | 7 +- .../domain/internal/JpaMetamodelImpl.java | 86 ++++++++++--------- .../domain/internal/MappingMetamodelImpl.java | 4 +- .../validation/MockSessionFactory.java | 7 +- .../validation/ProcessorSessionFactory.java | 37 +++++++- .../hibernate/processor/test/dao/Book.java | 3 + .../org/hibernate/processor/test/dao/Dao.java | 3 + .../hibernate/processor/test/dao/Dao2.java | 28 ++++++ .../processor/test/dao/DaoTest2.java | 28 ++++++ 9 files changed, 148 insertions(+), 55 deletions(-) create mode 100644 tooling/metamodel-generator/src/test/java/org/hibernate/processor/test/dao/Dao2.java create mode 100644 tooling/metamodel-generator/src/test/java/org/hibernate/processor/test/dao/DaoTest2.java 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 e1b0cf26a5..206eb6cc4d 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 @@ -90,12 +90,11 @@ public interface JpaMetamodel extends Metamodel { String qualifyImportableName(String queryName); - @Nullable - Set getEnumTypesForValue(String enumValue); + @Nullable Set getEnumTypesForValue(String enumValue); - EnumJavaType getEnumType(String prefix); + EnumJavaType getEnumType(String className); - > E enumValue(EnumJavaType enumType, String terminal); + > E enumValue(EnumJavaType enumType, String enumValueName); JavaType getJavaConstantType(String className, String fieldName); 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 85f4e64674..4367821420 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 @@ -58,7 +58,6 @@ import org.hibernate.type.descriptor.java.EnumJavaType; import org.hibernate.type.descriptor.java.JavaType; import org.hibernate.type.descriptor.java.spi.DynamicModelJavaType; import org.hibernate.type.descriptor.java.spi.EntityJavaType; -import org.hibernate.type.descriptor.java.spi.JavaTypeRegistry; import org.hibernate.type.spi.TypeConfiguration; import jakarta.persistence.EntityGraph; @@ -95,7 +94,8 @@ public class JpaMetamodelImpl implements JpaMetamodelImplementor, Serializable { private final Map> managedTypeByName = new TreeMap<>(); private final Map, ManagedDomainType> managedTypeByClass = new HashMap<>(); private JpaMetaModelPopulationSetting jpaMetaModelPopulationSetting; - private final Map> allowedEnumLiteralTexts = new HashMap<>(); + private final Map> allowedEnumLiteralsToEnumTypeNames = new HashMap<>(); + private final Map> enumJavaTypes = new HashMap<>(); private final transient Map> entityGraphMap = new ConcurrentHashMap<>(); @@ -282,43 +282,19 @@ public class JpaMetamodelImpl implements JpaMetamodelImplementor, Serializable { .collect( Collectors.toSet() ); } - @Override @Nullable - public Set getEnumTypesForValue(String enumValue) { - return allowedEnumLiteralTexts.get(enumValue); + @Override + public @Nullable Set getEnumTypesForValue(String enumValue) { + return allowedEnumLiteralsToEnumTypeNames.get( enumValue); } @Override - public EnumJavaType getEnumType(String prefix) { - final ClassLoaderService classLoaderService = - getServiceRegistry().requireService(ClassLoaderService.class); - final JavaTypeRegistry registry = getTypeConfiguration().getJavaTypeRegistry(); - try { - final Class namedClass = classLoaderService.classForName( prefix ); - if ( namedClass != null && namedClass.isEnum() ) { - return (EnumJavaType) registry.resolveDescriptor(namedClass); - } - } - catch (ClassLoadingException classLoadingException) { - try { - final int lastDot = prefix.lastIndexOf('.'); - if ( lastDot>0) { - final String replaced = - prefix.substring(0, lastDot) + '$' + prefix.substring(lastDot+1); - final Class namedClass = classLoaderService.classForName( replaced ); - if ( namedClass != null && namedClass.isEnum() ) { - return (EnumJavaType) registry.resolveDescriptor(namedClass); - } - } - } - catch (ClassLoadingException ignore) { - } - } - return null; + public EnumJavaType getEnumType(String className) { + return enumJavaTypes.get( className ); } @Override - public > E enumValue(EnumJavaType enumType, String terminal) { - return Enum.valueOf( enumType.getJavaTypeClass(), terminal ); + public > E enumValue(EnumJavaType enumType, String enumValueName) { + return Enum.valueOf( enumType.getJavaTypeClass(), enumValueName ); } @Override @@ -671,14 +647,15 @@ public class JpaMetamodelImpl implements JpaMetamodelImplementor, Serializable { final Class> enumJavaClass = enumJavaType.getJavaTypeClass(); final Enum[] enumConstants = enumJavaClass.getEnumConstants(); for ( Enum enumConstant : enumConstants ) { - allowedEnumLiteralTexts - .computeIfAbsent( enumConstant.name(), s -> new HashSet<>() ) - .add( enumJavaClass.getName() ); - - final String simpleQualifiedName = enumJavaClass.getSimpleName() + "." + enumConstant.name(); - allowedEnumLiteralTexts - .computeIfAbsent( simpleQualifiedName, s -> new HashSet<>() ) - .add( enumJavaClass.getName() ); + addAllowedEnumLiteralsToEnumTypesMap( + allowedEnumLiteralsToEnumTypeNames, + enumConstant.name(), + enumJavaClass.getSimpleName(), + enumJavaClass.getCanonicalName(), + enumJavaClass.getName() + ); + enumJavaTypes.put( enumJavaClass.getName(), enumJavaType ); + enumJavaTypes.put( enumJavaClass.getCanonicalName(), enumJavaType ); } } } ); @@ -686,6 +663,33 @@ public class JpaMetamodelImpl implements JpaMetamodelImplementor, Serializable { applyNamedEntityGraphs( namedEntityGraphDefinitions ); } + public static void addAllowedEnumLiteralsToEnumTypesMap( + Map> allowedEnumLiteralsToEnumTypeNames, + String enumConstantName, + String enumSimpleName, + String enumAlternativeName, + String enumClassName + ) { + allowedEnumLiteralsToEnumTypeNames + .computeIfAbsent( enumConstantName, s -> new HashSet<>() ) + .add( enumClassName ); + + final String simpleQualifiedName = enumSimpleName + "." + enumConstantName; + allowedEnumLiteralsToEnumTypeNames + .computeIfAbsent( simpleQualifiedName, s -> new HashSet<>() ) + .add( enumClassName ); + + final String qualifiedAlternativeName = enumAlternativeName + "." + enumConstantName; + allowedEnumLiteralsToEnumTypeNames + .computeIfAbsent( qualifiedAlternativeName, s -> new HashSet<>() ) + .add( enumClassName ); + + final String qualifiedName = enumClassName + "." + enumConstantName; + allowedEnumLiteralsToEnumTypeNames + .computeIfAbsent( qualifiedName, s -> new HashSet<>() ) + .add( enumClassName ); + } + private EntityDomainType locateOrBuildEntityType( PersistentClass persistentClass, MetadataContext context, 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 fb517dc2ec..8a73cad47e 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 @@ -550,8 +550,8 @@ public class MappingMetamodelImpl extends QueryParameterBindingTypeResolverImpl } @Override - public > E enumValue(EnumJavaType enumType, String terminal) { - return jpaMetamodel.enumValue(enumType, terminal); + public > E enumValue(EnumJavaType enumType, String enumValueName) { + return jpaMetamodel.enumValue( enumType, enumValueName ); } @Override 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 ef24ef754c..94762f4a73 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 @@ -873,8 +873,8 @@ public abstract class MockSessionFactory return null; } - @Override @Nullable - public Set getEnumTypesForValue(String enumValue) { + @Override + public @Nullable Set getEnumTypesForValue(String enumValue) { return MockSessionFactory.this.getEnumTypesForValue(enumValue); } @@ -884,8 +884,7 @@ public abstract class MockSessionFactory } } - @Nullable - Set getEnumTypesForValue(String value) { + @Nullable Set getEnumTypesForValue(String value) { return emptySet(); } 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 cb0fb5a7a7..c99c1d0da6 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 @@ -49,6 +49,7 @@ import java.io.IOException; import java.io.Reader; import java.util.ArrayList; import java.util.HashMap; +import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; @@ -58,6 +59,7 @@ import static org.hibernate.internal.util.StringHelper.qualify; import static org.hibernate.internal.util.StringHelper.root; import static org.hibernate.internal.util.StringHelper.split; import static org.hibernate.internal.util.StringHelper.unroot; +import static org.hibernate.metamodel.model.domain.internal.JpaMetamodelImpl.addAllowedEnumLiteralsToEnumTypesMap; import static org.hibernate.processor.util.Constants.JAVA_OBJECT; /** @@ -90,7 +92,7 @@ public abstract class ProcessorSessionFactory extends MockSessionFactory { private final Types typeUtil; private final Filer filer; private final Map entityNameMappings; - private final Map> enumTypesByValue; + private final Map> allowedEnumLiteralsToEnumTypeNames; public ProcessorSessionFactory( ProcessingEnvironment processingEnvironment, @@ -100,7 +102,23 @@ public abstract class ProcessorSessionFactory extends MockSessionFactory { typeUtil = processingEnvironment.getTypeUtils(); filer = processingEnvironment.getFiler(); this.entityNameMappings = entityNameMappings; - this.enumTypesByValue = enumTypesByValue; + final Map> allowedEnumLiteralsToEnumTypeNames = new HashMap<>( enumTypesByValue.size() << 2 ); + for ( Map.Entry> entry : enumTypesByValue.entrySet() ) { + final String enumConstantName = entry.getKey(); + for ( String enumClassName : entry.getValue() ) { + final TypeElement enumTypeElement = elementUtil.getTypeElement( enumClassName ); + if ( enumTypeElement != null ) { + addAllowedEnumLiteralsToEnumTypesMap( + allowedEnumLiteralsToEnumTypeNames, + enumConstantName, + enumTypeElement.getSimpleName().toString(), + elementUtil.getBinaryName( enumTypeElement ).toString(), + enumClassName + ); + } + } + } + this.allowedEnumLiteralsToEnumTypeNames = allowedEnumLiteralsToEnumTypeNames; } @Override @@ -217,7 +235,7 @@ public abstract class ProcessorSessionFactory extends MockSessionFactory { @Override @Nullable Set getEnumTypesForValue(String value) { - Set result = enumTypesByValue.get(value); + Set result = allowedEnumLiteralsToEnumTypeNames.get( value); if ( result != null ) { return result; } @@ -631,7 +649,18 @@ public abstract class ProcessorSessionFactory extends MockSessionFactory { @Override boolean isEnum(String className) { - final TypeElement typeElement = elementUtil.getTypeElement(className); + TypeElement typeElement = elementUtil.getTypeElement( className ); + int startIdx = 0; + int dollarIdx; + while ( typeElement == null && ( dollarIdx = className.indexOf( '$', startIdx ) ) != -1 ) { + final String potentialBaseTypeName = className.substring( 0, dollarIdx ); + final TypeElement potentialBaseType = elementUtil.getTypeElement( potentialBaseTypeName ); + if ( potentialBaseType != null ) { + className = potentialBaseTypeName + className.substring( dollarIdx + 1 ); + typeElement = elementUtil.getTypeElement( className ); + } + startIdx = dollarIdx + 1; + } return typeElement != null && typeElement.getKind() == ElementKind.ENUM; } diff --git a/tooling/metamodel-generator/src/test/java/org/hibernate/processor/test/dao/Book.java b/tooling/metamodel-generator/src/test/java/org/hibernate/processor/test/dao/Book.java index 5f38f0af63..7635f4184f 100644 --- a/tooling/metamodel-generator/src/test/java/org/hibernate/processor/test/dao/Book.java +++ b/tooling/metamodel-generator/src/test/java/org/hibernate/processor/test/dao/Book.java @@ -11,4 +11,7 @@ public class Book { @NaturalId String author; String text; int pages; + Type type; + + enum Type { Book, Magazine, Journal } } diff --git a/tooling/metamodel-generator/src/test/java/org/hibernate/processor/test/dao/Dao.java b/tooling/metamodel-generator/src/test/java/org/hibernate/processor/test/dao/Dao.java index c627d12478..3574492084 100644 --- a/tooling/metamodel-generator/src/test/java/org/hibernate/processor/test/dao/Dao.java +++ b/tooling/metamodel-generator/src/test/java/org/hibernate/processor/test/dao/Dao.java @@ -147,4 +147,7 @@ public interface Dao { @HQL("select b\nfrom Book b\nwhere b.isbn = :isbn") Book findByIsbnMultiline(String isbn); + + @HQL("from Book b where b.type = Magazine") + List findMagazines(); } diff --git a/tooling/metamodel-generator/src/test/java/org/hibernate/processor/test/dao/Dao2.java b/tooling/metamodel-generator/src/test/java/org/hibernate/processor/test/dao/Dao2.java new file mode 100644 index 0000000000..bb4d180011 --- /dev/null +++ b/tooling/metamodel-generator/src/test/java/org/hibernate/processor/test/dao/Dao2.java @@ -0,0 +1,28 @@ +package org.hibernate.processor.test.dao; + +import java.util.List; + +import org.hibernate.annotations.processing.HQL; + +import jakarta.persistence.EntityManager; + +public interface Dao2 { + + EntityManager getEntityManager(); + + // Simple name + @HQL("from Book b where b.type = Magazine") + List findMagazines1(); + + // Simple qualified name + @HQL("from Book b where b.type = Type.Magazine") + List findMagazines2(); + + // Canonical FQN + @HQL("from Book b where b.type = org.hibernate.processor.test.dao.Book.Type.Magazine") + List findMagazines3(); + + // Binary FQN + @HQL("from Book b where b.type = org.hibernate.processor.test.dao.Book$Type.Magazine") + List findMagazines4(); +} diff --git a/tooling/metamodel-generator/src/test/java/org/hibernate/processor/test/dao/DaoTest2.java b/tooling/metamodel-generator/src/test/java/org/hibernate/processor/test/dao/DaoTest2.java new file mode 100644 index 0000000000..4e048e02cd --- /dev/null +++ b/tooling/metamodel-generator/src/test/java/org/hibernate/processor/test/dao/DaoTest2.java @@ -0,0 +1,28 @@ +/* + * 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.dao; + +import org.hibernate.processor.test.util.CompilationTest; +import org.hibernate.processor.test.util.TestUtil; +import org.hibernate.processor.test.util.WithClasses; + +import org.junit.Test; + +import static org.hibernate.processor.test.util.TestUtil.assertMetamodelClassGeneratedFor; + +/** + * @author Gavin King + */ +public class DaoTest2 extends CompilationTest { + @Test + @WithClasses({ Book.class, Dao2.class }) + public void testDao() { + System.out.println( TestUtil.getMetaModelSourceAsString( Dao2.class ) ); + assertMetamodelClassGeneratedFor( Book.class ); + assertMetamodelClassGeneratedFor( Dao2.class ); + } +}