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 24474ae8f2..daeecb3e82 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 @@ -4,11 +4,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; @@ -18,7 +23,12 @@ import java.io.PrintWriter; 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. @@ -39,7 +49,7 @@ public final class ClassWriter { String body = generateBody( entity, context ).toString(); FileObject fo = context.getProcessingEnvironment().getFiler().createSourceFile( - getFullyQualifiedClassName( entity, metaModelPackage ), + getFullyQualifiedClassName( entity ), entity.getElement() ); OutputStream os = fo.openOutputStream(); @@ -101,6 +111,15 @@ public final class ClassWriter { pw.println(); final List members = entity.getMembers(); + for ( MetaAttribute metaMember : members ) { + if ( metaMember instanceof InnerClassMetaAttribute innerClass ) { + generateBody( innerClass.getMetaEntity(), context ) + .toString().lines() + .forEach(line -> pw.println('\t' + line)); + context.markGenerated( innerClass.getMetaEntity() ); + } + } + for ( MetaAttribute metaMember : members ) { if ( metaMember.hasStringAttribute() ) { metaMember.getAttributeNameDeclarationString().lines() @@ -133,16 +152,29 @@ public final class ClassWriter { } 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 " ); @@ -152,13 +184,21 @@ public final class ClassWriter { 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) { @@ -166,20 +206,14 @@ public final class ClassWriter { 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) { @@ -227,6 +261,7 @@ public final class ClassWriter { 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 8350400e6f..04efd972df 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 final class Context { 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 88e5696910..78f514e867 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 @@ -7,6 +7,7 @@ package org.hibernate.processor; 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; @@ -22,6 +23,7 @@ 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.Modifier; import javax.lang.model.element.PackageElement; import javax.lang.model.element.QualifiedNameable; import javax.lang.model.element.TypeElement; @@ -82,6 +84,7 @@ import static org.hibernate.processor.util.TypeUtils.getAnnotationMirror; 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. @@ -358,59 +361,101 @@ public class HibernateProcessor extends AbstractProcessor { } for ( Element element : roundEnvironment.getRootElements() ) { - try { - if ( !included( element ) - || hasAnnotation( element, Constants.EXCLUDE ) - || hasPackageAnnotation( element, Constants.EXCLUDE ) ) { - // skip it completely + 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 ) { + 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 ( 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 ); - } - else if ( element instanceof TypeElement typeElement ) { - 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 parentElement ) { + final String key = parentElement.getQualifiedName().toString(); + if ( context.getMetamodel( key ) instanceof AnnotationMetaEntity parentMetaEntity ) { + parentMeta = parentMetaEntity; } } + final NonManagedMetamodel metaEntity = + NonManagedMetamodel .create( + typeElement, context, + false, parentMeta ); + context.addMetaEntity( metaEntity.getQualifiedName(), metaEntity ); + if ( context.generateJakartaDataStaticMetamodel()) { + AnnotationMetaEntity parentDataMeta = null; + if ( parent instanceof TypeElement parentElement ) { + final String key = parentElement.getQualifiedName().toString(); + if ( context.getDataMetaEntity( key ) instanceof AnnotationMetaEntity parentMetaEntity ) { + parentDataMeta = parentMetaEntity; + } + } + 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) { @@ -430,7 +475,7 @@ public class HibernateProcessor extends AbstractProcessor { } 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 ); @@ -439,7 +484,7 @@ public class HibernateProcessor extends AbstractProcessor { } 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 ); @@ -516,6 +561,18 @@ public class HibernateProcessor extends AbstractProcessor { return false; } + private static boolean enclosesEntityOrEmbeddable(Element element) { + if ( !(element instanceof TypeElement typeElement) ) { + return false; + } + for ( final Element enclosedElement : typeElement.getEnclosedElements() ) { + if ( isEntityOrEmbeddable( enclosedElement ) || enclosesEntityOrEmbeddable( enclosedElement ) ) { + return true; + } + } + return false; + } + private static boolean isEntityOrEmbeddable(Element element) { return hasAnnotation( element, @@ -547,7 +604,7 @@ public class HibernateProcessor extends AbstractProcessor { ); } - 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; @@ -564,12 +621,20 @@ public class HibernateProcessor extends AbstractProcessor { + "' since XML configuration is metadata complete."); } else { + AnnotationMetaEntity parentMetaEntity = null; + if ( parent instanceof TypeElement parentTypeElement ) { + if ( context.getMetamodel( + parentTypeElement.getQualifiedName().toString() ) + instanceof AnnotationMetaEntity pme ) { + parentMetaEntity = pme; + } + } 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 ); } @@ -583,10 +648,17 @@ public class HibernateProcessor extends AbstractProcessor { // let a handwritten metamodel "override" the generated one // (this is used in the Jakarta Data TCK) && !hasHandwrittenMetamodel(element) ) { + AnnotationMetaEntity parentDataEntity = null; + if ( parent instanceof TypeElement parentTypeElement ) { + if ( context.getDataMetaEntity( parentTypeElement.getQualifiedName().toString() ) + instanceof AnnotationMetaEntity pme ) { + parentDataEntity = pme; + } + } final AnnotationMetaEntity dataMetaEntity = AnnotationMetaEntity.create( typeElement, context, requiresLazyMemberInitialization, - true, true ); + true, true, parentDataEntity ); // final Metamodel alreadyExistingDataMetaEntity = // tryGettingExistingDataEntityFromContext( mirror, '_' + qualifiedName ); // if ( alreadyExistingDataMetaEntity != 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 65b547399c..388268119c 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 @@ -80,11 +80,12 @@ import static org.hibernate.processor.annotation.QueryMethod.isOrderParam; 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; @@ -110,6 +111,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; @@ -163,26 +165,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(); } @@ -225,18 +233,6 @@ public class AnnotationMetaEntity extends AnnotationMeta { 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 ) { @@ -246,8 +242,8 @@ public class AnnotationMetaEntity extends AnnotationMeta { } @Override - public @Nullable String getSupertypeName() { - return repository ? null : findMappedSuperClass( this, context ); + public @Nullable Element getSuperTypeElement() { + return repository ? null : findMappedSuperElement( this, context ); } @Override @@ -270,6 +266,14 @@ public class AnnotationMetaEntity extends AnnotationMeta { 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; @@ -363,7 +367,7 @@ public class AnnotationMetaEntity extends AnnotationMeta { .toString(); } - protected final void init() { + protected void init() { getContext().logMessage( Diagnostic.Kind.OTHER, "Initializing type '" + getQualifiedName() + "'" ); setupSession(); 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 a8c322f918..4e90e60f19 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 @@ -11,6 +11,7 @@ import org.hibernate.processor.model.ImportContext; 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; @@ -69,7 +70,7 @@ public class AnnotationMetaPackage extends AnnotationMeta { } @Override - public @Nullable String getSupertypeName() { + public @Nullable Element getSuperTypeElement() { return null; } 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 9e512a0759..b7b87abfcc 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 @@ -66,7 +66,7 @@ public class DefaultConstructor implements MetaAttribute { 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 0000000000..aa748575da --- /dev/null +++ b/tooling/metamodel-generator/src/main/java/org/hibernate/processor/annotation/InnerClassMetaAttribute.java @@ -0,0 +1,73 @@ +/* + * SPDX-License-Identifier: LGPL-2.1-or-later + * Copyright Red Hat Inc. and Hibernate Authors + */ +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 0000000000..d3129af10c --- /dev/null +++ b/tooling/metamodel-generator/src/main/java/org/hibernate/processor/annotation/NonManagedMetamodel.java @@ -0,0 +1,39 @@ +/* + * SPDX-License-Identifier: LGPL-2.1-or-later + * Copyright Red Hat Inc. and Hibernate Authors + */ +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 c49a7e2e33..cecddea531 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 @@ -67,7 +67,7 @@ public class RepositoryConstructor implements MetaAttribute { final StringBuilder declaration = new StringBuilder(); declaration .append('\n'); - if ( annotationMetaEntity.getSupertypeName() == null ) { + if ( annotationMetaEntity.getSuperTypeElement() == null ) { declaration .append("protected "); if ( !dataRepository ) { @@ -96,7 +96,7 @@ public class RepositoryConstructor implements MetaAttribute { .append(" ") .append(sessionVariableName) .append(") {\n"); - if ( annotationMetaEntity.getSupertypeName() != null ) { + if ( annotationMetaEntity.getSuperTypeElement() != null ) { declaration .append("\tsuper(") .append(sessionVariableName) @@ -112,7 +112,7 @@ public class RepositoryConstructor implements MetaAttribute { } 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 2a6c5ca026..49cdfb09e6 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 @@ -19,7 +19,7 @@ public interface Metamodel extends ImportContext { String getQualifiedName(); - @Nullable String getSupertypeName(); + @Nullable Element getSuperTypeElement(); String getPackageName(); 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 83bc5a54fa..6fd474b51c 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 @@ -98,6 +98,9 @@ public final class StringUtil { } public static String getUpperUnderscoreCaseFromLowerCamelCase(String lowerCamelCaseString) { + if ( lowerCamelCaseString.length() == 1 && isUpperCase( lowerCamelCaseString.charAt( 0 ) ) ) { + return "_" + lowerCamelCaseString; + } final StringBuilder result = new StringBuilder(); int position = 0; while ( position < lowerCamelCaseString.length() ) { @@ -116,4 +119,16 @@ public final class StringUtil { && isUpperCase( string.charAt( 0 ) ) && 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 556e6bec4b..33afa615b0 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 @@ -598,7 +598,7 @@ public final class TypeUtils { } } - 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 typeElement ) { TypeMirror superClass = typeElement.getSuperclass(); @@ -607,7 +607,7 @@ public final class TypeUtils { 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(); } @@ -667,6 +667,10 @@ public final class TypeUtils { 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 23d18b5e19..6a0fd9c0b7 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 @@ -113,6 +113,8 @@ import org.hibernate.type.MapType; 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; @@ -120,6 +122,7 @@ import org.hibernate.type.descriptor.jdbc.JdbcTypeIndicators; 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; @@ -280,7 +283,7 @@ public abstract class MockSessionFactory abstract boolean isEntityDefined(String entityName); - abstract String findEntityName(String typeName); + abstract TypeElement findEntityClass(String entityName); abstract String qualifyName(String entityName); @@ -681,6 +684,39 @@ public abstract class MockSessionFactory 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); @@ -774,21 +810,21 @@ public abstract class MockSessionFactory @Override public EntityDomainType entity(String entityName) { - return isEntityDefined( entityName ) - ? new MockEntityDomainType<>( entityName ) - : null; - } - - @Override - public @Nullable EntityDomainType findEntityType(@Nullable 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; } } + @Override + public @Nullable EntityDomainType findEntityType(@Nullable String entityName) { + return entity( entityName ); + } + @Override public String qualifyImportableName(String queryName) { if (isClassDefined(queryName)) { @@ -825,9 +861,12 @@ public abstract class MockSessionFactory @Override public EntityDomainType findEntityType(Class cls) { - return isEntityDefined( cls.getName() ) - ? new MockEntityDomainType( cls.getName() ) - : null; + if ( isEntityDefined( cls.getName() ) ) { + return new MockEntityDomainType<>( cls.getName(), new MockJavaType( cls.getName() )); + } + else { + return null; + } } @Override @@ -906,8 +945,8 @@ public abstract class MockSessionFactory 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()); } @@ -991,7 +1030,7 @@ public abstract class MockSessionFactory if (!entry.getValue().getEntityName().equals(getHibernateEntityName()) && isSubtype(entry.getValue().getEntityName(), getHibernateEntityName())) { final PersistentAttribute subattribute - = new MockEntityDomainType<>(entry.getValue().getEntityName()).findAttribute(name); + = new MockEntityDomainType<>(entry.getValue().getEntityName(), new MockJavaType<>(entry.getValue().getEntityName()) ).findAttribute(name); if (subattribute != null) { return (SqmPathSource) subattribute; } @@ -1037,7 +1076,7 @@ public abstract class MockSessionFactory owner, name, AttributeClassification.MANY_TO_ONE, - new MockEntityDomainType<>(type.getName()), + new MockEntityDomainType<>(type.getName(), new MockJavaType<>(type.getName())), null, null, false, @@ -1095,7 +1134,9 @@ public abstract class MockSessionFactory private DomainType getDomainType(String entityName, CollectionType collectionType, ManagedDomainType owner, Type elementType) { if ( elementType.isEntityType() ) { final String associatedEntityName = collectionType.getAssociatedEntityName(MockSessionFactory.this); - return new MockEntityDomainType<>(associatedEntityName); + 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() ) { final 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 994d3550c3..e0d224c50a 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 @@ -477,7 +477,8 @@ public abstract class ProcessorSessionFactory extends MockSessionFactory { && 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 7a409df6b3..473a5a7a0d 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 @@ -53,7 +53,7 @@ import static org.hibernate.processor.util.StringUtil.determineFullyQualifiedCla import static org.hibernate.processor.util.StringUtil.isFullyQualified; import static org.hibernate.processor.util.StringUtil.packageNameFromFullyQualifiedName; 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; /** @@ -165,8 +165,8 @@ public class XmlMetaEntity implements Metamodel { } @Override - public @Nullable String getSupertypeName() { - return findMappedSuperClass( this, context ); + public @Nullable Element getSuperTypeElement() { + return findMappedSuperElement( this, context ); } public List getMembers() {