1111import  jakarta .persistence .Entity ;
1212import  jakarta .persistence .Id ;
1313import  jakarta .persistence .MappedSuperclass ;
14+ import  jakarta .persistence .Transient ;
1415import  jakarta .persistence .metamodel .Type ;
1516import  net .bytebuddy .asm .Advice ;
1617import  net .bytebuddy .description .annotation .AnnotationDescription ;
7273import  static  net .bytebuddy .matcher .ElementMatchers .isDefaultFinalizer ;
7374import  static  net .bytebuddy .matcher .ElementMatchers .isGetter ;
7475import  static  net .bytebuddy .matcher .ElementMatchers .isSetter ;
76+ import  static  net .bytebuddy .matcher .ElementMatchers .isStatic ;
77+ import  static  net .bytebuddy .matcher .ElementMatchers .named ;
78+ import  static  net .bytebuddy .matcher .ElementMatchers .not ;
7579
7680public  class  EnhancerImpl  implements  Enhancer  {
7781
@@ -489,21 +493,13 @@ private static boolean hasMappingAnnotation(AnnotationList annotations) {
489493				|| annotations .isAnnotationPresent ( Embeddable .class  );
490494	}
491495
492- 	private  static  boolean  hasPersistenceAnnotation (AnnotationList  annotations ) {
493- 		boolean  found  = false ;
494- 		for  ( AnnotationDescription  annotation  : annotations  ) {
495- 			final  String  annotationName  = annotation .getAnnotationType ().getName ();
496- 			if  ( annotationName .startsWith ( "jakarta.persistence"  ) ) {
497- 				if  ( annotationName .equals ( "jakarta.persistence.Transient"  ) ) {
498- 					// transient property so ignore it 
499- 					return  false ;
500- 				}
501- 				else  if  ( !found  && !IGNORED_PERSISTENCE_ANNOTATIONS .contains ( annotationName  ) ) {
502- 					found  = true ;
503- 				}
504- 			}
496+ 	private  static  boolean  isPersistentMethod (MethodDescription  method ) {
497+ 		final  AnnotationList  annotations  = method .getDeclaredAnnotations ();
498+ 		if  ( annotations .isAnnotationPresent ( Transient .class  ) ) {
499+ 			return  false ;
505500		}
506- 		return  found ;
501+ 
502+ 		return  annotations .stream ().noneMatch ( a  -> IGNORED_PERSISTENCE_ANNOTATIONS .contains ( a .getAnnotationType ().getName () ) );
507503	}
508504
509505	private  static  final  Set <String > IGNORED_PERSISTENCE_ANNOTATIONS  = Set .of (
@@ -516,6 +512,17 @@ else if ( !found && !IGNORED_PERSISTENCE_ANNOTATIONS.contains( annotationName )
516512			"jakarta.persistence.PreUpdate" 
517513	);
518514
515+ 	private  static  boolean  containsField (Generic  type , String  fieldName ) {
516+ 		do  {
517+ 			if  ( !type .getDeclaredFields ().filter ( not ( isStatic () ).and ( named ( fieldName  ) ) ).isEmpty () ) {
518+ 				return  true ;
519+ 			}
520+ 			type  = type .getSuperClass ();
521+ 		}
522+ 		while  ( type  != null  && !type .represents ( Object .class  ) );
523+ 		return  false ;
524+ 	}
525+ 
519526	/** 
520527	 * Check whether an entity class ({@code managedCtClass}) has mismatched names between a persistent field and its 
521528	 * getter/setter when using {@link AccessType#PROPERTY}, which Hibernate does not currently support for enhancement. 
@@ -558,61 +565,46 @@ private static boolean checkUnsupportedAttributeNaming(TypeDescription managedCt
558565				.asMethodList ()
559566				.filter ( isGetter ().or ( isSetter () ) );
560567		for  ( final  MethodDescription  methodDescription  : methods  ) {
561- 			if  ( determineAccessType ( methodDescription , defaultAccessType  ) != AccessType .PROPERTY  ) {
568+ 			if  ( methodDescription .getDeclaringType ().represents ( Object .class  )
569+ 				|| determineAccessType ( methodDescription , defaultAccessType  ) != AccessType .PROPERTY  ) {
562570				// We only need to check this for AccessType.PROPERTY 
563571				continue ;
564572			}
565573
566574			final  String  methodName  = methodDescription .getActualName ();
567- 			String  methodFieldName ;
575+ 			String  fieldName ;
568576			if  ( methodName .startsWith ( "get"  ) || methodName .startsWith ( "set"  ) ) {
569- 				methodFieldName  = methodName .substring ( 3  );
577+ 				fieldName  = methodName .substring ( 3  );
570578			}
571579			else  {
572580				assert  methodName .startsWith ( "is"  );
573- 				methodFieldName  = methodName .substring ( 2  );
581+ 				fieldName  = methodName .substring ( 2  );
574582			}
575583			// convert first field letter to lower case 
576- 			methodFieldName  = getJavaBeansFieldName ( methodFieldName  );
577- 			if  ( methodFieldName  != null  && hasPersistenceAnnotation ( methodDescription .getDeclaredAnnotations () ) ) {
578- 				boolean  propertyNameMatchesFieldName  = false ;
579- 				for  ( final  FieldDescription  field  : methodDescription .getDeclaringType ().getDeclaredFields () ) {
580- 					if  ( !Modifier .isStatic ( field .getModifiers () ) ) {
581- 						final  AnnotatedFieldDescription  annotatedField  = new  AnnotatedFieldDescription (
582- 								enhancementContext ,
583- 								field 
584- 						);
585- 						if  ( enhancementContext .isPersistentField ( annotatedField  ) ) {
586- 							if  ( methodFieldName .equals ( field .getActualName () ) ) {
587- 								propertyNameMatchesFieldName  = true ;
588- 								break ;
589- 							}
590- 						}
591- 					}
592- 				}
593- 				if  ( !propertyNameMatchesFieldName  ) {
594- 					// We shouldn't even be in this method if using LEGACY, see top of this method. 
595- 					return  switch  ( strategy  ) {
596- 						case  SKIP  -> {
597- 							log .debugf (
598- 									"Skipping enhancement of [%s] because no field named [%s] could be found for property accessor method [%s]." 
599- 											+ " To fix this, make sure all property accessor methods have a matching field." ,
600- 									managedCtClass .getName (),
601- 									methodFieldName ,
602- 									methodDescription .getName ()
603- 							);
604- 							yield true ;
605- 						}
606- 						case  FAIL  -> throw  new  EnhancementException ( String .format (
607- 								"Enhancement of [%s] failed because no field named [%s] could be found for property accessor method [%s]." 
608- 										+ " To fix this, make sure all property accessor methods have a matching field." ,
584+ 			fieldName  = getJavaBeansFieldName ( fieldName  );
585+ 			if  ( fieldName  != null  && isPersistentMethod ( methodDescription  )
586+ 				&& !containsField ( managedCtClass .asGenericType (), fieldName  ) ) {
587+ 				// We shouldn't even be in this method if using LEGACY, see top of this method. 
588+ 				return  switch  ( strategy  ) {
589+ 					case  SKIP  -> {
590+ 						log .debugf (
591+ 								"Skipping enhancement of [%s] because no field named [%s] could be found for property accessor method [%s]." 
592+ 								+ " To fix this, make sure all property accessor methods have a matching field." ,
609593								managedCtClass .getName (),
610- 								methodFieldName ,
594+ 								fieldName ,
611595								methodDescription .getName ()
612- 						) );
613- 						default  -> throw  new  AssertionFailure ( "Unexpected strategy at this point: "  + strategy  );
614- 					};
615- 				}
596+ 						);
597+ 						yield true ;
598+ 					}
599+ 					case  FAIL  -> throw  new  EnhancementException ( String .format (
600+ 							"Enhancement of [%s] failed because no field named [%s] could be found for property accessor method [%s]." 
601+ 							+ " To fix this, make sure all property accessor methods have a matching field." ,
602+ 							managedCtClass .getName (),
603+ 							fieldName ,
604+ 							methodDescription .getName ()
605+ 					) );
606+ 					default  -> throw  new  AssertionFailure ( "Unexpected strategy at this point: "  + strategy  );
607+ 				};
616608			}
617609		}
618610		return  false ;
0 commit comments