66 */
77package org .hibernate .jpamodelgen .annotation ;
88
9- import java .beans .Introspector ;
109import java .util .ArrayList ;
1110import java .util .Collection ;
1211import java .util .HashMap ;
1312import java .util .List ;
1413import java .util .Map ;
14+ import java .util .StringTokenizer ;
1515import javax .lang .model .element .AnnotationMirror ;
1616import javax .lang .model .element .AnnotationValue ;
1717import javax .lang .model .element .Element ;
4343import org .hibernate .query .sqm .tree .SqmStatement ;
4444import org .hibernate .query .sqm .tree .expression .SqmParameter ;
4545
46+ import static java .beans .Introspector .decapitalize ;
4647import static java .util .Collections .emptyList ;
4748import static java .util .stream .Collectors .toList ;
4849import static javax .lang .model .util .ElementFilter .fieldsIn ;
4950import static javax .lang .model .util .ElementFilter .methodsIn ;
51+ import static org .hibernate .internal .util .StringHelper .qualify ;
5052import static org .hibernate .jpamodelgen .annotation .QueryMethod .isOrderParam ;
5153import static org .hibernate .jpamodelgen .annotation .QueryMethod .isPageParam ;
5254import static org .hibernate .jpamodelgen .util .Constants .SESSION_TYPES ;
@@ -109,6 +111,8 @@ public class AnnotationMetaEntity extends AnnotationMeta {
109111 */
110112 private String sessionType = Constants .ENTITY_MANAGER ;
111113
114+ private final Map <String ,String > memberTypes = new HashMap <>();
115+
112116 public AnnotationMetaEntity (TypeElement element , Context context ) {
113117 this .element = element ;
114118 this .context = context ;
@@ -124,6 +128,10 @@ public static AnnotationMetaEntity create(TypeElement element, Context context,
124128 return annotationMetaEntity ;
125129 }
126130
131+ public @ Nullable String getMemberType (String entityType , String memberName ) {
132+ return memberTypes .get ( qualify (entityType , memberName ) );
133+ }
134+
127135 public AccessTypeInformation getEntityAccessTypeInfo () {
128136 return entityAccessTypeInfo ;
129137 }
@@ -739,19 +747,9 @@ private int countNaturalIdFields(TypeElement entity) {
739747 }
740748
741749 private @ Nullable FieldType validateFinderParameter (TypeElement entity , VariableElement param ) {
742- final String entityClassName = entity .getQualifiedName ().toString ();
743- determineAccessTypeForHierarchy ( entity , context );
744- final AccessType accessType = castNonNull ( context .getAccessTypeInfo ( entityClassName ) ).getAccessType ();
750+ final AccessType accessType = getAccessType (entity );
745751 for ( Element member : entity .getEnclosedElements () ) {
746- if ( fieldMatchesParameter ( param , member , accessType ) ) {
747- final String memberType = memberType ( member ).toString ();
748- final String paramType = param .asType ().toString ();
749- if ( !memberType .equals (paramType ) ) {
750- context .message ( param ,
751- "matching field has type '" + memberType
752- + "' in entity class '" + entityClassName + "'" ,
753- Diagnostic .Kind .ERROR );
754- }
752+ if ( fieldMatchesParameter ( entity , param , member , accessType ) ) {
755753 if ( containsAnnotation ( member , Constants .ID , Constants .EMBEDDED_ID ) ) {
756754 return FieldType .ID ;
757755 }
@@ -773,37 +771,107 @@ else if ( containsAnnotation( member, Constants.NATURAL_ID ) ) {
773771 }
774772 }
775773 context .message ( param ,
776- "no matching field named '" + param .getSimpleName ()
777- + "' in entity class '" + entityClassName + "'" ,
774+ "no matching field named '"
775+ + param .getSimpleName ().toString ().replace ('$' , '.' )
776+ + "' in entity class '" + entity + "'" ,
778777 Diagnostic .Kind .ERROR );
779778 return null ;
780779 }
781780
781+ private AccessType getAccessType (TypeElement entity ) {
782+ final String entityClassName = entity .getQualifiedName ().toString ();
783+ determineAccessTypeForHierarchy (entity , context );
784+ return castNonNull ( context .getAccessTypeInfo ( entityClassName ) ).getAccessType ();
785+ }
786+
782787 private static TypeMirror memberType (Element member ) {
783- if (member .getKind () == ElementKind .METHOD ) {
784- ExecutableElement method = (ExecutableElement ) member ;
788+ if ( member .getKind () == ElementKind .METHOD ) {
789+ final ExecutableElement method = (ExecutableElement ) member ;
785790 return method .getReturnType ();
786791 }
787792 else {
788793 return member .asType ();
789794 }
790795 }
791796
792- private static boolean fieldMatchesParameter (VariableElement param , Element member , AccessType accessType ) {
793- final Name name = member .getSimpleName ();
794- final Name paramName = param .getSimpleName ();
795- if ( accessType == AccessType .FIELD ) {
796- return name .contentEquals (paramName );
797+ private boolean fieldMatchesParameter (TypeElement entityType , VariableElement param , Element member , AccessType accessType ) {
798+ final StringTokenizer tokens = new StringTokenizer ( param .getSimpleName ().toString (), "$" );
799+ return fieldMatchesParameter ( entityType , param , member , accessType , tokens , tokens .nextToken () );
800+ }
801+
802+ private boolean fieldMatchesParameter (
803+ TypeElement entityType ,
804+ VariableElement param ,
805+ Element member ,
806+ AccessType accessType ,
807+ StringTokenizer tokens ,
808+ String token ) {
809+ final Name memberName = member .getSimpleName ();
810+ final TypeMirror type ;
811+ if ( accessType == AccessType .FIELD && member .getKind () == ElementKind .FIELD ) {
812+ if ( !fieldMatches (token , memberName ) ) {
813+ return false ;
814+ }
815+ else {
816+ type = member .asType ();
817+ }
818+ }
819+ else if ( accessType == AccessType .PROPERTY && member .getKind () == ElementKind .METHOD ) {
820+ if ( !getterMatches (token , memberName ) ) {
821+ return false ;
822+ }
823+ else {
824+ final ExecutableElement method = (ExecutableElement ) member ;
825+ type = method .getReturnType ();
826+ }
797827 }
798828 else {
799- if ( name .length () > 3 && name .subSequence (0 ,3 ).toString ().equals ("get" )) {
800- final String propertyName = Introspector .decapitalize (name .subSequence (3 , name .length ()).toString ());
801- return paramName .contentEquals (propertyName );
829+ return false ;
830+ }
831+
832+ if ( tokens .hasMoreTokens () ) {
833+ final String nextToken = tokens .nextToken ();
834+ if ( type .getKind () == TypeKind .DECLARED ) {
835+ final DeclaredType declaredType = (DeclaredType ) type ;
836+ final TypeElement memberType = (TypeElement ) declaredType .asElement ();
837+ memberTypes .put ( qualify (entityType .getQualifiedName ().toString (), memberName .toString ()),
838+ memberType .getQualifiedName ().toString () );
839+ final AccessType memberAccessType = getAccessType (memberType );
840+ for ( Element entityMember : memberType .getEnclosedElements () ) {
841+ if ( fieldMatchesParameter (memberType , param , entityMember , memberAccessType , tokens , nextToken ) ) {
842+ return true ;
843+ }
844+ }
802845 }
803- if ( name .length () > 2 && name .subSequence (0 ,2 ).toString ().equals ("is" )) {
804- final String propertyName = Introspector .decapitalize (name .subSequence (2 , name .length ()).toString ());
805- return paramName .contentEquals (propertyName );
846+ return false ;
847+ }
848+ else {
849+ final String memberType = memberType ( member ).toString ();
850+ final String paramType = param .asType ().toString ();
851+ if ( !isLegalAssignment ( paramType , memberType ) ) {
852+ context .message ( param ,
853+ "matching field has type '" + memberType
854+ + "' in entity class '" + entityType + "'" ,
855+ Diagnostic .Kind .ERROR );
806856 }
857+ return true ;
858+ }
859+ }
860+
861+ private static boolean fieldMatches (String token , Name fieldName ) {
862+ return fieldName .contentEquals (token );
863+ }
864+
865+ private static boolean getterMatches (String token , Name methodName ) {
866+ if ( methodName .length () > 3 && methodName .subSequence (0 ,3 ).toString ().equals ("get" )) {
867+ final String propertyName = decapitalize (methodName .subSequence (3 , methodName .length ()).toString ());
868+ return token .equals (propertyName );
869+ }
870+ else if ( methodName .length () > 2 && methodName .subSequence (0 ,2 ).toString ().equals ("is" )) {
871+ final String propertyName = decapitalize (methodName .subSequence (2 , methodName .length ()).toString ());
872+ return token .equals (propertyName );
873+ }
874+ else {
807875 return false ;
808876 }
809877 }
0 commit comments