in grails-datamapping-core/src/main/groovy/org/grails/datastore/gorm/query/transform/DetachedCriteriaTransformer.java [896:1046]
private void handleAssociationQueryViaPropertyExpression(PropertyExpression pe, Expression oppositeSide, String operator, BlockStatement newCode, List<String> propertyNames, String functionName, VariableScope variableScope) {
Expression objectExpression = pe.getObjectExpression();
if (objectExpression instanceof PropertyExpression) {
// nested property expression, we have to find the root variable expression and walk backwards through all the properties involved
List<String> associationMethodCalls = new ArrayList<String>();
while (objectExpression instanceof PropertyExpression) {
PropertyExpression currentPe = (PropertyExpression) objectExpression;
associationMethodCalls.add(currentPe.getPropertyAsString());
objectExpression = currentPe.getObjectExpression();
}
if (objectExpression instanceof VariableExpression) {
VariableExpression ve = (VariableExpression) objectExpression;
String propertyName = ve.getName();
// handle trait
if(!(propertyName.equals("$self") && Traits.isTrait(objectExpression.getType()))) {
associationMethodCalls.add(propertyName);
}
Collections.reverse(associationMethodCalls);
ClassNode currentType = currentClassNode;
BlockStatement currentBody = newCode;
VariableExpression delegateExpression = new VariableExpression("delegate");
for (Iterator<String> iterator = associationMethodCalls.iterator(); iterator.hasNext(); ) {
String associationMethodCall = iterator.next();
ClosureAndArguments closureAndArguments = new ClosureAndArguments(variableScope);
ArgumentListExpression arguments = closureAndArguments.getArguments();
ClassNode type = getPropertyTypeFromGenerics(associationMethodCall, currentType);
if (type == null) break;
currentType = type;
currentBody.addStatement(new ExpressionStatement(new MethodCallExpression(delegateExpression, associationMethodCall, arguments)));
currentBody = closureAndArguments.getCurrentBody();
if (!iterator.hasNext()) {
String associationProperty = pe.getPropertyAsString();
List<String> associationPropertyNames = getPropertyNamesForAssociation(type);
ClassNode existing = this.currentClassNode;
try {
this.currentClassNode = type;
boolean hasNoProperties = associationPropertyNames.isEmpty();
if (functionName != null) {
handleFunctionCall(currentBody, operator, oppositeSide, functionName, new ConstantExpression(associationProperty));
} else {
addCriteriaCallMethodExpression(currentBody, operator, pe, oppositeSide, associationProperty, associationPropertyNames, hasNoProperties, variableScope);
}
} finally {
this.currentClassNode = existing;
}
}
}
}
} else if (objectExpression instanceof VariableExpression) {
String propertyName = objectExpression.getText();
// handle trait
if(propertyName.equals("$self") && Traits.isTrait(objectExpression.getType())) {
propertyName = pe.getPropertyAsString();
}
Object aliased = aliases.get(propertyName);
if (propertyNames.contains(propertyName) || (aliased != null && propertyNames.contains(aliased))) {
String associationProperty = pe.getPropertyAsString();
String actualPropertyName = aliased != null ? aliased.toString() : propertyName;
ClassNode classNode = currentClassNode;
ClassNode type = getPropertyTypeFromGenerics(actualPropertyName, classNode);
if(!AstUtils.isDomainClass(type)) {
if(AstUtils.isGroovyType(type)) {
// an embedded property
List<String> associationPropertyNames = AstPropertyResolveUtils.getPropertyNames(type);
boolean hasNoProperties = associationPropertyNames.isEmpty();
if (!hasNoProperties && !associationPropertyNames.contains(associationProperty)) {
sourceUnit.getErrorCollector().addError(new LocatedMessage("Cannot query property \"" + associationProperty + "\" - no such property on class " + type.getName() + " exists.", Token.newString(propertyName, pe.getLineNumber(), pe.getColumnNumber()), sourceUnit));
}
ClosureAndArguments closureAndArguments = new ClosureAndArguments(variableScope);
BlockStatement currentBody = closureAndArguments.getCurrentBody();
ArgumentListExpression arguments = closureAndArguments.getArguments();
ClassNode existing = this.currentClassNode;
try {
this.currentClassNode = type;
if (functionName != null) {
handleFunctionCall(currentBody, operator, oppositeSide, functionName, new ConstantExpression(associationProperty));
} else {
addCriteriaCallMethodExpression(currentBody, operator, pe, oppositeSide, associationProperty, associationPropertyNames, hasNoProperties, variableScope);
}
} finally {
this.currentClassNode = existing;
}
newCode.addStatement(new ExpressionStatement(new MethodCallExpression(new VariableExpression("delegate"), actualPropertyName, arguments)));
}
else {
addCriteriaCallMethodExpression(newCode, operator, pe, oppositeSide, associationProperty, Collections.<String>emptyList(), false, variableScope);
}
}
else {
List<String> associationPropertyNames = getPropertyNamesForAssociation(type);
if (associationPropertyNames == null) {
associationPropertyNames = getPropertyNamesForAssociation(classNode);
}
ClosureAndArguments closureAndArguments = new ClosureAndArguments(variableScope);
BlockStatement currentBody = closureAndArguments.getCurrentBody();
ArgumentListExpression arguments = closureAndArguments.getArguments();
boolean hasNoProperties = associationPropertyNames.isEmpty();
if (!hasNoProperties && !associationPropertyNames.contains(associationProperty)) {
sourceUnit.getErrorCollector().addError(new LocatedMessage("Cannot query property \"" + associationProperty + "\" - no such property on class " + type.getName() + " exists.", Token.newString(propertyName, pe.getLineNumber(), pe.getColumnNumber()), sourceUnit));
}
ClassNode existing = this.currentClassNode;
try {
this.currentClassNode = type;
if (functionName != null) {
handleFunctionCall(currentBody, operator, oppositeSide, functionName, new ConstantExpression(associationProperty));
} else {
addCriteriaCallMethodExpression(currentBody, operator, pe, oppositeSide, associationProperty, associationPropertyNames, hasNoProperties, variableScope);
}
} finally {
this.currentClassNode = existing;
}
newCode.addStatement(new ExpressionStatement(new MethodCallExpression(new VariableExpression("delegate"), actualPropertyName, arguments)));
}
} else if((aliased instanceof ClassNode) && (oppositeSide instanceof PropertyExpression)) {
String rootReference = pe.getText();
PropertyExpression oppositeProperty = (PropertyExpression) oppositeSide;
String targetObject = oppositeProperty.getObjectExpression().getText();
if(aliases.containsKey(targetObject)) {
String methodToCall = PROPERTY_COMPARISON_OPERATOR_TO_CRITERIA_METHOD_MAP.get(operator);
ArgumentListExpression args = new ArgumentListExpression();
args.addExpression(new ConstantExpression(rootReference));
args.addExpression(new ConstantExpression(oppositeProperty.getText()));
newCode.addStatement(new ExpressionStatement(new MethodCallExpression(new VariableExpression("this"), methodToCall, args)));
}
} else if (!variableScope.isReferencedLocalVariable(propertyName)) {
sourceUnit.getErrorCollector().addError(new LocatedMessage("Cannot query property \"" + propertyName + "\" - no such property on class " + this.currentClassNode.getName() + " exists.", Token.newString(propertyName, pe.getLineNumber(), pe.getColumnNumber()), sourceUnit));
}
}
}