60
60
import java .util .Set ;
61
61
import java .util .StringTokenizer ;
62
62
import java .util .regex .Pattern ;
63
+ import java .util .stream .Stream ;
63
64
64
65
import jakarta .persistence .AccessType ;
65
66
@@ -463,31 +464,148 @@ && containsAnnotation( method, HQL, SQL, FIND ) ) {
463
464
initialized = true ;
464
465
}
465
466
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 ) {
467
473
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
+ }
469
481
}
482
+ }
483
+
484
+ private List <MetaAttribute > getIdMemberNames (List <VariableElement > fields , List <ExecutableElement > methods ) {
470
485
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 ) ) {
473
488
final String propertyName = propertyName ( field );
474
489
if ( members .containsKey ( propertyName ) ) {
475
490
components .add ( members .get ( propertyName ) );
476
491
}
477
492
}
478
493
}
479
- for ( Element method : methods ) {
480
- if ( hasAnnotation ( method , ID ) && isPersistent ( method , AccessType . PROPERTY ) ) {
494
+ for ( var method : methods ) {
495
+ if ( isIdProperty ( method ) ) {
481
496
final String propertyName = propertyName ( method );
482
497
if ( members .containsKey ( propertyName ) ) {
483
498
components .add ( members .get ( propertyName ) );
484
499
}
485
500
}
486
501
}
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 ();
489
590
}
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 );
491
609
}
492
610
493
611
private boolean checkEntities (List <ExecutableElement > lifecycleMethods ) {
@@ -1394,7 +1512,7 @@ else if ( !hasAnnotation( declaredType.asElement(), ENTITY )
1394
1512
Diagnostic .Kind .ERROR );
1395
1513
}
1396
1514
else if ( returnArgument
1397
- && !context . getTypeUtils (). isSameType ( returnType , declaredParameterType ) ) {
1515
+ && !isSameType ( returnType , declaredParameterType ) ) {
1398
1516
message ( parameter ,
1399
1517
"return type '" + returnType
1400
1518
+ "' disagrees with parameter type '" + parameterType + "'" ,
@@ -1767,7 +1885,7 @@ private List<OrderBy> orderByList(ExecutableElement method, TypeElement returnTy
1767
1885
final List <OrderBy > result = new ArrayList <>();
1768
1886
@ SuppressWarnings ("unchecked" )
1769
1887
final List <AnnotationValue > list = (List <AnnotationValue >)
1770
- castNonNull ( getAnnotationValue ( orderByList , "value" ) ).getValue ();
1888
+ castNonNull ( getAnnotationValue ( orderByList ) ).getValue ();
1771
1889
for ( AnnotationValue element : list ) {
1772
1890
result .add ( orderByExpression ( castNonNull ( (AnnotationMirror ) element .getValue () ), entityType , method ) );
1773
1891
}
@@ -1782,7 +1900,7 @@ private List<OrderBy> orderByList(ExecutableElement method, TypeElement returnTy
1782
1900
}
1783
1901
1784
1902
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 ();
1786
1904
if ( fieldName .equals ("<error>" ) ) {
1787
1905
throw new ProcessLaterException ();
1788
1906
}
@@ -2138,9 +2256,9 @@ else if ( containsAnnotation( member, NATURAL_ID ) ) {
2138
2256
else {
2139
2257
final AnnotationMirror idClass = getAnnotationMirror ( entityType , ID_CLASS );
2140
2258
if ( idClass != null ) {
2141
- final AnnotationValue value = getAnnotationValue ( idClass , "value" );
2259
+ final AnnotationValue value = getAnnotationValue ( idClass );
2142
2260
if ( value != null ) {
2143
- if ( context . getTypeUtils (). isSameType ( param .asType (), (TypeMirror ) value .getValue () ) ) {
2261
+ if ( isSameType ( param .asType (), (TypeMirror ) value .getValue () ) ) {
2144
2262
return FieldType .ID ;
2145
2263
}
2146
2264
}
@@ -2360,7 +2478,7 @@ private void addQueryMethod(
2360
2478
final ArrayType arrayType = (ArrayType ) returnType ;
2361
2479
final TypeMirror componentType = arrayType .getComponentType ();
2362
2480
final TypeElement object = context .getElementUtils ().getTypeElement (JAVA_OBJECT );
2363
- if ( !context . getTypeUtils (). isSameType ( object .asType (), componentType ) ) {
2481
+ if ( !isSameType ( object .asType (), componentType ) ) {
2364
2482
returnType = componentType ;
2365
2483
containerTypeName = "[]" ;
2366
2484
}
@@ -2377,7 +2495,7 @@ private void addQueryMethod(
2377
2495
containerTypeName = containerType .getQualifiedName ().toString ();
2378
2496
}
2379
2497
2380
- final AnnotationValue value = getAnnotationValue ( mirror , "value" );
2498
+ final AnnotationValue value = getAnnotationValue ( mirror );
2381
2499
if ( value != null && value .getValue () instanceof String queryString ) {
2382
2500
addQueryMethod ( method , returnType , containerTypeName , mirror , isNative , value , queryString );
2383
2501
}
@@ -2679,7 +2797,7 @@ else if ( selection instanceof JpaRoot<?> from ) {
2679
2797
else {
2680
2798
final TypeElement typeElement = context .getTypeElementForFullyQualifiedName ( javaResultType .getName () );
2681
2799
final Types types = context .getTypeUtils ();
2682
- returnTypeCorrect = context . getTypeUtils () .isAssignable ( returnType , types .erasure ( typeElement .asType () ) );
2800
+ returnTypeCorrect = types .isAssignable ( returnType , types .erasure ( typeElement .asType () ) );
2683
2801
}
2684
2802
}
2685
2803
catch (Exception e ) {
@@ -3026,7 +3144,7 @@ private static String parameterName(VariableElement parameter) {
3026
3144
final AnnotationMirror param = getAnnotationMirror ( parameter , "jakarta.data.repository.Param" );
3027
3145
if ( by != null ) {
3028
3146
final String name =
3029
- castNonNull (getAnnotationValue (by , "value" ))
3147
+ castNonNull (getAnnotationValue (by ))
3030
3148
.getValue ().toString ();
3031
3149
if ( name .contains ("<error>" ) ) {
3032
3150
throw new ProcessLaterException ();
@@ -3037,7 +3155,7 @@ private static String parameterName(VariableElement parameter) {
3037
3155
}
3038
3156
else if ( param != null ) {
3039
3157
final String name =
3040
- castNonNull (getAnnotationValue (param , "value" ))
3158
+ castNonNull (getAnnotationValue (param ))
3041
3159
.getValue ().toString ();
3042
3160
if ( name .contains ("<error>" ) ) {
3043
3161
throw new ProcessLaterException ();
0 commit comments