private void handleAssociationQueryViaPropertyExpression()

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));
            }
        }
    }