path expressions in finder method parameter names
this sounds a bit crazy but why not?
This commit is contained in:
parent
c5c3bb8ac8
commit
ec8d574e4a
|
@ -7,12 +7,12 @@
|
|||
package org.hibernate.jpamodelgen.annotation;
|
||||
|
||||
import org.hibernate.internal.util.StringHelper;
|
||||
import org.hibernate.jpamodelgen.model.Metamodel;
|
||||
import org.hibernate.jpamodelgen.util.Constants;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
|
||||
import static org.hibernate.internal.util.StringHelper.qualifier;
|
||||
import static org.hibernate.jpamodelgen.util.StringUtil.getUpperUnderscoreCaseFromLowerCamelCase;
|
||||
|
||||
/**
|
||||
|
@ -23,7 +23,7 @@ public abstract class AbstractFinderMethod extends AbstractQueryMethod {
|
|||
final List<String> fetchProfiles;
|
||||
|
||||
public AbstractFinderMethod(
|
||||
Metamodel annotationMetaEntity,
|
||||
AnnotationMetaEntity annotationMetaEntity,
|
||||
String methodName,
|
||||
String entity,
|
||||
boolean belongsToDao,
|
||||
|
@ -104,13 +104,14 @@ public abstract class AbstractFinderMethod extends AbstractQueryMethod {
|
|||
.append(", ");
|
||||
}
|
||||
}
|
||||
final String path = param.replace('$', '.');
|
||||
declaration
|
||||
.append("{@link ")
|
||||
.append(annotationMetaEntity.importType(entity))
|
||||
.append('#')
|
||||
.append(param)
|
||||
.append(qualifier(path))
|
||||
.append(' ')
|
||||
.append(param)
|
||||
.append(path)
|
||||
.append("}");
|
||||
}
|
||||
declaration
|
||||
|
|
|
@ -19,7 +19,7 @@ import static org.hibernate.jpamodelgen.util.Constants.SESSION_TYPES;
|
|||
* @author Gavin King
|
||||
*/
|
||||
public abstract class AbstractQueryMethod implements MetaAttribute {
|
||||
final Metamodel annotationMetaEntity;
|
||||
final AnnotationMetaEntity annotationMetaEntity;
|
||||
final String methodName;
|
||||
final List<String> paramNames;
|
||||
final List<String> paramTypes;
|
||||
|
@ -30,7 +30,7 @@ public abstract class AbstractQueryMethod implements MetaAttribute {
|
|||
final boolean addNonnullAnnotation;
|
||||
|
||||
public AbstractQueryMethod(
|
||||
Metamodel annotationMetaEntity,
|
||||
AnnotationMetaEntity annotationMetaEntity,
|
||||
String methodName,
|
||||
List<String> paramNames, List<String> paramTypes,
|
||||
@Nullable String returnTypeName,
|
||||
|
|
|
@ -6,12 +6,12 @@
|
|||
*/
|
||||
package org.hibernate.jpamodelgen.annotation;
|
||||
|
||||
import java.beans.Introspector;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.StringTokenizer;
|
||||
import javax.lang.model.element.AnnotationMirror;
|
||||
import javax.lang.model.element.AnnotationValue;
|
||||
import javax.lang.model.element.Element;
|
||||
|
@ -43,10 +43,12 @@ import org.hibernate.query.sqm.SqmExpressible;
|
|||
import org.hibernate.query.sqm.tree.SqmStatement;
|
||||
import org.hibernate.query.sqm.tree.expression.SqmParameter;
|
||||
|
||||
import static java.beans.Introspector.decapitalize;
|
||||
import static java.util.Collections.emptyList;
|
||||
import static java.util.stream.Collectors.toList;
|
||||
import static javax.lang.model.util.ElementFilter.fieldsIn;
|
||||
import static javax.lang.model.util.ElementFilter.methodsIn;
|
||||
import static org.hibernate.internal.util.StringHelper.qualify;
|
||||
import static org.hibernate.jpamodelgen.annotation.QueryMethod.isOrderParam;
|
||||
import static org.hibernate.jpamodelgen.annotation.QueryMethod.isPageParam;
|
||||
import static org.hibernate.jpamodelgen.util.Constants.SESSION_TYPES;
|
||||
|
@ -109,6 +111,8 @@ public class AnnotationMetaEntity extends AnnotationMeta {
|
|||
*/
|
||||
private String sessionType = Constants.ENTITY_MANAGER;
|
||||
|
||||
private final Map<String,String> memberTypes = new HashMap<>();
|
||||
|
||||
public AnnotationMetaEntity(TypeElement element, Context context) {
|
||||
this.element = element;
|
||||
this.context = context;
|
||||
|
@ -124,6 +128,10 @@ public class AnnotationMetaEntity extends AnnotationMeta {
|
|||
return annotationMetaEntity;
|
||||
}
|
||||
|
||||
public @Nullable String getMemberType(String entityType, String memberName) {
|
||||
return memberTypes.get( qualify(entityType, memberName) );
|
||||
}
|
||||
|
||||
public AccessTypeInformation getEntityAccessTypeInfo() {
|
||||
return entityAccessTypeInfo;
|
||||
}
|
||||
|
@ -739,19 +747,9 @@ public class AnnotationMetaEntity extends AnnotationMeta {
|
|||
}
|
||||
|
||||
private @Nullable FieldType validateFinderParameter(TypeElement entity, VariableElement param) {
|
||||
final String entityClassName = entity.getQualifiedName().toString();
|
||||
determineAccessTypeForHierarchy( entity, context );
|
||||
final AccessType accessType = castNonNull( context.getAccessTypeInfo( entityClassName ) ).getAccessType();
|
||||
final AccessType accessType = getAccessType(entity);
|
||||
for ( Element member : entity.getEnclosedElements() ) {
|
||||
if ( fieldMatchesParameter( param, member, accessType ) ) {
|
||||
final String memberType = memberType( member ).toString();
|
||||
final String paramType = param.asType().toString();
|
||||
if ( !memberType.equals(paramType) ) {
|
||||
context.message( param,
|
||||
"matching field has type '" + memberType
|
||||
+ "' in entity class '" + entityClassName + "'",
|
||||
Diagnostic.Kind.ERROR );
|
||||
}
|
||||
if ( fieldMatchesParameter( entity, param, member, accessType ) ) {
|
||||
if ( containsAnnotation( member, Constants.ID, Constants.EMBEDDED_ID ) ) {
|
||||
return FieldType.ID;
|
||||
}
|
||||
|
@ -773,15 +771,22 @@ public class AnnotationMetaEntity extends AnnotationMeta {
|
|||
}
|
||||
}
|
||||
context.message( param,
|
||||
"no matching field named '" + param.getSimpleName()
|
||||
+ "' in entity class '" + entityClassName + "'",
|
||||
"no matching field named '"
|
||||
+ param.getSimpleName().toString().replace('$', '.')
|
||||
+ "' in entity class '" + entity + "'",
|
||||
Diagnostic.Kind.ERROR );
|
||||
return null;
|
||||
}
|
||||
|
||||
private AccessType getAccessType(TypeElement entity) {
|
||||
final String entityClassName = entity.getQualifiedName().toString();
|
||||
determineAccessTypeForHierarchy(entity, context );
|
||||
return castNonNull( context.getAccessTypeInfo( entityClassName ) ).getAccessType();
|
||||
}
|
||||
|
||||
private static TypeMirror memberType(Element member) {
|
||||
if (member.getKind() == ElementKind.METHOD) {
|
||||
ExecutableElement method = (ExecutableElement) member;
|
||||
if ( member.getKind() == ElementKind.METHOD ) {
|
||||
final ExecutableElement method = (ExecutableElement) member;
|
||||
return method.getReturnType();
|
||||
}
|
||||
else {
|
||||
|
@ -789,21 +794,84 @@ public class AnnotationMetaEntity extends AnnotationMeta {
|
|||
}
|
||||
}
|
||||
|
||||
private static boolean fieldMatchesParameter(VariableElement param, Element member, AccessType accessType) {
|
||||
final Name name = member.getSimpleName();
|
||||
final Name paramName = param.getSimpleName();
|
||||
if ( accessType == AccessType.FIELD ) {
|
||||
return name.contentEquals(paramName);
|
||||
private boolean fieldMatchesParameter(TypeElement entityType, VariableElement param, Element member, AccessType accessType) {
|
||||
final StringTokenizer tokens = new StringTokenizer( param.getSimpleName().toString(), "$" );
|
||||
return fieldMatchesParameter( entityType, param, member, accessType, tokens, tokens.nextToken() );
|
||||
}
|
||||
|
||||
private boolean fieldMatchesParameter(
|
||||
TypeElement entityType,
|
||||
VariableElement param,
|
||||
Element member,
|
||||
AccessType accessType,
|
||||
StringTokenizer tokens,
|
||||
String token) {
|
||||
final Name memberName = member.getSimpleName();
|
||||
final TypeMirror type;
|
||||
if ( accessType == AccessType.FIELD && member.getKind() == ElementKind.FIELD ) {
|
||||
if ( !fieldMatches(token, memberName) ) {
|
||||
return false;
|
||||
}
|
||||
else {
|
||||
type = member.asType();
|
||||
}
|
||||
}
|
||||
else if ( accessType == AccessType.PROPERTY && member.getKind() == ElementKind.METHOD ) {
|
||||
if ( !getterMatches(token, memberName) ) {
|
||||
return false;
|
||||
}
|
||||
else {
|
||||
final ExecutableElement method = (ExecutableElement) member;
|
||||
type = method.getReturnType();
|
||||
}
|
||||
}
|
||||
else {
|
||||
if ( name.length() > 3 && name.subSequence(0,3).toString().equals("get")) {
|
||||
final String propertyName = Introspector.decapitalize(name.subSequence(3, name.length()).toString());
|
||||
return paramName.contentEquals(propertyName);
|
||||
return false;
|
||||
}
|
||||
|
||||
if ( tokens.hasMoreTokens() ) {
|
||||
final String nextToken = tokens.nextToken();
|
||||
if ( type.getKind() == TypeKind.DECLARED ) {
|
||||
final DeclaredType declaredType = (DeclaredType) type;
|
||||
final TypeElement memberType = (TypeElement) declaredType.asElement();
|
||||
memberTypes.put( qualify(entityType.getQualifiedName().toString(), memberName.toString()),
|
||||
memberType.getQualifiedName().toString() );
|
||||
final AccessType memberAccessType = getAccessType(memberType);
|
||||
for ( Element entityMember : memberType.getEnclosedElements() ) {
|
||||
if ( fieldMatchesParameter(memberType, param, entityMember, memberAccessType, tokens, nextToken) ) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
if ( name.length() > 2 && name.subSequence(0,2).toString().equals("is")) {
|
||||
final String propertyName = Introspector.decapitalize(name.subSequence(2, name.length()).toString());
|
||||
return paramName.contentEquals(propertyName);
|
||||
return false;
|
||||
}
|
||||
else {
|
||||
final String memberType = memberType( member ).toString();
|
||||
final String paramType = param.asType().toString();
|
||||
if ( !isLegalAssignment( paramType, memberType ) ) {
|
||||
context.message( param,
|
||||
"matching field has type '" + memberType
|
||||
+ "' in entity class '" + entityType + "'",
|
||||
Diagnostic.Kind.ERROR );
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
private static boolean fieldMatches(String token, Name fieldName) {
|
||||
return fieldName.contentEquals(token);
|
||||
}
|
||||
|
||||
private static boolean getterMatches(String token, Name methodName) {
|
||||
if ( methodName.length() > 3 && methodName.subSequence(0,3).toString().equals("get")) {
|
||||
final String propertyName = decapitalize(methodName.subSequence(3, methodName.length()).toString());
|
||||
return token.equals(propertyName);
|
||||
}
|
||||
else if ( methodName.length() > 2 && methodName.subSequence(0,2).toString().equals("is")) {
|
||||
final String propertyName = decapitalize(methodName.subSequence(2, methodName.length()).toString());
|
||||
return token.equals(propertyName);
|
||||
}
|
||||
else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -7,11 +7,11 @@
|
|||
package org.hibernate.jpamodelgen.annotation;
|
||||
|
||||
import org.checkerframework.checker.nullness.qual.Nullable;
|
||||
import org.hibernate.jpamodelgen.model.Metamodel;
|
||||
import org.hibernate.jpamodelgen.util.Constants;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.StringTokenizer;
|
||||
|
||||
/**
|
||||
* @author Gavin King
|
||||
|
@ -22,7 +22,7 @@ public class CriteriaFinderMethod extends AbstractFinderMethod {
|
|||
private final boolean isId;
|
||||
|
||||
public CriteriaFinderMethod(
|
||||
Metamodel annotationMetaEntity,
|
||||
AnnotationMetaEntity annotationMetaEntity,
|
||||
String methodName, String entity,
|
||||
@Nullable String containerType,
|
||||
List<String> paramNames, List<String> paramTypes,
|
||||
|
@ -94,20 +94,20 @@ public class CriteriaFinderMethod extends AbstractFinderMethod {
|
|||
.append(paramName)
|
||||
.append("==null")
|
||||
.append("\n\t\t\t\t? ")
|
||||
.append("entity.get(");
|
||||
attributeRef( declaration, paramName );
|
||||
.append("entity");
|
||||
path( declaration, paramName );
|
||||
declaration
|
||||
.append(").isNull()")
|
||||
.append(".isNull()")
|
||||
.append("\n\t\t\t\t: ");
|
||||
}
|
||||
declaration
|
||||
.append("builder.equal(entity.get(");
|
||||
attributeRef( declaration, paramName );
|
||||
.append("builder.equal(entity");
|
||||
path( declaration, paramName );
|
||||
declaration
|
||||
.append("), ")
|
||||
.append(", ")
|
||||
//TODO: only safe if we are binding literals as parameters!!!
|
||||
.append(paramName)
|
||||
.append(")");
|
||||
.append(')');
|
||||
}
|
||||
}
|
||||
declaration
|
||||
|
@ -136,7 +136,7 @@ public class CriteriaFinderMethod extends AbstractFinderMethod {
|
|||
.append(".getSingleResult()");
|
||||
}
|
||||
else if ( containerType.equals(Constants.LIST) ) {
|
||||
if ( unwrap || hasEnabledFetchProfiles) {
|
||||
if ( unwrap || hasEnabledFetchProfiles ) {
|
||||
declaration.append("\n\t\t\t");
|
||||
}
|
||||
declaration
|
||||
|
@ -147,6 +147,21 @@ public class CriteriaFinderMethod extends AbstractFinderMethod {
|
|||
return declaration.toString();
|
||||
}
|
||||
|
||||
private void path(StringBuilder declaration, String paramName) {
|
||||
final StringTokenizer tokens = new StringTokenizer(paramName, "$");
|
||||
String typeName = entity;
|
||||
while ( typeName!= null && tokens.hasMoreTokens() ) {
|
||||
final String memberName = tokens.nextToken();
|
||||
declaration
|
||||
.append(".get(")
|
||||
.append(annotationMetaEntity.importType(typeName + '_'))
|
||||
.append('.')
|
||||
.append(memberName)
|
||||
.append(')');
|
||||
typeName = annotationMetaEntity.getMemberType(typeName, memberName);
|
||||
}
|
||||
}
|
||||
|
||||
private static boolean isPrimitive(String paramType) {
|
||||
return PRIMITIVE_TYPES.contains( paramType );
|
||||
}
|
||||
|
@ -154,13 +169,6 @@ public class CriteriaFinderMethod extends AbstractFinderMethod {
|
|||
private static final Set<String> PRIMITIVE_TYPES =
|
||||
Set.of("boolean", "char", "long", "int", "short", "byte", "double", "float");
|
||||
|
||||
private void attributeRef(StringBuilder declaration, String paramName) {
|
||||
declaration
|
||||
.append(annotationMetaEntity.importType(entity + '_'))
|
||||
.append('.')
|
||||
.append(paramName);
|
||||
}
|
||||
|
||||
private StringBuilder returnType() {
|
||||
StringBuilder type = new StringBuilder();
|
||||
boolean returnsUni = isReactive()
|
||||
|
|
|
@ -6,9 +6,6 @@
|
|||
*/
|
||||
package org.hibernate.jpamodelgen.annotation;
|
||||
|
||||
import org.hibernate.jpamodelgen.model.Metamodel;
|
||||
import org.hibernate.jpamodelgen.util.Constants;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
|
@ -19,7 +16,7 @@ public class IdFinderMethod extends AbstractFinderMethod {
|
|||
private final String paramName;
|
||||
|
||||
public IdFinderMethod(
|
||||
Metamodel annotationMetaEntity,
|
||||
AnnotationMetaEntity annotationMetaEntity,
|
||||
String methodName, String entity,
|
||||
List<String> paramNames, List<String> paramTypes,
|
||||
boolean belongsToDao,
|
||||
|
|
|
@ -6,8 +6,6 @@
|
|||
*/
|
||||
package org.hibernate.jpamodelgen.annotation;
|
||||
|
||||
import org.hibernate.jpamodelgen.model.Metamodel;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
|
@ -16,7 +14,7 @@ import java.util.List;
|
|||
public class NaturalIdFinderMethod extends AbstractFinderMethod {
|
||||
|
||||
public NaturalIdFinderMethod(
|
||||
Metamodel annotationMetaEntity,
|
||||
AnnotationMetaEntity annotationMetaEntity,
|
||||
String methodName, String entity,
|
||||
List<String> paramNames, List<String> paramTypes,
|
||||
boolean belongsToDao,
|
||||
|
|
|
@ -8,7 +8,6 @@ package org.hibernate.jpamodelgen.annotation;
|
|||
|
||||
import org.checkerframework.checker.nullness.qual.Nullable;
|
||||
import org.hibernate.internal.util.StringHelper;
|
||||
import org.hibernate.jpamodelgen.model.Metamodel;
|
||||
import org.hibernate.jpamodelgen.util.Constants;
|
||||
import org.hibernate.query.Order;
|
||||
import org.hibernate.query.Page;
|
||||
|
@ -28,7 +27,7 @@ public class QueryMethod extends AbstractQueryMethod {
|
|||
private final boolean isNative;
|
||||
|
||||
public QueryMethod(
|
||||
Metamodel annotationMetaEntity,
|
||||
AnnotationMetaEntity annotationMetaEntity,
|
||||
String methodName,
|
||||
String queryString,
|
||||
@Nullable
|
||||
|
|
|
@ -2,6 +2,7 @@ package org.hibernate.jpamodelgen.test.hqlsql;
|
|||
|
||||
import jakarta.persistence.Entity;
|
||||
import jakarta.persistence.Id;
|
||||
import jakarta.persistence.ManyToOne;
|
||||
import org.hibernate.annotations.NaturalId;
|
||||
|
||||
@Entity
|
||||
|
@ -10,4 +11,5 @@ public class Book {
|
|||
@NaturalId String title;
|
||||
String text;
|
||||
@NaturalId String authorName;
|
||||
@ManyToOne Publisher publisher;
|
||||
}
|
||||
|
|
|
@ -41,4 +41,7 @@ public interface Dao {
|
|||
|
||||
@SQL("select * from Book where isbn = :isbn")
|
||||
Book findByIsbnNative(String isbn);
|
||||
|
||||
@Find
|
||||
List<Book> publishedBooks(String publisher$name);
|
||||
}
|
||||
|
|
|
@ -0,0 +1,10 @@
|
|||
package org.hibernate.jpamodelgen.test.hqlsql;
|
||||
|
||||
import jakarta.persistence.Entity;
|
||||
import jakarta.persistence.Id;
|
||||
|
||||
@Entity
|
||||
public class Publisher {
|
||||
@Id Long id;
|
||||
String name;
|
||||
}
|
|
@ -18,7 +18,7 @@ import static org.hibernate.jpamodelgen.test.util.TestUtil.assertMetamodelClassG
|
|||
*/
|
||||
public class QueryMethodTest extends CompilationTest {
|
||||
@Test
|
||||
@WithClasses({ Book.class, Dao.class, Books.class })
|
||||
@WithClasses({ Book.class, Publisher.class, Dao.class, Books.class })
|
||||
public void testQueryMethod() {
|
||||
System.out.println( TestUtil.getMetaModelSourceAsString( Dao.class ) );
|
||||
System.out.println( TestUtil.getMetaModelSourceAsString( Books.class ) );
|
||||
|
|
Loading…
Reference in New Issue