HHH-18162 index creation in HibernateProcessor

This stores an index of entities and enum values in the
entity.index directory. This approach has some downsides
but works around some limitations of the processor API.

Signed-off-by: Gavin King <gavin@hibernate.org>
This commit is contained in:
Gavin King 2024-05-23 15:53:15 +02:00
parent ed2fdce0a6
commit ebd8619f73
11 changed files with 245 additions and 48 deletions

View File

@ -13,6 +13,7 @@ import jakarta.persistence.metamodel.EntityType;
import jakarta.persistence.metamodel.ManagedType;
import jakarta.persistence.metamodel.Metamodel;
import org.checkerframework.checker.nullness.qual.Nullable;
import org.hibernate.Incubating;
import org.hibernate.graph.spi.RootGraphImplementor;
import org.hibernate.jpa.spi.JpaCompliance;
@ -89,7 +90,8 @@ public interface JpaMetamodel extends Metamodel {
String qualifyImportableName(String queryName);
Set<String> getAllowedEnumLiteralTexts(String enumValue);
@Nullable
Set<String> getEnumTypesForValue(String enumValue);
EnumJavaType<?> getEnumType(String prefix);

View File

@ -21,6 +21,7 @@ import java.util.TreeMap;
import java.util.concurrent.ConcurrentHashMap;
import java.util.stream.Collectors;
import org.checkerframework.checker.nullness.qual.Nullable;
import org.hibernate.boot.model.NamedEntityGraphDefinition;
import org.hibernate.boot.registry.classloading.spi.ClassLoaderService;
import org.hibernate.boot.registry.classloading.spi.ClassLoadingException;
@ -281,8 +282,8 @@ public class JpaMetamodelImpl implements JpaMetamodelImplementor, Serializable {
.collect( Collectors.toSet() );
}
@Override
public Set<String> getAllowedEnumLiteralTexts(String enumValue) {
@Override @Nullable
public Set<String> getEnumTypesForValue(String enumValue) {
return allowedEnumLiteralTexts.get(enumValue);
}

View File

@ -530,8 +530,8 @@ public class MappingMetamodelImpl extends QueryParameterBindingTypeResolverImpl
}
@Override
public Set<String> getAllowedEnumLiteralTexts(String enumValue) {
return jpaMetamodel.getAllowedEnumLiteralTexts(enumValue);
public Set<String> getEnumTypesForValue(String enumValue) {
return jpaMetamodel.getEnumTypesForValue(enumValue);
}
@Override

View File

@ -2603,7 +2603,7 @@ public class SemanticQueryBuilder<R> extends HqlParserBaseVisitor<Object> implem
ctx = ctx.getChild( 0 );
if ( ctx instanceof HqlParser.SimplePathContext ) {
return creationContext.getJpaMetamodel().getAllowedEnumLiteralTexts( ctx.getText() );
return creationContext.getJpaMetamodel().getEnumTypesForValue( ctx.getText() );
}
}
}

View File

@ -106,6 +106,9 @@ public final class Context {
private String[] includes = {"*"};
private String[] excludes = {};
private final Map<String, String> entityNameMappings = new HashMap<>();
private final Map<String, Set<String>> enumTypesByValue = new HashMap<>();
public Context(ProcessingEnvironment processingEnvironment) {
this.processingEnvironment = processingEnvironment;
@ -483,4 +486,24 @@ public final class Context {
|| elements.stream().noneMatch(member -> member instanceof ExecutableElement
&& getElementUtils().overrides((ExecutableElement) member, (ExecutableElement) inherited, type));
}
public Map<String, String> getEntityNameMappings() {
return entityNameMappings;
}
public void addEntityNameMapping(String entityName, String qualifiedName) {
entityNameMappings.put( entityName, qualifiedName );
}
public @Nullable String qualifiedNameForEntityName(String entityName) {
return entityNameMappings.get(entityName);
}
public Map<String,Set<String>> getEnumTypesByValue() {
return enumTypesByValue;
}
public void addEnumValue(String type, String value) {
enumTypesByValue.computeIfAbsent( value, s -> new HashSet<>() ).add( type );
}
}

View File

@ -22,13 +22,19 @@ import javax.lang.model.SourceVersion;
import javax.lang.model.element.AnnotationMirror;
import javax.lang.model.element.AnnotationValue;
import javax.lang.model.element.Element;
import javax.lang.model.element.ElementKind;
import javax.lang.model.element.ExecutableElement;
import javax.lang.model.element.PackageElement;
import javax.lang.model.element.TypeElement;
import javax.lang.model.type.DeclaredType;
import javax.lang.model.type.TypeKind;
import javax.lang.model.type.TypeMirror;
import javax.tools.Diagnostic;
import javax.tools.StandardLocation;
import java.io.IOException;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.io.Writer;
import java.util.Collection;
import java.util.HashSet;
import java.util.List;
@ -42,12 +48,36 @@ import static org.hibernate.processor.HibernateProcessor.ADD_GENERATED_ANNOTATIO
import static org.hibernate.processor.HibernateProcessor.ADD_GENERATION_DATE;
import static org.hibernate.processor.HibernateProcessor.ADD_SUPPRESS_WARNINGS_ANNOTATION;
import static org.hibernate.processor.HibernateProcessor.DEBUG_OPTION;
import static org.hibernate.processor.HibernateProcessor.EXCLUDE;
import static org.hibernate.processor.HibernateProcessor.FULLY_ANNOTATION_CONFIGURED_OPTION;
import static org.hibernate.processor.HibernateProcessor.INCLUDE;
import static org.hibernate.processor.HibernateProcessor.LAZY_XML_PARSING;
import static org.hibernate.processor.HibernateProcessor.ORM_XML_OPTION;
import static org.hibernate.processor.HibernateProcessor.PERSISTENCE_XML_OPTION;
import static org.hibernate.processor.HibernateProcessor.SUPPRESS_JAKARTA_DATA_METAMODEL;
import static org.hibernate.processor.util.Constants.*;
import static org.hibernate.processor.util.Constants.EMBEDDABLE;
import static org.hibernate.processor.util.Constants.ENTITY;
import static org.hibernate.processor.util.Constants.FIND;
import static org.hibernate.processor.util.Constants.HIB_FETCH_PROFILE;
import static org.hibernate.processor.util.Constants.HIB_FETCH_PROFILES;
import static org.hibernate.processor.util.Constants.HIB_FILTER_DEF;
import static org.hibernate.processor.util.Constants.HIB_FILTER_DEFS;
import static org.hibernate.processor.util.Constants.HIB_NAMED_NATIVE_QUERIES;
import static org.hibernate.processor.util.Constants.HIB_NAMED_NATIVE_QUERY;
import static org.hibernate.processor.util.Constants.HIB_NAMED_QUERIES;
import static org.hibernate.processor.util.Constants.HIB_NAMED_QUERY;
import static org.hibernate.processor.util.Constants.HQL;
import static org.hibernate.processor.util.Constants.JD_REPOSITORY;
import static org.hibernate.processor.util.Constants.MAPPED_SUPERCLASS;
import static org.hibernate.processor.util.Constants.NAMED_ENTITY_GRAPH;
import static org.hibernate.processor.util.Constants.NAMED_ENTITY_GRAPHS;
import static org.hibernate.processor.util.Constants.NAMED_NATIVE_QUERIES;
import static org.hibernate.processor.util.Constants.NAMED_NATIVE_QUERY;
import static org.hibernate.processor.util.Constants.NAMED_QUERIES;
import static org.hibernate.processor.util.Constants.NAMED_QUERY;
import static org.hibernate.processor.util.Constants.SQL;
import static org.hibernate.processor.util.Constants.SQL_RESULT_SET_MAPPING;
import static org.hibernate.processor.util.Constants.SQL_RESULT_SET_MAPPINGS;
import static org.hibernate.processor.util.TypeUtils.containsAnnotation;
import static org.hibernate.processor.util.TypeUtils.getAnnotationMirror;
import static org.hibernate.processor.util.TypeUtils.getAnnotationValue;
@ -85,7 +115,8 @@ import static org.hibernate.processor.util.TypeUtils.isClassOrRecordType;
ADD_GENERATION_DATE,
ADD_GENERATED_ANNOTATION,
ADD_SUPPRESS_WARNINGS_ANNOTATION,
SUPPRESS_JAKARTA_DATA_METAMODEL
SUPPRESS_JAKARTA_DATA_METAMODEL,
INCLUDE, EXCLUDE
})
public class HibernateProcessor extends AbstractProcessor {
@ -157,6 +188,8 @@ public class HibernateProcessor extends AbstractProcessor {
private static final boolean ALLOW_OTHER_PROCESSORS_TO_CLAIM_ANNOTATIONS = false;
public static final String ENTITY_INDEX = "entity.index";
private Context context;
@Override
@ -168,7 +201,7 @@ public class HibernateProcessor extends AbstractProcessor {
"Hibernate compile-time tooling " + Version.getVersionString()
);
boolean fullyAnnotationConfigured = handleSettings( processingEnvironment );
final boolean fullyAnnotationConfigured = handleSettings( processingEnvironment );
if ( !fullyAnnotationConfigured ) {
new JpaDescriptorParser( context ).parseXml();
if ( context.isFullyXmlConfigured() ) {
@ -275,6 +308,7 @@ public class HibernateProcessor extends AbstractProcessor {
if ( !elementsToRedo.isEmpty() ) {
context.logMessage( Diagnostic.Kind.ERROR, "Failed to generate code for " + elementsToRedo );
}
writeIndex();
}
else if ( context.isFullyXmlConfigured() ) {
context.logMessage(
@ -518,6 +552,9 @@ public class HibernateProcessor extends AbstractProcessor {
if ( isClassOrRecordType( element ) ) {
if ( hasAnnotation( element, ENTITY, MAPPED_SUPERCLASS, EMBEDDABLE ) ) {
final TypeElement typeElement = (TypeElement) element;
indexEntityName( typeElement );
indexEnumFields( typeElement );
final String qualifiedName = typeElement.getQualifiedName().toString();
final Metamodel alreadyExistingMetaEntity =
tryGettingExistingEntityFromContext( typeElement, qualifiedName );
@ -565,6 +602,54 @@ public class HibernateProcessor extends AbstractProcessor {
}
}
private void indexEntityName(TypeElement typeElement) {
final AnnotationMirror mirror = getAnnotationMirror( typeElement, ENTITY );
if ( mirror != null ) {
context.addEntityNameMapping( entityName( typeElement, mirror ),
typeElement.getQualifiedName().toString() );
}
}
private static String entityName(TypeElement entityType, AnnotationMirror mirror) {
final String className = entityType.getSimpleName().toString();
final AnnotationValue name = getAnnotationValue(mirror, "name" );
if (name != null) {
final String explicitName = name.getValue().toString();
if ( !explicitName.isEmpty() ) {
return explicitName;
}
}
return className;
}
private void indexEnumFields(TypeElement typeElement) {
for ( Element member : context.getAllMembers(typeElement) ) {
switch ( member.getKind() ) {
case FIELD:
indexEnumValues( member.asType() );
break;
case METHOD:
indexEnumValues( ((ExecutableElement) member).getReturnType() );
break;
}
}
}
private void indexEnumValues(TypeMirror type) {
if ( type.getKind() == TypeKind.DECLARED ) {
final DeclaredType declaredType = (DeclaredType) type;
final TypeElement fieldType = (TypeElement) declaredType.asElement();
if ( fieldType.getKind() == ElementKind.ENUM ) {
for (Element enumMember : fieldType.getEnclosedElements() ) {
if ( enumMember.getKind() == ElementKind.ENUM_CONSTANT) {
context.addEnumValue( fieldType.getQualifiedName().toString(),
enumMember.getSimpleName().toString() );
}
}
}
}
}
private void handleRootElementAuxiliaryAnnotationMirrors(final Element element) {
if ( element instanceof TypeElement ) {
final AnnotationMetaEntity metaEntity =
@ -615,4 +700,33 @@ public class HibernateProcessor extends AbstractProcessor {
}
}
private void writeIndex() {
final ProcessingEnvironment processingEnvironment = context.getProcessingEnvironment();
context.getEntityNameMappings().forEach((entityName, className) -> {
try (Writer writer = processingEnvironment.getFiler()
.createResource(StandardLocation.SOURCE_OUTPUT, ENTITY_INDEX, entityName)
.openWriter()) {
writer.append(className);
}
catch (IOException e) {
processingEnvironment.getMessager()
.printMessage(Diagnostic.Kind.WARNING,
"could not write entity index " + e.getMessage());
}
});
context.getEnumTypesByValue().forEach((valueName, enumTypeNames) -> {
try (Writer writer = processingEnvironment.getFiler()
.createResource(StandardLocation.SOURCE_OUTPUT, ENTITY_INDEX, '.' + valueName)
.openWriter()) {
for (String enumTypeName : enumTypeNames) {
writer.append(enumTypeName).append(" ");
}
}
catch (IOException e) {
processingEnvironment.getMessager()
.printMessage(Diagnostic.Kind.WARNING,
"could not write entity index " + e.getMessage());
}
});
}
}

View File

@ -84,7 +84,8 @@ public abstract class AnnotationMeta implements Metamodel {
final AnnotationValue nameValue = getAnnotationValue( mirror, "name" );
if ( nameValue != null ) {
final String name = nameValue.getValue().toString();
final boolean reportErrors = getContext().checkNamedQuery( name );
final Context context = getContext();
final boolean reportErrors = context.checkNamedQuery( name );
final AnnotationValue value = getAnnotationValue( mirror, "query" );
if ( value != null ) {
final Object query = value.getValue();
@ -98,9 +99,10 @@ public abstract class AnnotationMeta implements Metamodel {
// If we are in the scope of @CheckHQL, semantic errors in the
// query result in compilation errors. Otherwise, they only
// result in warnings, so we don't break working code.
new WarningErrorHandler( getContext(), getElement(), mirror, value, hql,
new WarningErrorHandler( context, getElement(), mirror, value, hql,
reportErrors, checkHql ),
ProcessorSessionFactory.create( getContext().getProcessingEnvironment() )
ProcessorSessionFactory.create( context.getProcessingEnvironment(),
context.getEntityNameMappings(), context.getEnumTypesByValue() )
);
if ( statement instanceof SqmSelectStatement
&& isQueryMethodName( name ) ) {
@ -112,7 +114,7 @@ public abstract class AnnotationMeta implements Metamodel {
isRepository(),
getSessionType(),
getSessionVariableName(),
getContext().addNonnullAnnotation()
context.addNonnullAnnotation()
)
);
}

View File

@ -2469,7 +2469,8 @@ public class AnnotationMetaEntity extends AnnotationMeta {
returnType,
true,
new ErrorHandler( context, isLocal(method) ? method : element, mirror, value, hql ),
ProcessorSessionFactory.create( context.getProcessingEnvironment() )
ProcessorSessionFactory.create( context.getProcessingEnvironment(),
context.getEntityNameMappings(), context.getEnumTypesByValue() )
);
if ( statement != null ) {
if ( statement instanceof SqmSelectStatement ) {

View File

@ -7,6 +7,7 @@
package org.hibernate.processor.annotation;
import org.checkerframework.checker.nullness.qual.Nullable;
import org.hibernate.processor.Context;
import org.hibernate.processor.model.MetaAttribute;
import org.hibernate.processor.model.Metamodel;
import org.hibernate.processor.util.Constants;
@ -18,6 +19,7 @@ import org.hibernate.type.descriptor.java.JavaType;
import javax.lang.model.element.ModuleElement;
import javax.lang.model.element.TypeElement;
import javax.lang.model.util.Elements;
import java.util.List;
import java.util.TreeSet;
@ -194,13 +196,19 @@ class NamedQueryMethod implements MetaAttribute {
}
private @Nullable TypeElement entityType(String entityName) {
final Context context = annotationMeta.getContext();
final Elements elementUtils = context.getElementUtils();
final String qualifiedName = context.qualifiedNameForEntityName(entityName);
if ( qualifiedName != null ) {
return elementUtils.getTypeElement(qualifiedName);
}
TypeElement symbol =
findEntityByUnqualifiedName( entityName,
annotationMeta.getContext().getElementUtils().getModuleElement("") );
elementUtils.getModuleElement("") );
if ( symbol != null ) {
return symbol;
}
for ( ModuleElement module : annotationMeta.getContext().getElementUtils().getAllModuleElements() ) {
for ( ModuleElement module : elementUtils.getAllModuleElements() ) {
symbol = findEntityByUnqualifiedName( entityName, module );
if ( symbol != null ) {
return symbol;

View File

@ -6,6 +6,7 @@
*/
package org.hibernate.processor.validation;
import org.checkerframework.checker.nullness.qual.Nullable;
import org.hibernate.CustomEntityDirtinessStrategy;
import org.hibernate.EntityNameResolver;
import org.hibernate.MappingException;
@ -58,7 +59,6 @@ import org.hibernate.metamodel.internal.MetadataContext;
import org.hibernate.metamodel.internal.RuntimeMetamodelsImpl;
import org.hibernate.metamodel.mapping.JdbcMapping;
import org.hibernate.metamodel.model.domain.DomainType;
import org.hibernate.metamodel.model.domain.EmbeddableDomainType;
import org.hibernate.metamodel.model.domain.EntityDomainType;
import org.hibernate.metamodel.model.domain.ManagedDomainType;
import org.hibernate.metamodel.model.domain.PersistentAttribute;
@ -729,6 +729,11 @@ public abstract class MockSessionFactory
public EntityPersister findEntityDescriptor(String entityName) {
return createEntityPersister(entityName);
}
@Override
public Set<String> getEnumTypesForValue(String enumValue) {
return MockSessionFactory.this.getEnumTypesForValue(enumValue);
}
}
@Override
@ -868,9 +873,9 @@ public abstract class MockSessionFactory
return null;
}
@Override
public Set<String> getAllowedEnumLiteralTexts(String enumValue) {
return MockSessionFactory.this.getAllowedEnumLiteralTexts().get(enumValue);
@Override @Nullable
public Set<String> getEnumTypesForValue(String enumValue) {
return MockSessionFactory.this.getEnumTypesForValue(enumValue);
}
@Override
@ -879,8 +884,9 @@ public abstract class MockSessionFactory
}
}
Map<String, Set<String>> getAllowedEnumLiteralTexts() {
return emptyMap();
@Nullable
Set<String> getEnumTypesForValue(String value) {
return emptySet();
}
class MockMappedDomainType<X> extends MappedSuperclassTypeImpl<X>{

View File

@ -7,6 +7,7 @@
package org.hibernate.processor.validation;
import jakarta.persistence.AccessType;
import org.checkerframework.checker.nullness.qual.Nullable;
import org.hibernate.PropertyNotFoundException;
import org.hibernate.engine.spi.Mapping;
import org.hibernate.type.BasicType;
@ -21,6 +22,7 @@ import org.hibernate.type.descriptor.jdbc.JdbcType;
import org.hibernate.type.descriptor.jdbc.VarcharJdbcType;
import org.hibernate.type.internal.BasicTypeImpl;
import javax.annotation.processing.Filer;
import javax.annotation.processing.ProcessingEnvironment;
import javax.lang.model.element.AnnotationMirror;
import javax.lang.model.element.AnnotationValue;
@ -40,10 +42,13 @@ import javax.lang.model.type.TypeMirror;
import javax.lang.model.type.TypeVariable;
import javax.lang.model.util.Elements;
import javax.lang.model.util.Types;
import javax.tools.StandardLocation;
import java.beans.Introspector;
import java.io.BufferedReader;
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;
@ -65,8 +70,11 @@ import static org.hibernate.processor.util.Constants.JAVA_OBJECT;
@SuppressWarnings("nullness")
public abstract class ProcessorSessionFactory extends MockSessionFactory {
public static MockSessionFactory create(ProcessingEnvironment environment) {
return instance.make(environment);
public static MockSessionFactory create(
ProcessingEnvironment environment,
Map<String,String> entityNameMappings,
Map<String, Set<String>> enumTypesByValue) {
return instance.make(environment, entityNameMappings, enumTypesByValue);
}
static final Mocker<ProcessorSessionFactory> instance = Mocker.variadic(ProcessorSessionFactory.class);
@ -80,10 +88,19 @@ public abstract class ProcessorSessionFactory extends MockSessionFactory {
private final Elements elementUtil;
private final Types typeUtil;
private final Filer filer;
private final Map<String, String> entityNameMappings;
private final Map<String, Set<String>> enumTypesByValue;
public ProcessorSessionFactory(ProcessingEnvironment processingEnv) {
elementUtil = processingEnv.getElementUtils();
typeUtil = processingEnv.getTypeUtils();
public ProcessorSessionFactory(
ProcessingEnvironment processingEnvironment,
Map<String,String> entityNameMappings,
Map<String, Set<String>> enumTypesByValue) {
elementUtil = processingEnvironment.getElementUtils();
typeUtil = processingEnvironment.getTypeUtils();
filer = processingEnvironment.getFiler();
this.entityNameMappings = entityNameMappings;
this.enumTypesByValue = enumTypesByValue;
}
@Override
@ -196,28 +213,27 @@ public abstract class ProcessorSessionFactory extends MockSessionFactory {
: IntegerJdbcType.INSTANCE;
}
final Map<String, Set<String>> result = new HashMap<>();
public static final String ENTITY_INDEX = "entity.index";
@Override
Map<String, Set<String>> getAllowedEnumLiteralTexts() {
//TODO: elementUtil.getAllModuleElements();
if ( result.isEmpty() ) {
for (Element mod : elementUtil.getModuleElement("").getEnclosedElements()) {
for (Element element : mod.getEnclosedElements()) {
if (element.getKind() == ElementKind.ENUM) {
TypeElement typeElement = (TypeElement) element;
for (Element member : element.getEnclosedElements()) {
if (member.getKind() == ElementKind.ENUM_CONSTANT) {
String name = member.getSimpleName().toString();
result.computeIfAbsent( name, s -> new HashSet<>() )
.add( typeElement.getQualifiedName().toString() );
}
}
}
}
}
@Override @Nullable
Set<String> getEnumTypesForValue(String value) {
Set<String> result = enumTypesByValue.get(value);
if ( result != null ) {
return result;
}
return result;
try (Reader reader = filer.getResource(StandardLocation.SOURCE_OUTPUT, ENTITY_INDEX, value)
.openReader(true); BufferedReader buffered = new BufferedReader(reader) ) {
return Set.of(buffered.readLine().split(" "));
}
catch (IOException e) {
}
try (Reader reader = filer.getResource(StandardLocation.CLASS_PATH, ENTITY_INDEX, '.' + value)
.openReader(true); BufferedReader buffered = new BufferedReader(reader) ) {
return Set.of(buffered.readLine().split(" "));
}
catch (IOException e) {
}
return null;
}
private static Type elementCollectionElementType(TypeElement elementType,
@ -430,6 +446,30 @@ public abstract class ProcessorSessionFactory extends MockSessionFactory {
if ( cached != null ) {
return cached;
}
String qualifiedName = entityNameMappings.get(entityName);
if ( qualifiedName != null ) {
TypeElement result = elementUtil.getTypeElement(qualifiedName);
entityCache.put(entityName, result);
return result;
}
StandardLocation location = StandardLocation.SOURCE_OUTPUT;
try (Reader reader = filer.getResource(location, ENTITY_INDEX, entityName)
.openReader(true); BufferedReader buffered = new BufferedReader(reader) ) {
TypeElement result = elementUtil.getTypeElement(buffered.readLine());
entityCache.put(entityName, result);
return result;
}
catch (IOException e) {
}
try (Reader reader = filer.getResource(StandardLocation.CLASS_PATH, ENTITY_INDEX, entityName)
.openReader(true); BufferedReader buffered = new BufferedReader(reader) ) {
TypeElement result = elementUtil.getTypeElement(buffered.readLine());
entityCache.put(entityName, result);
return result;
}
catch (IOException e) {
}
TypeElement symbol =
findEntityByUnqualifiedName(entityName,
elementUtil.getModuleElement(""));