From 8681d702a4e93637793f9d6c558a4fbb58f1fae2 Mon Sep 17 00:00:00 2001 From: Gavin King Date: Wed, 12 Jul 2023 22:30:41 +0200 Subject: [PATCH] HHH-16633 support AccessType.PROPERTY in @Find method parameter matching --- .../annotation/AnnotationMetaEntity.java | 41 ++++++++++-- .../hibernate/jpamodelgen/util/TypeUtils.java | 65 ++++++++++--------- 2 files changed, 70 insertions(+), 36 deletions(-) diff --git a/tooling/metamodel-generator/src/main/java/org/hibernate/jpamodelgen/annotation/AnnotationMetaEntity.java b/tooling/metamodel-generator/src/main/java/org/hibernate/jpamodelgen/annotation/AnnotationMetaEntity.java index 155a230866..2c92a45f7a 100644 --- a/tooling/metamodel-generator/src/main/java/org/hibernate/jpamodelgen/annotation/AnnotationMetaEntity.java +++ b/tooling/metamodel-generator/src/main/java/org/hibernate/jpamodelgen/annotation/AnnotationMetaEntity.java @@ -6,6 +6,7 @@ */ package org.hibernate.jpamodelgen.annotation; +import java.beans.Introspector; import java.util.ArrayList; import java.util.Collection; import java.util.HashMap; @@ -698,14 +699,17 @@ public class AnnotationMetaEntity extends AnnotationMeta { } private @Nullable FieldType validateFinderParameter(TypeElement entity, VariableElement param) { + final String entityClassName = entity.getQualifiedName().toString(); + determineAccessTypeForHierarchy( entity, context ); + final AccessType accessType = castNonNull( context.getAccessTypeInfo( entityClassName ) ).getAccessType(); for ( Element member : entity.getEnclosedElements() ) { - if ( member.getSimpleName().contentEquals( param.getSimpleName() ) ) { - final String memberType = member.asType().toString(); + if ( fieldMatchesParameter( param, member, accessType ) ) { + final String memberType = memberType( member ).toString(); final String paramType = param.asType().toString(); if ( !memberType.equals(paramType) ) { context.message( param, "matching field has type '" + memberType - + "' in entity class '" + entity.getQualifiedName() + "'", + + "' in entity class '" + entityClassName + "'", Diagnostic.Kind.ERROR ); } if ( containsAnnotation( member, Constants.ID, Constants.EMBEDDED_ID ) ) { @@ -730,11 +734,40 @@ public class AnnotationMetaEntity extends AnnotationMeta { } context.message( param, "no matching field named '" + param.getSimpleName() - + "' in entity class '" + entity.getQualifiedName() + "'", + + "' in entity class '" + entityClassName + "'", Diagnostic.Kind.ERROR ); return null; } + private static TypeMirror memberType(Element member) { + if (member.getKind() == ElementKind.METHOD) { + ExecutableElement method = (ExecutableElement) member; + return method.getReturnType(); + } + else { + return member.asType(); + } + } + + private static boolean fieldMatchesParameter(VariableElement param, Element member, AccessType accessType) { + final Name name = member.getSimpleName(); + final Name paramName = param.getSimpleName(); + if ( accessType == AccessType.FIELD ) { + return name.contentEquals(paramName); + } + else { + if ( name.length() > 3 && name.subSequence(0,3).toString().equals("get")) { + final String propertyName = Introspector.decapitalize(name.subSequence(3, name.length()).toString()); + return paramName.contentEquals(propertyName); + } + if ( name.length() > 2 && name.subSequence(0,2).toString().equals("is")) { + final String propertyName = Introspector.decapitalize(name.subSequence(2, name.length()).toString()); + return paramName.contentEquals(propertyName); + } + return false; + } + } + private void addQueryMethod( ExecutableElement method, @Nullable TypeMirror returnType, diff --git a/tooling/metamodel-generator/src/main/java/org/hibernate/jpamodelgen/util/TypeUtils.java b/tooling/metamodel-generator/src/main/java/org/hibernate/jpamodelgen/util/TypeUtils.java index 1a837f944d..429c490dbc 100644 --- a/tooling/metamodel-generator/src/main/java/org/hibernate/jpamodelgen/util/TypeUtils.java +++ b/tooling/metamodel-generator/src/main/java/org/hibernate/jpamodelgen/util/TypeUtils.java @@ -16,6 +16,7 @@ import javax.lang.model.element.Element; import javax.lang.model.element.ElementKind; import javax.lang.model.element.ExecutableElement; import javax.lang.model.element.TypeElement; +import javax.lang.model.element.VariableElement; import javax.lang.model.type.ArrayType; import javax.lang.model.type.DeclaredType; import javax.lang.model.type.ExecutableType; @@ -179,31 +180,31 @@ public final class TypeUtils { * getting-class-values-from-annotations. * * @param annotationMirror The annotation mirror - * @param fqcn the fully qualified class name to check against + * @param qualifiedName the fully qualified class name to check against * * @return {@code true} if the provided annotation type is of the same type as the provided class, {@code false} otherwise. */ - public static boolean isAnnotationMirrorOfType(AnnotationMirror annotationMirror, String fqcn) { + public static boolean isAnnotationMirrorOfType(AnnotationMirror annotationMirror, String qualifiedName) { assert annotationMirror != null; - assert fqcn != null; + assert qualifiedName != null; final Element element = annotationMirror.getAnnotationType().asElement(); - return ((TypeElement) element).getQualifiedName().contentEquals( fqcn ); + return ((TypeElement) element).getQualifiedName().contentEquals( qualifiedName ); } /** * Checks whether the {@code Element} hosts the annotation with the given fully qualified class name. * * @param element the element to check for the hosted annotation - * @param fqcn the fully qualified class name of the annotation to check for + * @param qualifiedName the fully qualified class name of the annotation to check for * * @return the annotation mirror for the specified annotation class from the {@code Element} or {@code null} in case * the {@code TypeElement} does not host the specified annotation. */ - public static @Nullable AnnotationMirror getAnnotationMirror(Element element, String fqcn) { + public static @Nullable AnnotationMirror getAnnotationMirror(Element element, String qualifiedName) { assert element != null; - assert fqcn != null; + assert qualifiedName != null; for ( AnnotationMirror mirror : element.getAnnotationMirrors() ) { - if ( isAnnotationMirrorOfType( mirror, fqcn ) ) { + if ( isAnnotationMirrorOfType( mirror, qualifiedName ) ) { return mirror; } } @@ -235,9 +236,9 @@ public final class TypeUtils { } public static void determineAccessTypeForHierarchy(TypeElement searchedElement, Context context) { - final String fqcn = searchedElement.getQualifiedName().toString(); - context.logMessage( Diagnostic.Kind.OTHER, "Determining access type for " + fqcn ); - final AccessTypeInformation accessTypeInfo = context.getAccessTypeInfo( fqcn ); + final String qualifiedName = searchedElement.getQualifiedName().toString(); + context.logMessage( Diagnostic.Kind.OTHER, "Determining access type for " + qualifiedName ); + final AccessTypeInformation accessTypeInfo = context.getAccessTypeInfo( qualifiedName ); if ( accessTypeInfo != null && accessTypeInfo.isAccessTypeResolved() ) { context.logMessage( Diagnostic.Kind.OTHER, @@ -253,8 +254,8 @@ public final class TypeUtils { "Explicit access type on " + searchedElement + ":" + forcedAccessType ); final AccessTypeInformation newAccessTypeInfo = - new AccessTypeInformation( fqcn, forcedAccessType, null ); - context.addAccessTypeInformation( fqcn, newAccessTypeInfo ); + new AccessTypeInformation( qualifiedName, forcedAccessType, null ); + context.addAccessTypeInformation( qualifiedName, newAccessTypeInfo ); updateEmbeddableAccessType( searchedElement, context, forcedAccessType ); } else { @@ -264,8 +265,8 @@ public final class TypeUtils { final AccessType defaultAccessType = getAccessTypeInCaseElementIsRoot( searchedElement, context ); if ( defaultAccessType != null ) { final AccessTypeInformation newAccessTypeInfo = - new AccessTypeInformation(fqcn, null, defaultAccessType); - context.addAccessTypeInformation( fqcn, newAccessTypeInfo ); + new AccessTypeInformation(qualifiedName, null, defaultAccessType); + context.addAccessTypeInformation( qualifiedName, newAccessTypeInfo ); updateEmbeddableAccessType( searchedElement, context, defaultAccessType ); setDefaultAccessTypeForMappedSuperclassesInHierarchy( searchedElement, defaultAccessType, context ); } @@ -277,8 +278,8 @@ public final class TypeUtils { newDefaultAccessType = AccessType.PROPERTY; } final AccessTypeInformation newAccessTypeInfo = - new AccessTypeInformation( fqcn, null, newDefaultAccessType ); - context.addAccessTypeInformation( fqcn, newAccessTypeInfo ); + new AccessTypeInformation( qualifiedName, null, newDefaultAccessType ); + context.addAccessTypeInformation( qualifiedName, newAccessTypeInfo ); updateEmbeddableAccessType( searchedElement, context, newDefaultAccessType ); } } @@ -335,8 +336,8 @@ public final class TypeUtils { do { superClass = getSuperclassTypeElement( superClass ); if ( superClass != null ) { - final String fqcn = superClass.getQualifiedName().toString(); - final AccessTypeInformation accessTypeInfo = context.getAccessTypeInfo( fqcn ); + final String qualifiedName = superClass.getQualifiedName().toString(); + final AccessTypeInformation accessTypeInfo = context.getAccessTypeInfo( qualifiedName ); if ( accessTypeInfo != null && accessTypeInfo.getDefaultAccessType() != null ) { return accessTypeInfo.getDefaultAccessType(); } @@ -344,8 +345,8 @@ public final class TypeUtils { defaultAccessType = getAccessTypeInCaseElementIsRoot( superClass, context ); if ( defaultAccessType != null ) { final AccessTypeInformation newAccessTypeInfo - = new AccessTypeInformation( fqcn, null, defaultAccessType ); - context.addAccessTypeInformation( fqcn, newAccessTypeInfo ); + = new AccessTypeInformation( qualifiedName, null, defaultAccessType ); + context.addAccessTypeInformation( qualifiedName, newAccessTypeInfo ); // we found an id within the class hierarchy and determined a default access type // there cannot be any super entity classes (otherwise it would be a configuration error) @@ -373,14 +374,14 @@ public final class TypeUtils { do { superClass = getSuperclassTypeElement( superClass ); if ( superClass != null ) { - final String fqcn = superClass.getQualifiedName().toString(); + final String qualifiedName = superClass.getQualifiedName().toString(); if ( containsAnnotation( superClass, Constants.MAPPED_SUPERCLASS ) ) { final AccessType forcedAccessType = determineAnnotationSpecifiedAccessType( superClass ); final AccessTypeInformation accessTypeInfo = forcedAccessType != null - ? new AccessTypeInformation( fqcn, null, forcedAccessType ) - : new AccessTypeInformation( fqcn, null, defaultAccessType ); - context.addAccessTypeInformation( fqcn, accessTypeInfo ); + ? new AccessTypeInformation( qualifiedName, null, forcedAccessType ) + : new AccessTypeInformation( qualifiedName, null, defaultAccessType ); + context.addAccessTypeInformation( qualifiedName, accessTypeInfo ); } } } @@ -425,15 +426,15 @@ public final class TypeUtils { } public static @Nullable AccessType determineAnnotationSpecifiedAccessType(Element element) { - final AnnotationMirror accessAnnotationMirror = TypeUtils.getAnnotationMirror( element, Constants.ACCESS ); - if ( accessAnnotationMirror != null ) { - final Element accessElement = (Element) - castNonNull( getAnnotationValue( accessAnnotationMirror, DEFAULT_ANNOTATION_PARAMETER_NAME ) ); - if ( accessElement.getKind() == ElementKind.ENUM_CONSTANT ) { - if ( accessElement.getSimpleName().contentEquals( AccessType.PROPERTY.toString() ) ) { + final AnnotationMirror mirror = getAnnotationMirror( element, Constants.ACCESS ); + if ( mirror != null ) { + final Object accessType = getAnnotationValue( mirror, DEFAULT_ANNOTATION_PARAMETER_NAME ); + if ( accessType instanceof VariableElement) { + final VariableElement enumValue = (VariableElement) accessType; + if ( enumValue.getSimpleName().contentEquals( AccessType.PROPERTY.toString() ) ) { return AccessType.PROPERTY; } - else if ( accessElement.getSimpleName().contentEquals( AccessType.FIELD.toString() ) ) { + else if ( enumValue.getSimpleName().contentEquals( AccessType.FIELD.toString() ) ) { return AccessType.FIELD; } }