HHH-17782, HHH-17901 Support enum literals in annotation processor HQL validation

This commit is contained in:
Christian Beikov 2024-04-02 20:29:07 +02:00
parent de3a4c0af9
commit 5a889f7d56
9 changed files with 148 additions and 55 deletions

View File

@ -90,12 +90,11 @@ public interface JpaMetamodel extends Metamodel {
String qualifyImportableName(String queryName);
@Nullable
Set<String> getEnumTypesForValue(String enumValue);
@Nullable Set<String> getEnumTypesForValue(String enumValue);
EnumJavaType<?> getEnumType(String prefix);
EnumJavaType<?> getEnumType(String className);
<E extends Enum<E>> E enumValue(EnumJavaType<E> enumType, String terminal);
<E extends Enum<E>> E enumValue(EnumJavaType<E> enumType, String enumValueName);
JavaType<?> getJavaConstantType(String className, String fieldName);

View File

@ -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<String, ManagedDomainType<?>> managedTypeByName = new TreeMap<>();
private final Map<Class<?>, ManagedDomainType<?>> managedTypeByClass = new HashMap<>();
private JpaMetaModelPopulationSetting jpaMetaModelPopulationSetting;
private final Map<String, Set<String>> allowedEnumLiteralTexts = new HashMap<>();
private final Map<String, Set<String>> allowedEnumLiteralsToEnumTypeNames = new HashMap<>();
private final Map<String, EnumJavaType<?>> enumJavaTypes = new HashMap<>();
private final transient Map<String, RootGraphImplementor<?>> entityGraphMap = new ConcurrentHashMap<>();
@ -282,43 +282,19 @@ public class JpaMetamodelImpl implements JpaMetamodelImplementor, Serializable {
.collect( Collectors.toSet() );
}
@Override @Nullable
public Set<String> getEnumTypesForValue(String enumValue) {
return allowedEnumLiteralTexts.get(enumValue);
@Override
public @Nullable Set<String> 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 extends Enum<E>> E enumValue(EnumJavaType<E> enumType, String terminal) {
return Enum.valueOf( enumType.getJavaTypeClass(), terminal );
public <E extends Enum<E>> E enumValue(EnumJavaType<E> enumType, String enumValueName) {
return Enum.valueOf( enumType.getJavaTypeClass(), enumValueName );
}
@Override
@ -671,14 +647,15 @@ public class JpaMetamodelImpl implements JpaMetamodelImplementor, Serializable {
final Class<? extends Enum<?>> 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<String, Set<String>> 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,

View File

@ -550,8 +550,8 @@ public class MappingMetamodelImpl extends QueryParameterBindingTypeResolverImpl
}
@Override
public <E extends Enum<E>> E enumValue(EnumJavaType<E> enumType, String terminal) {
return jpaMetamodel.enumValue(enumType, terminal);
public <E extends Enum<E>> E enumValue(EnumJavaType<E> enumType, String enumValueName) {
return jpaMetamodel.enumValue( enumType, enumValueName );
}
@Override

View File

@ -873,8 +873,8 @@ public abstract class MockSessionFactory
return null;
}
@Override @Nullable
public Set<String> getEnumTypesForValue(String enumValue) {
@Override
public @Nullable Set<String> getEnumTypesForValue(String enumValue) {
return MockSessionFactory.this.getEnumTypesForValue(enumValue);
}
@ -884,8 +884,7 @@ public abstract class MockSessionFactory
}
}
@Nullable
Set<String> getEnumTypesForValue(String value) {
@Nullable Set<String> getEnumTypesForValue(String value) {
return emptySet();
}

View File

@ -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<String, String> entityNameMappings;
private final Map<String, Set<String>> enumTypesByValue;
private final Map<String, Set<String>> 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<String, Set<String>> allowedEnumLiteralsToEnumTypeNames = new HashMap<>( enumTypesByValue.size() << 2 );
for ( Map.Entry<String, Set<String>> 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<String> getEnumTypesForValue(String value) {
Set<String> result = enumTypesByValue.get(value);
Set<String> 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;
}

View File

@ -11,4 +11,7 @@ public class Book {
@NaturalId String author;
String text;
int pages;
Type type;
enum Type { Book, Magazine, Journal }
}

View File

@ -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<Book> findMagazines();
}

View File

@ -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<Book> findMagazines1();
// Simple qualified name
@HQL("from Book b where b.type = Type.Magazine")
List<Book> findMagazines2();
// Canonical FQN
@HQL("from Book b where b.type = org.hibernate.processor.test.dao.Book.Type.Magazine")
List<Book> findMagazines3();
// Binary FQN
@HQL("from Book b where b.type = org.hibernate.processor.test.dao.Book$Type.Magazine")
List<Book> findMagazines4();
}

View File

@ -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 <http://www.gnu.org/licenses/lgpl-2.1.html>.
*/
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 );
}
}