Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
import jakarta.persistence.Id;
import jakarta.persistence.IdClass;
import org.hibernate.AnnotationException;
import org.hibernate.annotations.processing.Exclude;
import org.hibernate.boot.MetadataSources;
import org.hibernate.boot.registry.StandardServiceRegistry;
import org.hibernate.testing.util.ServiceRegistryUtil;
Expand All @@ -19,6 +20,7 @@
/**
* @author Yanming Zhou
*/
@Exclude
public class CompositeIdTypeMismatchTest {

@Test
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
import java.io.Serializable;

import org.hibernate.AnnotationException;
import org.hibernate.annotations.processing.Exclude;
import org.hibernate.boot.MetadataSources;
import org.hibernate.boot.registry.StandardServiceRegistry;

Expand All @@ -26,6 +27,7 @@
* @author Marco Belladelli
*/
@Jira( "https://hibernate.atlassian.net/browse/HHH-16761" )
@Exclude
public class IdClassPropertiesTest {
@Test
public void testRight() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ private void handleNamedQueryAnnotation(String annotationName, boolean checkHql)
private void handleNamedQueryRepeatableAnnotation(String annotationName, boolean checkHql) {
final AnnotationMirror mirror = getAnnotationMirror( getElement(), annotationName );
if ( mirror != null ) {
final AnnotationValue value = getAnnotationValue( mirror, "value" );
final AnnotationValue value = getAnnotationValue( mirror );
if ( value != null ) {
@SuppressWarnings("unchecked")
final List<? extends AnnotationValue> annotationValues =
Expand Down Expand Up @@ -151,7 +151,7 @@ && isJavaIdentifierStart( name.charAt(1) )
private void addAuxiliaryMembersForRepeatableAnnotation(String annotationName, String prefix) {
final AnnotationMirror mirror = getAnnotationMirror( getElement(), annotationName );
if ( mirror != null ) {
final AnnotationValue value = getAnnotationValue( mirror, "value" );
final AnnotationValue value = getAnnotationValue( mirror );
if ( value != null ) {
@SuppressWarnings("unchecked")
final List<? extends AnnotationValue> annotationValues =
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@
import java.util.Set;
import java.util.StringTokenizer;
import java.util.regex.Pattern;
import java.util.stream.Stream;

import jakarta.persistence.AccessType;

Expand Down Expand Up @@ -463,31 +464,148 @@
initialized = true;
}

private void addIdClassIfNeeded(List<? extends Element> fields, List<? extends Element> methods) {
/**
* Creates a generated id class named {@code Entity_.Id} if the
* entity has multiple {@code @Id} fields, but no {@code @IdClass}
* annotation.
*/
private void addIdClassIfNeeded(List<VariableElement> fields, List<ExecutableElement> methods) {
if ( hasAnnotation( element, ID_CLASS ) ) {
return;
checkIdMembers( fields, methods );
}
else {
final List<MetaAttribute> components = getIdMemberNames( fields, methods );
if ( components.size() >= 2 ) {
putMember( ID_CLASS_MEMBER_NAME, new IdClassMetaAttribute( this, components ) );
}
}
}

private List<MetaAttribute> getIdMemberNames(List<VariableElement> fields, List<ExecutableElement> methods) {
final List<MetaAttribute> components = new ArrayList<>();
for ( Element field : fields ) {
if ( hasAnnotation( field, ID ) && isPersistent( field, AccessType.FIELD ) ) {
for ( var field : fields ) {
if ( isIdField( field ) ) {
final String propertyName = propertyName( field );
if ( members.containsKey( propertyName ) ) {
components.add( members.get( propertyName ) );
}
}
}
for ( Element method : methods ) {
if ( hasAnnotation( method, ID ) && isPersistent( method, AccessType.PROPERTY ) ) {
for ( var method : methods ) {
if ( isIdProperty( method ) ) {
final String propertyName = propertyName( method );
if ( members.containsKey( propertyName ) ) {
components.add( members.get( propertyName ) );
}
}
}
if ( components.size() < 2 ) {
return;
return components;
}

private void checkIdMembers(List<VariableElement> fields, List<ExecutableElement> methods) {
final AnnotationMirror annotationMirror = getAnnotationMirror( element, ID_CLASS );
if ( annotationMirror != null ) {
final AnnotationValue annotationValue = getAnnotationValue( annotationMirror );
if ( annotationValue != null && annotationValue.getValue() instanceof DeclaredType declaredType ) {
final TypeElement idClass = (TypeElement) declaredType.asElement();
if ( fields.stream().filter( this::isIdField ).count()
+ methods.stream().filter( this::isIdProperty ).count()
== 1 ) {
for ( var field : fields ) {
if ( isIdField( field ) ) {
if ( hasAnnotation( field, ONE_TO_ONE ) ) {
// Special case for @Id @OneToOne to an associated entity with an @IdClass
//TODO: check the id type of the associated entity is the same as idClass
return;
}
}
}
for ( var method : methods ) {
if ( isIdProperty( method ) ) {
if ( hasAnnotation( method, ONE_TO_ONE ) ) {
// Special case for @Id @OneToOne to an associated entity with an @IdClass
//TODO: check the id type of the associated entity is the same as idClass
return;
}
}
}
}
else {
for ( var field : fields ) {
if ( isIdField( field ) ) {
memberStream( idClass )
.filter( element -> element.getKind() == ElementKind.FIELD
&& element.getSimpleName().contentEquals( field.getSimpleName() ) )
.findAny()
.ifPresentOrElse( match -> {
if ( !isMatchingIdType( field, field.asType(), match.asType() ) ) {
context.message( match,
"id field should be of type '" + field.asType() + "'",
Diagnostic.Kind.ERROR );
}
},
() -> context.message( field,
"no matching field in id class '" + idClass.getSimpleName() + "'",
Diagnostic.Kind.ERROR ) );
}
}
for ( var method : methods ) {
if ( isIdProperty( method ) ) {
memberStream( idClass )
.filter( element -> element.getKind() == ElementKind.METHOD
&& element.getSimpleName().contentEquals( method.getSimpleName() )
&& isMatchingIdType( method, method.getReturnType(),
((ExecutableElement) element).getReturnType() ) )
.findAny()
.ifPresentOrElse( match -> {

Check notice

Code scanning / CodeQL

Useless parameter Note

The parameter 'match' is never used.
},
() -> context.message( method,
"no matching property in id class '" + idClass.getSimpleName() + "'",
Diagnostic.Kind.ERROR ) );
}
}
}
}
}
}

private boolean isIdProperty(ExecutableElement method) {
return hasAnnotation( method, ID )
&& isPersistent( method, AccessType.PROPERTY );
}

private boolean isIdField(VariableElement field) {
return hasAnnotation( field, ID )
&& isPersistent( field, AccessType.FIELD );
}

private static Stream<? extends Element> memberStream(TypeElement idClass) {
Stream<? extends Element> result = idClass.getEnclosedElements().stream();
TypeMirror superclass = idClass.getSuperclass();
while ( superclass.getKind() == TypeKind.DECLARED ) {
final DeclaredType declaredType = (DeclaredType) superclass;
final TypeElement typeElement = (TypeElement) declaredType.asElement();
result = Stream.concat( result, typeElement.getEnclosedElements().stream() );
superclass = typeElement.getSuperclass();
}
putMember( ID_CLASS_MEMBER_NAME, new IdClassMetaAttribute( this, components ) );
return result;
}

private boolean isMatchingIdType(Element id, TypeMirror type, TypeMirror match) {
return isSameType( type, match )
|| isEquivalentPrimitiveType( type, match )
|| isEquivalentPrimitiveType( match, type )
//TODO: check the id type of the associated entity
|| hasAnnotation( id, MANY_TO_ONE, ONE_TO_ONE );
}

private boolean isSameType(TypeMirror type, TypeMirror match) {
return context.getTypeUtils().isSameType( type, match );
}

private boolean isEquivalentPrimitiveType(TypeMirror type, TypeMirror match) {
return type.getKind().isPrimitive()
&& isSameType( context.getTypeUtils().boxedClass( ((PrimitiveType) type) ).asType(), match );
}

private boolean checkEntities(List<ExecutableElement> lifecycleMethods) {
Expand Down Expand Up @@ -1394,7 +1512,7 @@
Diagnostic.Kind.ERROR );
}
else if ( returnArgument
&& !context.getTypeUtils().isSameType( returnType, declaredParameterType ) ) {
&& !isSameType( returnType, declaredParameterType ) ) {
message( parameter,
"return type '" + returnType
+ "' disagrees with parameter type '" + parameterType + "'",
Expand Down Expand Up @@ -1767,7 +1885,7 @@
final List<OrderBy> result = new ArrayList<>();
@SuppressWarnings("unchecked")
final List<AnnotationValue> list = (List<AnnotationValue>)
castNonNull( getAnnotationValue( orderByList, "value" ) ).getValue();
castNonNull( getAnnotationValue( orderByList ) ).getValue();
for ( AnnotationValue element : list ) {
result.add( orderByExpression( castNonNull( (AnnotationMirror) element.getValue() ), entityType, method ) );
}
Expand All @@ -1782,7 +1900,7 @@
}

private OrderBy orderByExpression(AnnotationMirror orderBy, TypeElement entityType, ExecutableElement method) {
final String fieldName = castNonNull( getAnnotationValue(orderBy, "value") ).getValue().toString();
final String fieldName = castNonNull( getAnnotationValue(orderBy) ).getValue().toString();
if ( fieldName.equals("<error>") ) {
throw new ProcessLaterException();
}
Expand Down Expand Up @@ -2138,9 +2256,9 @@
else {
final AnnotationMirror idClass = getAnnotationMirror( entityType, ID_CLASS );
if ( idClass != null ) {
final AnnotationValue value = getAnnotationValue( idClass, "value" );
final AnnotationValue value = getAnnotationValue( idClass );
if ( value != null ) {
if ( context.getTypeUtils().isSameType( param.asType(), (TypeMirror) value.getValue() ) ) {
if ( isSameType( param.asType(), (TypeMirror) value.getValue() ) ) {
return FieldType.ID;
}
}
Expand Down Expand Up @@ -2360,7 +2478,7 @@
final ArrayType arrayType = (ArrayType) returnType;
final TypeMirror componentType = arrayType.getComponentType();
final TypeElement object = context.getElementUtils().getTypeElement(JAVA_OBJECT);
if ( !context.getTypeUtils().isSameType( object.asType(), componentType ) ) {
if ( !isSameType( object.asType(), componentType ) ) {
returnType = componentType;
containerTypeName = "[]";
}
Expand All @@ -2377,7 +2495,7 @@
containerTypeName = containerType.getQualifiedName().toString();
}

final AnnotationValue value = getAnnotationValue( mirror, "value" );
final AnnotationValue value = getAnnotationValue( mirror );
if ( value != null && value.getValue() instanceof String queryString ) {
addQueryMethod( method, returnType, containerTypeName, mirror, isNative, value, queryString );
}
Expand Down Expand Up @@ -2679,7 +2797,7 @@
else {
final TypeElement typeElement = context.getTypeElementForFullyQualifiedName( javaResultType.getName() );
final Types types = context.getTypeUtils();
returnTypeCorrect = context.getTypeUtils().isAssignable( returnType, types.erasure( typeElement.asType() ) );
returnTypeCorrect = types.isAssignable( returnType, types.erasure( typeElement.asType() ) );
}
}
catch (Exception e) {
Expand Down Expand Up @@ -3026,7 +3144,7 @@
final AnnotationMirror param = getAnnotationMirror( parameter, "jakarta.data.repository.Param" );
if ( by != null ) {
final String name =
castNonNull(getAnnotationValue(by, "value"))
castNonNull(getAnnotationValue(by))
.getValue().toString();
if ( name.contains("<error>") ) {
throw new ProcessLaterException();
Expand All @@ -3037,7 +3155,7 @@
}
else if ( param != null ) {
final String name =
castNonNull(getAnnotationValue(param, "value"))
castNonNull(getAnnotationValue(param))
.getValue().toString();
if ( name.contains("<error>") ) {
throw new ProcessLaterException();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@
import org.checkerframework.checker.nullness.qual.Nullable;
import org.hibernate.processor.Context;
import org.hibernate.processor.util.Constants;
import org.hibernate.processor.util.NullnessUtil;

import javax.lang.model.element.Element;
import javax.lang.model.element.TypeElement;
Expand All @@ -19,6 +18,7 @@
import javax.lang.model.util.SimpleTypeVisitor8;
import javax.lang.model.util.Types;

import static org.hibernate.processor.util.NullnessUtil.castNonNull;
import static org.hibernate.processor.util.TypeUtils.getTargetEntity;
import static org.hibernate.processor.util.TypeUtils.isBasicAttribute;
import static org.hibernate.processor.util.TypeUtils.isPropertyGetter;
Expand Down Expand Up @@ -71,7 +71,7 @@ private Types typeUtils() {
public @Nullable DataAnnotationMetaAttribute visitDeclared(DeclaredType declaredType, Element element) {
final TypeElement returnedElement = (TypeElement) typeUtils().asElement( declaredType );
// WARNING: .toString() is necessary here since Name equals does not compare to String
final String returnTypeName = NullnessUtil.castNonNull( returnedElement ).getQualifiedName().toString();
final String returnTypeName = castNonNull( returnedElement ).getQualifiedName().toString();
final String collection = Constants.COLLECTIONS.get( returnTypeName );
final String targetEntity = getTargetEntity( element.getAnnotationMirrors() );
if ( collection != null ) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@
import org.hibernate.processor.Context;
import org.hibernate.processor.util.AccessTypeInformation;
import org.hibernate.processor.util.Constants;
import org.hibernate.processor.util.NullnessUtil;

import javax.lang.model.element.AnnotationMirror;
import javax.lang.model.element.Element;
Expand Down Expand Up @@ -92,7 +91,7 @@ private Types typeUtils() {
final TypeElement returnedElement = (TypeElement) typeUtils().asElement( declaredType );
assert returnedElement != null;
// WARNING: .toString() is necessary here since Name equals does not compare to String
final String returnTypeName = NullnessUtil.castNonNull( returnedElement ).getQualifiedName().toString();
final String returnTypeName = castNonNull( returnedElement ).getQualifiedName().toString();
final String collection = Constants.COLLECTIONS.get( returnTypeName );
final String targetEntity = getTargetEntity( element.getAnnotationMirrors() );
if ( collection != null ) {
Expand All @@ -116,7 +115,7 @@ private AnnotationMetaAttribute createMetaCollectionAttribute(
getCollectionElementType( declaredType, returnTypeName, explicitTargetEntity, context );
if ( collectionElementType.getKind() == TypeKind.DECLARED ) {
final TypeElement collectionElement = (TypeElement) typeUtils().asElement( collectionElementType );
setAccessType( collectionElementType, NullnessUtil.castNonNull( collectionElement ) );
setAccessType( collectionElementType, castNonNull( collectionElement ) );
}
}
return createMetaAttribute( declaredType, element, collection, targetEntity );
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -260,6 +260,10 @@ public static boolean hasAnnotation(Element element, String... qualifiedNames) {
return false;
}

public static @Nullable AnnotationValue getAnnotationValue(AnnotationMirror annotationMirror) {
return getAnnotationValue( annotationMirror, DEFAULT_ANNOTATION_PARAMETER_NAME );
}

public static @Nullable AnnotationValue getAnnotationValue(AnnotationMirror annotationMirror, String member) {
assert annotationMirror != null;
assert member != null;
Expand Down Expand Up @@ -474,7 +478,7 @@ private static boolean isIdAnnotation(AnnotationMirror annotationMirror) {
public static @Nullable AccessType determineAnnotationSpecifiedAccessType(Element element) {
final AnnotationMirror mirror = getAnnotationMirror( element, ACCESS );
if ( mirror != null ) {
final AnnotationValue accessType = getAnnotationValue( mirror, DEFAULT_ANNOTATION_PARAMETER_NAME );
final AnnotationValue accessType = getAnnotationValue( mirror );
if ( accessType != null ) {
final VariableElement enumValue = (VariableElement) accessType.getValue();
final Name enumValueName = enumValue.getSimpleName();
Expand Down