HHH-17875 validate association mappings in processor

This commit is contained in:
Gavin King 2024-03-22 10:32:57 +01:00
parent c98cd5e675
commit c9c0261bfa
3 changed files with 165 additions and 5 deletions

View File

@ -375,6 +375,9 @@ public class AnnotationMetaEntity extends AnnotationMeta {
putMember( "class", new AnnotationMetaType(this) ); putMember( "class", new AnnotationMetaType(this) );
} }
validatePersistentMembers( fieldsOfClass );
validatePersistentMembers( gettersAndSettersOfClass );
addPersistentMembers( fieldsOfClass, AccessType.FIELD ); addPersistentMembers( fieldsOfClass, AccessType.FIELD );
addPersistentMembers( gettersAndSettersOfClass, AccessType.PROPERTY ); addPersistentMembers( gettersAndSettersOfClass, AccessType.PROPERTY );
} }
@ -391,6 +394,7 @@ public class AnnotationMetaEntity extends AnnotationMeta {
} }
private void addMethods(TypeElement element, List<ExecutableElement> methodsOfClass) { private void addMethods(TypeElement element, List<ExecutableElement> methodsOfClass) {
//TODO just use Elements.getAllMembers(element) here!
for ( TypeMirror typeMirror : element.getInterfaces() ) { for ( TypeMirror typeMirror : element.getInterfaces() ) {
final DeclaredType declaredType = (DeclaredType) typeMirror; final DeclaredType declaredType = (DeclaredType) typeMirror;
final TypeElement typeElement = (TypeElement) declaredType.asElement(); final TypeElement typeElement = (TypeElement) declaredType.asElement();
@ -669,6 +673,14 @@ public class AnnotationMetaEntity extends AnnotationMeta {
&& returnType.getKind() != TypeKind.VOID; && returnType.getKind() != TypeKind.VOID;
} }
private void validatePersistentMembers(List<? extends Element> membersOfClass) {
for ( Element memberOfClass : membersOfClass ) {
if ( hasAnnotation(memberOfClass, MANY_TO_ONE, ONE_TO_ONE, ONE_TO_MANY, MANY_TO_MANY) ) {
validateAssociation(memberOfClass);
}
}
}
private void addPersistentMembers(List<? extends Element> membersOfClass, AccessType membersKind) { private void addPersistentMembers(List<? extends Element> membersOfClass, AccessType membersKind) {
for ( Element memberOfClass : membersOfClass ) { for ( Element memberOfClass : membersOfClass ) {
if ( isPersistent( memberOfClass, membersKind ) ) { if ( isPersistent( memberOfClass, membersKind ) ) {
@ -692,6 +704,133 @@ public class AnnotationMetaEntity extends AnnotationMeta {
} }
} }
private void validateAssociation(Element memberOfClass) {
final TypeMirror type = attributeType(memberOfClass);
if ( hasAnnotation(memberOfClass, MANY_TO_ONE) ) {
final AnnotationMirror annotation =
castNonNull(getAnnotationMirror(memberOfClass, MANY_TO_ONE));
validateToOneAssociation(memberOfClass, annotation, type);
}
else if ( hasAnnotation(memberOfClass, ONE_TO_ONE) ) {
final AnnotationMirror annotation =
castNonNull(getAnnotationMirror(memberOfClass, ONE_TO_ONE));
validateToOneAssociation(memberOfClass, annotation, type);
}
else if ( hasAnnotation(memberOfClass, ONE_TO_MANY) ) {
final AnnotationMirror annotation =
castNonNull(getAnnotationMirror(memberOfClass, ONE_TO_MANY));
validateToManyAssociation(memberOfClass, annotation, type);
}
else if ( hasAnnotation(memberOfClass, MANY_TO_MANY) ) {
final AnnotationMirror annotation =
castNonNull(getAnnotationMirror(memberOfClass, MANY_TO_MANY));
validateToManyAssociation(memberOfClass, annotation, type);
}
}
private static TypeMirror attributeType(Element memberOfClass) {
switch ( memberOfClass.getKind() ) {
case METHOD:
final ExecutableElement method = (ExecutableElement) memberOfClass;
return method.getReturnType();
case FIELD:
return memberOfClass.asType();
default:
throw new AssertionFailure("should be a field or getter");
}
}
private void validateToOneAssociation(Element memberOfClass, AnnotationMirror annotation, TypeMirror type) {
final TypeMirror target = (TypeMirror) getAnnotationValue(annotation, "targetEntity");
validateAssociation(memberOfClass, annotation, target == null ? type : target);
}
private void validateToManyAssociation(Element memberOfClass, AnnotationMirror annotation, TypeMirror type) {
final TypeMirror target = (TypeMirror) getAnnotationValue(annotation, "targetEntity");
validateAssociation(memberOfClass, annotation, target == null ? elementType(type) : target);
}
private void validateAssociation(Element memberOfClass, AnnotationMirror annotation, @Nullable TypeMirror typeMirror) {
if ( typeMirror != null ) {
switch ( typeMirror.getKind() ) {
case TYPEVAR:
if ( hasAnnotation(element, ENTITY) ) {
context.message(memberOfClass, "type '" + typeMirror + "' is a type variable",
Diagnostic.Kind.WARNING);
}
break;
case DECLARED:
final DeclaredType assocDeclaredType = (DeclaredType) typeMirror;
final TypeElement assocTypeElement = (TypeElement) assocDeclaredType.asElement();
if ( !hasAnnotation(assocTypeElement, ENTITY) ) {
context.message(memberOfClass, "type '" + assocTypeElement.getSimpleName()
+ "' is not annotated '@Entity'",
Diagnostic.Kind.WARNING);
}
final String mappedBy = (String) getAnnotationValue(annotation, "mappedBy");
if ( mappedBy != null && !mappedBy.isEmpty() ) {
if ( mappedBy.equals("<error>") ) {
return;
// throw new ProcessLaterException();
}
if ( mappedBy.indexOf('.')>0 ) {
//we don't know how to handle paths yet
return;
}
final List<? extends Element> members =
context.getElementUtils().getAllMembers(assocTypeElement);
final AnnotationValue annotationVal =
castNonNull(getAnnotationValueRef(annotation, "mappedBy"));
if ( members.stream().noneMatch(m -> propertyName(this, m).contentEquals(mappedBy)) ) {
context.message(memberOfClass, annotation,
annotationVal,
"no matching member in '" + assocTypeElement.getSimpleName() + "'",
Diagnostic.Kind.ERROR);
}
else {
final Element member =
members.stream()
.filter(m -> propertyName(this, m).contentEquals(mappedBy))
.findFirst().get();
if ( hasAnnotation(member, MANY_TO_ONE) ) {
final TypeMirror backType = attributeType(member);
if ( !context.getTypeUtils().isSameType(backType, element.asType()) ) {
context.message(memberOfClass, annotation, annotationVal,
"member '" + member.getSimpleName()
+ "' of '" + assocTypeElement.getSimpleName()
+ "' is not of type '" + element.getSimpleName() + "'",
Diagnostic.Kind.WARNING);
}
}
else if ( hasAnnotation(member, MANY_TO_MANY) ) {
final TypeMirror backType = elementType( attributeType(member) );
if ( backType != null ) {
if ( !context.getTypeUtils().isSameType(backType, element.asType()) ) {
context.message(memberOfClass, annotation, annotationVal,
"member '" + member.getSimpleName()
+ "' of '" + assocTypeElement.getSimpleName()
+ "' is not of type '" + element.getSimpleName() + "'",
Diagnostic.Kind.WARNING);
}
}
}
else {
context.message(memberOfClass, annotation, annotationVal,
"member '" + member.getSimpleName()
+ "' of '" + assocTypeElement.getSimpleName()
+ "' is not annotated '@ManyToMany' or '@ManyToOne'",
Diagnostic.Kind.WARNING);
}
}
}
break;
default:
context.message(memberOfClass, "type '" + typeMirror + "' is not an entity type",
Diagnostic.Kind.WARNING);
}
}
}
private boolean isPersistent(Element memberOfClass, AccessType membersKind) { private boolean isPersistent(Element memberOfClass, AccessType membersKind) {
return ( entityAccessTypeInfo.getAccessType() == membersKind return ( entityAccessTypeInfo.getAccessType() == membersKind
|| determineAnnotationSpecifiedAccessType( memberOfClass ) != null ) || determineAnnotationSpecifiedAccessType( memberOfClass ) != null )
@ -948,6 +1087,27 @@ public class AnnotationMetaEntity extends AnnotationMeta {
} }
} }
private @Nullable TypeMirror elementType(TypeMirror parameterType) {
switch ( parameterType.getKind() ) {
case DECLARED:
final DeclaredType declaredType = (DeclaredType) parameterType;
List<? extends TypeMirror> typeArguments = declaredType.getTypeArguments();
switch ( typeArguments.size() ) {
case 1:
return typeArguments.get(0);
case 2:
return typeArguments.get(1);
default:
return null;
}
case ARRAY:
final ArrayType arrayType = (ArrayType) parameterType;
return arrayType.getComponentType();
default:
return null;
}
}
private static String lifecycleOperation(ExecutableElement method) { private static String lifecycleOperation(ExecutableElement method) {
if ( hasAnnotation(method, JD_INSERT) ) { if ( hasAnnotation(method, JD_INSERT) ) {
return "insert"; return "insert";

View File

@ -9,7 +9,6 @@ package org.hibernate.processor.annotation;
import org.checkerframework.checker.nullness.qual.Nullable; import org.checkerframework.checker.nullness.qual.Nullable;
import org.hibernate.processor.Context; import org.hibernate.processor.Context;
import org.hibernate.processor.util.Constants; import org.hibernate.processor.util.Constants;
import org.hibernate.processor.util.TypeUtils;
import javax.lang.model.element.Element; import javax.lang.model.element.Element;
import javax.lang.model.element.TypeElement; import javax.lang.model.element.TypeElement;
@ -23,6 +22,7 @@ import javax.lang.model.util.Types;
import static org.hibernate.processor.util.TypeUtils.getTargetEntity; import static org.hibernate.processor.util.TypeUtils.getTargetEntity;
import static org.hibernate.processor.util.TypeUtils.isBasicAttribute; import static org.hibernate.processor.util.TypeUtils.isBasicAttribute;
import static org.hibernate.processor.util.TypeUtils.isPropertyGetter;
import static org.hibernate.processor.util.TypeUtils.toArrayTypeString; import static org.hibernate.processor.util.TypeUtils.toArrayTypeString;
import static org.hibernate.processor.util.TypeUtils.toTypeString; import static org.hibernate.processor.util.TypeUtils.toTypeString;
@ -81,7 +81,7 @@ public class DataMetaAttributeGenerationVisitor extends SimpleTypeVisitor8<@Null
@Override @Override
public @Nullable DataAnnotationMetaAttribute visitExecutable(ExecutableType executable, Element element) { public @Nullable DataAnnotationMetaAttribute visitExecutable(ExecutableType executable, Element element) {
return TypeUtils.isPropertyGetter( executable, element ) return isPropertyGetter( executable, element )
? executable.getReturnType().accept(this, element) ? executable.getReturnType().accept(this, element)
: null; : null;
} }

View File

@ -11,7 +11,6 @@ import org.hibernate.processor.Context;
import org.hibernate.processor.util.AccessType; import org.hibernate.processor.util.AccessType;
import org.hibernate.processor.util.AccessTypeInformation; import org.hibernate.processor.util.AccessTypeInformation;
import org.hibernate.processor.util.Constants; import org.hibernate.processor.util.Constants;
import org.hibernate.processor.util.TypeUtils;
import javax.lang.model.element.AnnotationMirror; import javax.lang.model.element.AnnotationMirror;
import javax.lang.model.element.Element; import javax.lang.model.element.Element;
@ -40,6 +39,7 @@ import static org.hibernate.processor.util.TypeUtils.getAnnotationMirror;
import static org.hibernate.processor.util.TypeUtils.getAnnotationValue; import static org.hibernate.processor.util.TypeUtils.getAnnotationValue;
import static org.hibernate.processor.util.TypeUtils.getCollectionElementType; import static org.hibernate.processor.util.TypeUtils.getCollectionElementType;
import static org.hibernate.processor.util.TypeUtils.getKeyType; import static org.hibernate.processor.util.TypeUtils.getKeyType;
import static org.hibernate.processor.util.TypeUtils.getTargetEntity;
import static org.hibernate.processor.util.TypeUtils.hasAnnotation; import static org.hibernate.processor.util.TypeUtils.hasAnnotation;
import static org.hibernate.processor.util.TypeUtils.isBasicAttribute; import static org.hibernate.processor.util.TypeUtils.isBasicAttribute;
import static org.hibernate.processor.util.TypeUtils.isPropertyGetter; import static org.hibernate.processor.util.TypeUtils.isPropertyGetter;
@ -86,7 +86,7 @@ public class MetaAttributeGenerationVisitor extends SimpleTypeVisitor8<@Nullable
// WARNING: .toString() is necessary here since Name equals does not compare to String // WARNING: .toString() is necessary here since Name equals does not compare to String
final String returnTypeName = returnedElement.getQualifiedName().toString(); final String returnTypeName = returnedElement.getQualifiedName().toString();
final String collection = Constants.COLLECTIONS.get( returnTypeName ); final String collection = Constants.COLLECTIONS.get( returnTypeName );
final String targetEntity = TypeUtils.getTargetEntity( element.getAnnotationMirrors() ); final String targetEntity = getTargetEntity( element.getAnnotationMirrors() );
if ( collection != null ) { if ( collection != null ) {
return createMetaCollectionAttribute( declaredType, element, returnTypeName, collection, targetEntity ); return createMetaCollectionAttribute( declaredType, element, returnTypeName, collection, targetEntity );
} }
@ -103,7 +103,7 @@ public class MetaAttributeGenerationVisitor extends SimpleTypeVisitor8<@Nullable
DeclaredType declaredType, Element element, String returnTypeName, String collection, DeclaredType declaredType, Element element, String returnTypeName, String collection,
@Nullable String targetEntity) { @Nullable String targetEntity) {
if ( hasAnnotation( element, ELEMENT_COLLECTION ) ) { if ( hasAnnotation( element, ELEMENT_COLLECTION ) ) {
final String explicitTargetEntity = TypeUtils.getTargetEntity( element.getAnnotationMirrors() ); final String explicitTargetEntity = getTargetEntity( element.getAnnotationMirrors() );
final TypeMirror collectionElementType = final TypeMirror collectionElementType =
getCollectionElementType( declaredType, returnTypeName, explicitTargetEntity, context ); getCollectionElementType( declaredType, returnTypeName, explicitTargetEntity, context );
if ( collectionElementType.getKind() == TypeKind.DECLARED ) { if ( collectionElementType.getKind() == TypeKind.DECLARED ) {