Skip to content

Commit ec8d574

Browse files
committed
path expressions in finder method parameter names
this sounds a bit crazy but why not?
1 parent c5c3bb8 commit ec8d574

File tree

11 files changed

+147
-61
lines changed

11 files changed

+147
-61
lines changed

tooling/metamodel-generator/src/main/java/org/hibernate/jpamodelgen/annotation/AbstractFinderMethod.java

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,12 +7,12 @@
77
package org.hibernate.jpamodelgen.annotation;
88

99
import org.hibernate.internal.util.StringHelper;
10-
import org.hibernate.jpamodelgen.model.Metamodel;
1110
import org.hibernate.jpamodelgen.util.Constants;
1211

1312
import java.util.List;
1413
import java.util.Locale;
1514

15+
import static org.hibernate.internal.util.StringHelper.qualifier;
1616
import static org.hibernate.jpamodelgen.util.StringUtil.getUpperUnderscoreCaseFromLowerCamelCase;
1717

1818
/**
@@ -23,7 +23,7 @@ public abstract class AbstractFinderMethod extends AbstractQueryMethod {
2323
final List<String> fetchProfiles;
2424

2525
public AbstractFinderMethod(
26-
Metamodel annotationMetaEntity,
26+
AnnotationMetaEntity annotationMetaEntity,
2727
String methodName,
2828
String entity,
2929
boolean belongsToDao,
@@ -104,13 +104,14 @@ void comment(StringBuilder declaration) {
104104
.append(", ");
105105
}
106106
}
107+
final String path = param.replace('$', '.');
107108
declaration
108109
.append("{@link ")
109110
.append(annotationMetaEntity.importType(entity))
110111
.append('#')
111-
.append(param)
112+
.append(qualifier(path))
112113
.append(' ')
113-
.append(param)
114+
.append(path)
114115
.append("}");
115116
}
116117
declaration

tooling/metamodel-generator/src/main/java/org/hibernate/jpamodelgen/annotation/AbstractQueryMethod.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@
1919
* @author Gavin King
2020
*/
2121
public abstract class AbstractQueryMethod implements MetaAttribute {
22-
final Metamodel annotationMetaEntity;
22+
final AnnotationMetaEntity annotationMetaEntity;
2323
final String methodName;
2424
final List<String> paramNames;
2525
final List<String> paramTypes;
@@ -30,7 +30,7 @@ public abstract class AbstractQueryMethod implements MetaAttribute {
3030
final boolean addNonnullAnnotation;
3131

3232
public AbstractQueryMethod(
33-
Metamodel annotationMetaEntity,
33+
AnnotationMetaEntity annotationMetaEntity,
3434
String methodName,
3535
List<String> paramNames, List<String> paramTypes,
3636
@Nullable String returnTypeName,

tooling/metamodel-generator/src/main/java/org/hibernate/jpamodelgen/annotation/AnnotationMetaEntity.java

Lines changed: 96 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -6,12 +6,12 @@
66
*/
77
package org.hibernate.jpamodelgen.annotation;
88

9-
import java.beans.Introspector;
109
import java.util.ArrayList;
1110
import java.util.Collection;
1211
import java.util.HashMap;
1312
import java.util.List;
1413
import java.util.Map;
14+
import java.util.StringTokenizer;
1515
import javax.lang.model.element.AnnotationMirror;
1616
import javax.lang.model.element.AnnotationValue;
1717
import javax.lang.model.element.Element;
@@ -43,10 +43,12 @@
4343
import org.hibernate.query.sqm.tree.SqmStatement;
4444
import org.hibernate.query.sqm.tree.expression.SqmParameter;
4545

46+
import static java.beans.Introspector.decapitalize;
4647
import static java.util.Collections.emptyList;
4748
import static java.util.stream.Collectors.toList;
4849
import static javax.lang.model.util.ElementFilter.fieldsIn;
4950
import static javax.lang.model.util.ElementFilter.methodsIn;
51+
import static org.hibernate.internal.util.StringHelper.qualify;
5052
import static org.hibernate.jpamodelgen.annotation.QueryMethod.isOrderParam;
5153
import static org.hibernate.jpamodelgen.annotation.QueryMethod.isPageParam;
5254
import static org.hibernate.jpamodelgen.util.Constants.SESSION_TYPES;
@@ -109,6 +111,8 @@ public class AnnotationMetaEntity extends AnnotationMeta {
109111
*/
110112
private String sessionType = Constants.ENTITY_MANAGER;
111113

114+
private final Map<String,String> memberTypes = new HashMap<>();
115+
112116
public AnnotationMetaEntity(TypeElement element, Context context) {
113117
this.element = element;
114118
this.context = context;
@@ -124,6 +128,10 @@ public static AnnotationMetaEntity create(TypeElement element, Context context,
124128
return annotationMetaEntity;
125129
}
126130

131+
public @Nullable String getMemberType(String entityType, String memberName) {
132+
return memberTypes.get( qualify(entityType, memberName) );
133+
}
134+
127135
public AccessTypeInformation getEntityAccessTypeInfo() {
128136
return entityAccessTypeInfo;
129137
}
@@ -739,19 +747,9 @@ private int countNaturalIdFields(TypeElement entity) {
739747
}
740748

741749
private @Nullable FieldType validateFinderParameter(TypeElement entity, VariableElement param) {
742-
final String entityClassName = entity.getQualifiedName().toString();
743-
determineAccessTypeForHierarchy( entity, context );
744-
final AccessType accessType = castNonNull( context.getAccessTypeInfo( entityClassName ) ).getAccessType();
750+
final AccessType accessType = getAccessType(entity);
745751
for ( Element member : entity.getEnclosedElements() ) {
746-
if ( fieldMatchesParameter( param, member, accessType ) ) {
747-
final String memberType = memberType( member ).toString();
748-
final String paramType = param.asType().toString();
749-
if ( !memberType.equals(paramType) ) {
750-
context.message( param,
751-
"matching field has type '" + memberType
752-
+ "' in entity class '" + entityClassName + "'",
753-
Diagnostic.Kind.ERROR );
754-
}
752+
if ( fieldMatchesParameter( entity, param, member, accessType ) ) {
755753
if ( containsAnnotation( member, Constants.ID, Constants.EMBEDDED_ID ) ) {
756754
return FieldType.ID;
757755
}
@@ -773,37 +771,107 @@ else if ( containsAnnotation( member, Constants.NATURAL_ID ) ) {
773771
}
774772
}
775773
context.message( param,
776-
"no matching field named '" + param.getSimpleName()
777-
+ "' in entity class '" + entityClassName + "'",
774+
"no matching field named '"
775+
+ param.getSimpleName().toString().replace('$', '.')
776+
+ "' in entity class '" + entity + "'",
778777
Diagnostic.Kind.ERROR );
779778
return null;
780779
}
781780

781+
private AccessType getAccessType(TypeElement entity) {
782+
final String entityClassName = entity.getQualifiedName().toString();
783+
determineAccessTypeForHierarchy(entity, context );
784+
return castNonNull( context.getAccessTypeInfo( entityClassName ) ).getAccessType();
785+
}
786+
782787
private static TypeMirror memberType(Element member) {
783-
if (member.getKind() == ElementKind.METHOD) {
784-
ExecutableElement method = (ExecutableElement) member;
788+
if ( member.getKind() == ElementKind.METHOD ) {
789+
final ExecutableElement method = (ExecutableElement) member;
785790
return method.getReturnType();
786791
}
787792
else {
788793
return member.asType();
789794
}
790795
}
791796

792-
private static boolean fieldMatchesParameter(VariableElement param, Element member, AccessType accessType) {
793-
final Name name = member.getSimpleName();
794-
final Name paramName = param.getSimpleName();
795-
if ( accessType == AccessType.FIELD ) {
796-
return name.contentEquals(paramName);
797+
private boolean fieldMatchesParameter(TypeElement entityType, VariableElement param, Element member, AccessType accessType) {
798+
final StringTokenizer tokens = new StringTokenizer( param.getSimpleName().toString(), "$" );
799+
return fieldMatchesParameter( entityType, param, member, accessType, tokens, tokens.nextToken() );
800+
}
801+
802+
private boolean fieldMatchesParameter(
803+
TypeElement entityType,
804+
VariableElement param,
805+
Element member,
806+
AccessType accessType,
807+
StringTokenizer tokens,
808+
String token) {
809+
final Name memberName = member.getSimpleName();
810+
final TypeMirror type;
811+
if ( accessType == AccessType.FIELD && member.getKind() == ElementKind.FIELD ) {
812+
if ( !fieldMatches(token, memberName) ) {
813+
return false;
814+
}
815+
else {
816+
type = member.asType();
817+
}
818+
}
819+
else if ( accessType == AccessType.PROPERTY && member.getKind() == ElementKind.METHOD ) {
820+
if ( !getterMatches(token, memberName) ) {
821+
return false;
822+
}
823+
else {
824+
final ExecutableElement method = (ExecutableElement) member;
825+
type = method.getReturnType();
826+
}
797827
}
798828
else {
799-
if ( name.length() > 3 && name.subSequence(0,3).toString().equals("get")) {
800-
final String propertyName = Introspector.decapitalize(name.subSequence(3, name.length()).toString());
801-
return paramName.contentEquals(propertyName);
829+
return false;
830+
}
831+
832+
if ( tokens.hasMoreTokens() ) {
833+
final String nextToken = tokens.nextToken();
834+
if ( type.getKind() == TypeKind.DECLARED ) {
835+
final DeclaredType declaredType = (DeclaredType) type;
836+
final TypeElement memberType = (TypeElement) declaredType.asElement();
837+
memberTypes.put( qualify(entityType.getQualifiedName().toString(), memberName.toString()),
838+
memberType.getQualifiedName().toString() );
839+
final AccessType memberAccessType = getAccessType(memberType);
840+
for ( Element entityMember : memberType.getEnclosedElements() ) {
841+
if ( fieldMatchesParameter(memberType, param, entityMember, memberAccessType, tokens, nextToken) ) {
842+
return true;
843+
}
844+
}
802845
}
803-
if ( name.length() > 2 && name.subSequence(0,2).toString().equals("is")) {
804-
final String propertyName = Introspector.decapitalize(name.subSequence(2, name.length()).toString());
805-
return paramName.contentEquals(propertyName);
846+
return false;
847+
}
848+
else {
849+
final String memberType = memberType( member ).toString();
850+
final String paramType = param.asType().toString();
851+
if ( !isLegalAssignment( paramType, memberType ) ) {
852+
context.message( param,
853+
"matching field has type '" + memberType
854+
+ "' in entity class '" + entityType + "'",
855+
Diagnostic.Kind.ERROR );
806856
}
857+
return true;
858+
}
859+
}
860+
861+
private static boolean fieldMatches(String token, Name fieldName) {
862+
return fieldName.contentEquals(token);
863+
}
864+
865+
private static boolean getterMatches(String token, Name methodName) {
866+
if ( methodName.length() > 3 && methodName.subSequence(0,3).toString().equals("get")) {
867+
final String propertyName = decapitalize(methodName.subSequence(3, methodName.length()).toString());
868+
return token.equals(propertyName);
869+
}
870+
else if ( methodName.length() > 2 && methodName.subSequence(0,2).toString().equals("is")) {
871+
final String propertyName = decapitalize(methodName.subSequence(2, methodName.length()).toString());
872+
return token.equals(propertyName);
873+
}
874+
else {
807875
return false;
808876
}
809877
}

tooling/metamodel-generator/src/main/java/org/hibernate/jpamodelgen/annotation/CriteriaFinderMethod.java

Lines changed: 25 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -7,11 +7,11 @@
77
package org.hibernate.jpamodelgen.annotation;
88

99
import org.checkerframework.checker.nullness.qual.Nullable;
10-
import org.hibernate.jpamodelgen.model.Metamodel;
1110
import org.hibernate.jpamodelgen.util.Constants;
1211

1312
import java.util.List;
1413
import java.util.Set;
14+
import java.util.StringTokenizer;
1515

1616
/**
1717
* @author Gavin King
@@ -22,7 +22,7 @@ public class CriteriaFinderMethod extends AbstractFinderMethod {
2222
private final boolean isId;
2323

2424
public CriteriaFinderMethod(
25-
Metamodel annotationMetaEntity,
25+
AnnotationMetaEntity annotationMetaEntity,
2626
String methodName, String entity,
2727
@Nullable String containerType,
2828
List<String> paramNames, List<String> paramTypes,
@@ -94,20 +94,20 @@ public String getAttributeDeclarationString() {
9494
.append(paramName)
9595
.append("==null")
9696
.append("\n\t\t\t\t? ")
97-
.append("entity.get(");
98-
attributeRef( declaration, paramName );
97+
.append("entity");
98+
path( declaration, paramName );
9999
declaration
100-
.append(").isNull()")
100+
.append(".isNull()")
101101
.append("\n\t\t\t\t: ");
102102
}
103103
declaration
104-
.append("builder.equal(entity.get(");
105-
attributeRef( declaration, paramName );
104+
.append("builder.equal(entity");
105+
path( declaration, paramName );
106106
declaration
107-
.append("), ")
107+
.append(", ")
108108
//TODO: only safe if we are binding literals as parameters!!!
109109
.append(paramName)
110-
.append(")");
110+
.append(')');
111111
}
112112
}
113113
declaration
@@ -136,7 +136,7 @@ public String getAttributeDeclarationString() {
136136
.append(".getSingleResult()");
137137
}
138138
else if ( containerType.equals(Constants.LIST) ) {
139-
if ( unwrap || hasEnabledFetchProfiles) {
139+
if ( unwrap || hasEnabledFetchProfiles ) {
140140
declaration.append("\n\t\t\t");
141141
}
142142
declaration
@@ -147,20 +147,28 @@ else if ( containerType.equals(Constants.LIST) ) {
147147
return declaration.toString();
148148
}
149149

150+
private void path(StringBuilder declaration, String paramName) {
151+
final StringTokenizer tokens = new StringTokenizer(paramName, "$");
152+
String typeName = entity;
153+
while ( typeName!= null && tokens.hasMoreTokens() ) {
154+
final String memberName = tokens.nextToken();
155+
declaration
156+
.append(".get(")
157+
.append(annotationMetaEntity.importType(typeName + '_'))
158+
.append('.')
159+
.append(memberName)
160+
.append(')');
161+
typeName = annotationMetaEntity.getMemberType(typeName, memberName);
162+
}
163+
}
164+
150165
private static boolean isPrimitive(String paramType) {
151166
return PRIMITIVE_TYPES.contains( paramType );
152167
}
153168

154169
private static final Set<String> PRIMITIVE_TYPES =
155170
Set.of("boolean", "char", "long", "int", "short", "byte", "double", "float");
156171

157-
private void attributeRef(StringBuilder declaration, String paramName) {
158-
declaration
159-
.append(annotationMetaEntity.importType(entity + '_'))
160-
.append('.')
161-
.append(paramName);
162-
}
163-
164172
private StringBuilder returnType() {
165173
StringBuilder type = new StringBuilder();
166174
boolean returnsUni = isReactive()

tooling/metamodel-generator/src/main/java/org/hibernate/jpamodelgen/annotation/IdFinderMethod.java

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,6 @@
66
*/
77
package org.hibernate.jpamodelgen.annotation;
88

9-
import org.hibernate.jpamodelgen.model.Metamodel;
10-
import org.hibernate.jpamodelgen.util.Constants;
11-
129
import java.util.List;
1310

1411
/**
@@ -19,7 +16,7 @@ public class IdFinderMethod extends AbstractFinderMethod {
1916
private final String paramName;
2017

2118
public IdFinderMethod(
22-
Metamodel annotationMetaEntity,
19+
AnnotationMetaEntity annotationMetaEntity,
2320
String methodName, String entity,
2421
List<String> paramNames, List<String> paramTypes,
2522
boolean belongsToDao,

tooling/metamodel-generator/src/main/java/org/hibernate/jpamodelgen/annotation/NaturalIdFinderMethod.java

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,6 @@
66
*/
77
package org.hibernate.jpamodelgen.annotation;
88

9-
import org.hibernate.jpamodelgen.model.Metamodel;
10-
119
import java.util.List;
1210

1311
/**
@@ -16,7 +14,7 @@
1614
public class NaturalIdFinderMethod extends AbstractFinderMethod {
1715

1816
public NaturalIdFinderMethod(
19-
Metamodel annotationMetaEntity,
17+
AnnotationMetaEntity annotationMetaEntity,
2018
String methodName, String entity,
2119
List<String> paramNames, List<String> paramTypes,
2220
boolean belongsToDao,

tooling/metamodel-generator/src/main/java/org/hibernate/jpamodelgen/annotation/QueryMethod.java

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,6 @@
88

99
import org.checkerframework.checker.nullness.qual.Nullable;
1010
import org.hibernate.internal.util.StringHelper;
11-
import org.hibernate.jpamodelgen.model.Metamodel;
1211
import org.hibernate.jpamodelgen.util.Constants;
1312
import org.hibernate.query.Order;
1413
import org.hibernate.query.Page;
@@ -28,7 +27,7 @@ public class QueryMethod extends AbstractQueryMethod {
2827
private final boolean isNative;
2928

3029
public QueryMethod(
31-
Metamodel annotationMetaEntity,
30+
AnnotationMetaEntity annotationMetaEntity,
3231
String methodName,
3332
String queryString,
3433
@Nullable

0 commit comments

Comments
 (0)