6060import java .util .Set ;
6161import java .util .StringTokenizer ;
6262import java .util .regex .Pattern ;
63+ import java .util .stream .Stream ;
6364
6465import jakarta .persistence .AccessType ;
6566
@@ -463,31 +464,148 @@ && containsAnnotation( method, HQL, SQL, FIND ) ) {
463464 initialized = true ;
464465 }
465466
466- private void addIdClassIfNeeded (List <? extends Element > fields , List <? extends Element > methods ) {
467+ /**
468+ * Creates a generated id class named {@code Entity_.Id} if the
469+ * entity has multiple {@code @Id} fields, but no {@code @IdClass}
470+ * annotation.
471+ */
472+ private void addIdClassIfNeeded (List <VariableElement > fields , List <ExecutableElement > methods ) {
467473 if ( hasAnnotation ( element , ID_CLASS ) ) {
468- return ;
474+ checkIdMembers ( fields , methods );
475+ }
476+ else {
477+ final List <MetaAttribute > components = getIdMemberNames ( fields , methods );
478+ if ( components .size () >= 2 ) {
479+ putMember ( ID_CLASS_MEMBER_NAME , new IdClassMetaAttribute ( this , components ) );
480+ }
469481 }
482+ }
483+
484+ private List <MetaAttribute > getIdMemberNames (List <VariableElement > fields , List <ExecutableElement > methods ) {
470485 final List <MetaAttribute > components = new ArrayList <>();
471- for ( Element field : fields ) {
472- if ( hasAnnotation ( field , ID ) && isPersistent ( field , AccessType . FIELD ) ) {
486+ for ( var field : fields ) {
487+ if ( isIdField ( field ) ) {
473488 final String propertyName = propertyName ( field );
474489 if ( members .containsKey ( propertyName ) ) {
475490 components .add ( members .get ( propertyName ) );
476491 }
477492 }
478493 }
479- for ( Element method : methods ) {
480- if ( hasAnnotation ( method , ID ) && isPersistent ( method , AccessType . PROPERTY ) ) {
494+ for ( var method : methods ) {
495+ if ( isIdProperty ( method ) ) {
481496 final String propertyName = propertyName ( method );
482497 if ( members .containsKey ( propertyName ) ) {
483498 components .add ( members .get ( propertyName ) );
484499 }
485500 }
486501 }
487- if ( components .size () < 2 ) {
488- return ;
502+ return components ;
503+ }
504+
505+ private void checkIdMembers (List <VariableElement > fields , List <ExecutableElement > methods ) {
506+ final AnnotationMirror annotationMirror = getAnnotationMirror ( element , ID_CLASS );
507+ if ( annotationMirror != null ) {
508+ final AnnotationValue annotationValue = getAnnotationValue ( annotationMirror );
509+ if ( annotationValue != null && annotationValue .getValue () instanceof DeclaredType declaredType ) {
510+ final TypeElement idClass = (TypeElement ) declaredType .asElement ();
511+ if ( fields .stream ().filter ( this ::isIdField ).count ()
512+ + methods .stream ().filter ( this ::isIdProperty ).count ()
513+ == 1 ) {
514+ for ( var field : fields ) {
515+ if ( isIdField ( field ) ) {
516+ if ( hasAnnotation ( field , ONE_TO_ONE ) ) {
517+ // Special case for @Id @OneToOne to an associated entity with an @IdClass
518+ //TODO: check the id type of the associated entity is the same as idClass
519+ return ;
520+ }
521+ }
522+ }
523+ for ( var method : methods ) {
524+ if ( isIdProperty ( method ) ) {
525+ if ( hasAnnotation ( method , ONE_TO_ONE ) ) {
526+ // Special case for @Id @OneToOne to an associated entity with an @IdClass
527+ //TODO: check the id type of the associated entity is the same as idClass
528+ return ;
529+ }
530+ }
531+ }
532+ }
533+ else {
534+ for ( var field : fields ) {
535+ if ( isIdField ( field ) ) {
536+ memberStream ( idClass )
537+ .filter ( element -> element .getKind () == ElementKind .FIELD
538+ && element .getSimpleName ().contentEquals ( field .getSimpleName () ) )
539+ .findAny ()
540+ .ifPresentOrElse ( match -> {
541+ if ( !isMatchingIdType ( field , field .asType (), match .asType () ) ) {
542+ context .message ( match ,
543+ "id field should be of type '" + field .asType () + "'" ,
544+ Diagnostic .Kind .ERROR );
545+ }
546+ },
547+ () -> context .message ( field ,
548+ "no matching field in id class '" + idClass .getSimpleName () + "'" ,
549+ Diagnostic .Kind .ERROR ) );
550+ }
551+ }
552+ for ( var method : methods ) {
553+ if ( isIdProperty ( method ) ) {
554+ memberStream ( idClass )
555+ .filter ( element -> element .getKind () == ElementKind .METHOD
556+ && element .getSimpleName ().contentEquals ( method .getSimpleName () )
557+ && isMatchingIdType ( method , method .getReturnType (),
558+ ((ExecutableElement ) element ).getReturnType () ) )
559+ .findAny ()
560+ .ifPresentOrElse ( match -> {
561+ },
562+ () -> context .message ( method ,
563+ "no matching property in id class '" + idClass .getSimpleName () + "'" ,
564+ Diagnostic .Kind .ERROR ) );
565+ }
566+ }
567+ }
568+ }
569+ }
570+ }
571+
572+ private boolean isIdProperty (ExecutableElement method ) {
573+ return hasAnnotation ( method , ID )
574+ && isPersistent ( method , AccessType .PROPERTY );
575+ }
576+
577+ private boolean isIdField (VariableElement field ) {
578+ return hasAnnotation ( field , ID )
579+ && isPersistent ( field , AccessType .FIELD );
580+ }
581+
582+ private static Stream <? extends Element > memberStream (TypeElement idClass ) {
583+ Stream <? extends Element > result = idClass .getEnclosedElements ().stream ();
584+ TypeMirror superclass = idClass .getSuperclass ();
585+ while ( superclass .getKind () == TypeKind .DECLARED ) {
586+ final DeclaredType declaredType = (DeclaredType ) superclass ;
587+ final TypeElement typeElement = (TypeElement ) declaredType .asElement ();
588+ result = Stream .concat ( result , typeElement .getEnclosedElements ().stream () );
589+ superclass = typeElement .getSuperclass ();
489590 }
490- putMember ( ID_CLASS_MEMBER_NAME , new IdClassMetaAttribute ( this , components ) );
591+ return result ;
592+ }
593+
594+ private boolean isMatchingIdType (Element id , TypeMirror type , TypeMirror match ) {
595+ return isSameType ( type , match )
596+ || isEquivalentPrimitiveType ( type , match )
597+ || isEquivalentPrimitiveType ( match , type )
598+ //TODO: check the id type of the associated entity
599+ || hasAnnotation ( id , MANY_TO_ONE , ONE_TO_ONE );
600+ }
601+
602+ private boolean isSameType (TypeMirror type , TypeMirror match ) {
603+ return context .getTypeUtils ().isSameType ( type , match );
604+ }
605+
606+ private boolean isEquivalentPrimitiveType (TypeMirror type , TypeMirror match ) {
607+ return type .getKind ().isPrimitive ()
608+ && isSameType ( context .getTypeUtils ().boxedClass ( ((PrimitiveType ) type ) ).asType (), match );
491609 }
492610
493611 private boolean checkEntities (List <ExecutableElement > lifecycleMethods ) {
@@ -1394,7 +1512,7 @@ else if ( !hasAnnotation( declaredType.asElement(), ENTITY )
13941512 Diagnostic .Kind .ERROR );
13951513 }
13961514 else if ( returnArgument
1397- && !context . getTypeUtils (). isSameType ( returnType , declaredParameterType ) ) {
1515+ && !isSameType ( returnType , declaredParameterType ) ) {
13981516 message ( parameter ,
13991517 "return type '" + returnType
14001518 + "' disagrees with parameter type '" + parameterType + "'" ,
@@ -1767,7 +1885,7 @@ private List<OrderBy> orderByList(ExecutableElement method, TypeElement returnTy
17671885 final List <OrderBy > result = new ArrayList <>();
17681886 @ SuppressWarnings ("unchecked" )
17691887 final List <AnnotationValue > list = (List <AnnotationValue >)
1770- castNonNull ( getAnnotationValue ( orderByList , "value" ) ).getValue ();
1888+ castNonNull ( getAnnotationValue ( orderByList ) ).getValue ();
17711889 for ( AnnotationValue element : list ) {
17721890 result .add ( orderByExpression ( castNonNull ( (AnnotationMirror ) element .getValue () ), entityType , method ) );
17731891 }
@@ -1782,7 +1900,7 @@ private List<OrderBy> orderByList(ExecutableElement method, TypeElement returnTy
17821900 }
17831901
17841902 private OrderBy orderByExpression (AnnotationMirror orderBy , TypeElement entityType , ExecutableElement method ) {
1785- final String fieldName = castNonNull ( getAnnotationValue (orderBy , "value" ) ).getValue ().toString ();
1903+ final String fieldName = castNonNull ( getAnnotationValue (orderBy ) ).getValue ().toString ();
17861904 if ( fieldName .equals ("<error>" ) ) {
17871905 throw new ProcessLaterException ();
17881906 }
@@ -2138,9 +2256,9 @@ else if ( containsAnnotation( member, NATURAL_ID ) ) {
21382256 else {
21392257 final AnnotationMirror idClass = getAnnotationMirror ( entityType , ID_CLASS );
21402258 if ( idClass != null ) {
2141- final AnnotationValue value = getAnnotationValue ( idClass , "value" );
2259+ final AnnotationValue value = getAnnotationValue ( idClass );
21422260 if ( value != null ) {
2143- if ( context . getTypeUtils (). isSameType ( param .asType (), (TypeMirror ) value .getValue () ) ) {
2261+ if ( isSameType ( param .asType (), (TypeMirror ) value .getValue () ) ) {
21442262 return FieldType .ID ;
21452263 }
21462264 }
@@ -2360,7 +2478,7 @@ private void addQueryMethod(
23602478 final ArrayType arrayType = (ArrayType ) returnType ;
23612479 final TypeMirror componentType = arrayType .getComponentType ();
23622480 final TypeElement object = context .getElementUtils ().getTypeElement (JAVA_OBJECT );
2363- if ( !context . getTypeUtils (). isSameType ( object .asType (), componentType ) ) {
2481+ if ( !isSameType ( object .asType (), componentType ) ) {
23642482 returnType = componentType ;
23652483 containerTypeName = "[]" ;
23662484 }
@@ -2377,7 +2495,7 @@ private void addQueryMethod(
23772495 containerTypeName = containerType .getQualifiedName ().toString ();
23782496 }
23792497
2380- final AnnotationValue value = getAnnotationValue ( mirror , "value" );
2498+ final AnnotationValue value = getAnnotationValue ( mirror );
23812499 if ( value != null && value .getValue () instanceof String queryString ) {
23822500 addQueryMethod ( method , returnType , containerTypeName , mirror , isNative , value , queryString );
23832501 }
@@ -2679,7 +2797,7 @@ else if ( selection instanceof JpaRoot<?> from ) {
26792797 else {
26802798 final TypeElement typeElement = context .getTypeElementForFullyQualifiedName ( javaResultType .getName () );
26812799 final Types types = context .getTypeUtils ();
2682- returnTypeCorrect = context . getTypeUtils () .isAssignable ( returnType , types .erasure ( typeElement .asType () ) );
2800+ returnTypeCorrect = types .isAssignable ( returnType , types .erasure ( typeElement .asType () ) );
26832801 }
26842802 }
26852803 catch (Exception e ) {
@@ -3026,7 +3144,7 @@ private static String parameterName(VariableElement parameter) {
30263144 final AnnotationMirror param = getAnnotationMirror ( parameter , "jakarta.data.repository.Param" );
30273145 if ( by != null ) {
30283146 final String name =
3029- castNonNull (getAnnotationValue (by , "value" ))
3147+ castNonNull (getAnnotationValue (by ))
30303148 .getValue ().toString ();
30313149 if ( name .contains ("<error>" ) ) {
30323150 throw new ProcessLaterException ();
@@ -3037,7 +3155,7 @@ private static String parameterName(VariableElement parameter) {
30373155 }
30383156 else if ( param != null ) {
30393157 final String name =
3040- castNonNull (getAnnotationValue (param , "value" ))
3158+ castNonNull (getAnnotationValue (param ))
30413159 .getValue ().toString ();
30423160 if ( name .contains ("<error>" ) ) {
30433161 throw new ProcessLaterException ();
0 commit comments