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); String qualifyImportableName(String queryName);
@Nullable @Nullable Set<String> getEnumTypesForValue(String enumValue);
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); 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.JavaType;
import org.hibernate.type.descriptor.java.spi.DynamicModelJavaType; import org.hibernate.type.descriptor.java.spi.DynamicModelJavaType;
import org.hibernate.type.descriptor.java.spi.EntityJavaType; import org.hibernate.type.descriptor.java.spi.EntityJavaType;
import org.hibernate.type.descriptor.java.spi.JavaTypeRegistry;
import org.hibernate.type.spi.TypeConfiguration; import org.hibernate.type.spi.TypeConfiguration;
import jakarta.persistence.EntityGraph; 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<String, ManagedDomainType<?>> managedTypeByName = new TreeMap<>();
private final Map<Class<?>, ManagedDomainType<?>> managedTypeByClass = new HashMap<>(); private final Map<Class<?>, ManagedDomainType<?>> managedTypeByClass = new HashMap<>();
private JpaMetaModelPopulationSetting jpaMetaModelPopulationSetting; 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<>(); private final transient Map<String, RootGraphImplementor<?>> entityGraphMap = new ConcurrentHashMap<>();
@ -282,43 +282,19 @@ public class JpaMetamodelImpl implements JpaMetamodelImplementor, Serializable {
.collect( Collectors.toSet() ); .collect( Collectors.toSet() );
} }
@Override @Nullable @Override
public Set<String> getEnumTypesForValue(String enumValue) { public @Nullable Set<String> getEnumTypesForValue(String enumValue) {
return allowedEnumLiteralTexts.get(enumValue); return allowedEnumLiteralsToEnumTypeNames.get( enumValue);
} }
@Override @Override
public EnumJavaType<?> getEnumType(String prefix) { public EnumJavaType<?> getEnumType(String className) {
final ClassLoaderService classLoaderService = return enumJavaTypes.get( className );
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;
} }
@Override @Override
public <E extends Enum<E>> E enumValue(EnumJavaType<E> enumType, String terminal) { public <E extends Enum<E>> E enumValue(EnumJavaType<E> enumType, String enumValueName) {
return Enum.valueOf( enumType.getJavaTypeClass(), terminal ); return Enum.valueOf( enumType.getJavaTypeClass(), enumValueName );
} }
@Override @Override
@ -671,14 +647,15 @@ public class JpaMetamodelImpl implements JpaMetamodelImplementor, Serializable {
final Class<? extends Enum<?>> enumJavaClass = enumJavaType.getJavaTypeClass(); final Class<? extends Enum<?>> enumJavaClass = enumJavaType.getJavaTypeClass();
final Enum<?>[] enumConstants = enumJavaClass.getEnumConstants(); final Enum<?>[] enumConstants = enumJavaClass.getEnumConstants();
for ( Enum<?> enumConstant : enumConstants ) { for ( Enum<?> enumConstant : enumConstants ) {
allowedEnumLiteralTexts addAllowedEnumLiteralsToEnumTypesMap(
.computeIfAbsent( enumConstant.name(), s -> new HashSet<>() ) allowedEnumLiteralsToEnumTypeNames,
.add( enumJavaClass.getName() ); enumConstant.name(),
enumJavaClass.getSimpleName(),
final String simpleQualifiedName = enumJavaClass.getSimpleName() + "." + enumConstant.name(); enumJavaClass.getCanonicalName(),
allowedEnumLiteralTexts enumJavaClass.getName()
.computeIfAbsent( simpleQualifiedName, s -> new HashSet<>() ) );
.add( enumJavaClass.getName() ); enumJavaTypes.put( enumJavaClass.getName(), enumJavaType );
enumJavaTypes.put( enumJavaClass.getCanonicalName(), enumJavaType );
} }
} }
} ); } );
@ -686,6 +663,33 @@ public class JpaMetamodelImpl implements JpaMetamodelImplementor, Serializable {
applyNamedEntityGraphs( namedEntityGraphDefinitions ); 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( private EntityDomainType<?> locateOrBuildEntityType(
PersistentClass persistentClass, PersistentClass persistentClass,
MetadataContext context, MetadataContext context,

View File

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

View File

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

View File

@ -49,6 +49,7 @@ import java.io.IOException;
import java.io.Reader; import java.io.Reader;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.HashMap; import java.util.HashMap;
import java.util.HashSet;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Set; 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.root;
import static org.hibernate.internal.util.StringHelper.split; import static org.hibernate.internal.util.StringHelper.split;
import static org.hibernate.internal.util.StringHelper.unroot; 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; 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 Types typeUtil;
private final Filer filer; private final Filer filer;
private final Map<String, String> entityNameMappings; private final Map<String, String> entityNameMappings;
private final Map<String, Set<String>> enumTypesByValue; private final Map<String, Set<String>> allowedEnumLiteralsToEnumTypeNames;
public ProcessorSessionFactory( public ProcessorSessionFactory(
ProcessingEnvironment processingEnvironment, ProcessingEnvironment processingEnvironment,
@ -100,7 +102,23 @@ public abstract class ProcessorSessionFactory extends MockSessionFactory {
typeUtil = processingEnvironment.getTypeUtils(); typeUtil = processingEnvironment.getTypeUtils();
filer = processingEnvironment.getFiler(); filer = processingEnvironment.getFiler();
this.entityNameMappings = entityNameMappings; 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 @Override
@ -217,7 +235,7 @@ public abstract class ProcessorSessionFactory extends MockSessionFactory {
@Override @Nullable @Override @Nullable
Set<String> getEnumTypesForValue(String value) { Set<String> getEnumTypesForValue(String value) {
Set<String> result = enumTypesByValue.get(value); Set<String> result = allowedEnumLiteralsToEnumTypeNames.get( value);
if ( result != null ) { if ( result != null ) {
return result; return result;
} }
@ -631,7 +649,18 @@ public abstract class ProcessorSessionFactory extends MockSessionFactory {
@Override @Override
boolean isEnum(String className) { 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; return typeElement != null && typeElement.getKind() == ElementKind.ENUM;
} }

View File

@ -11,4 +11,7 @@ public class Book {
@NaturalId String author; @NaturalId String author;
String text; String text;
int pages; 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") @HQL("select b\nfrom Book b\nwhere b.isbn = :isbn")
Book findByIsbnMultiline(String 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 );
}
}