/*
 * Copyright (c) 1998, 2019 Oracle and/or its affiliates. All rights reserved.
 * Copyright (c) 2019 IBM Corporation. All rights reserved.
 *
 * This program and the accompanying materials are made available under the
 * terms of the Eclipse Public License v. 2.0 which is available at
 * http://www.eclipse.org/legal/epl-2.0,
 * or the Eclipse Distribution License v. 1.0 which is available at
 * http://www.eclipse.org/org/documents/edl-v10.php.
 *
 * SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause
 */

// Contributors:
//     Oracle - initial API and implementation from Oracle TopLink
//     IBM - Bug 537795: CASE THEN and ELSE scalar expression Constants should not be casted to CASE operand type
package org.eclipse.persistence.internal.expressions;

import java.io.BufferedWriter;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Vector;

import org.eclipse.persistence.descriptors.ClassDescriptor;
import org.eclipse.persistence.exceptions.QueryException;
import org.eclipse.persistence.expressions.Expression;
import org.eclipse.persistence.expressions.ExpressionBuilder;
import org.eclipse.persistence.expressions.ExpressionOperator;
import org.eclipse.persistence.history.AsOfClause;
import org.eclipse.persistence.internal.databaseaccess.DatabasePlatform;
import org.eclipse.persistence.internal.helper.ClassConstants;
import org.eclipse.persistence.internal.helper.DatabaseField;
import org.eclipse.persistence.internal.helper.DatabaseTable;
import org.eclipse.persistence.internal.helper.NonSynchronizedVector;
import org.eclipse.persistence.internal.queries.ReportItem;
import org.eclipse.persistence.internal.sessions.AbstractRecord;
import org.eclipse.persistence.internal.sessions.AbstractSession;
import org.eclipse.persistence.mappings.DatabaseMapping;
import org.eclipse.persistence.mappings.OneToOneMapping;
import org.eclipse.persistence.mappings.querykeys.ForeignReferenceQueryKey;
import org.eclipse.persistence.mappings.querykeys.QueryKey;
import org.eclipse.persistence.queries.DatabaseQuery;
import org.eclipse.persistence.queries.ReadQuery;
import org.eclipse.persistence.queries.ReportQuery;

/**
 * Used for expressions that have 0 to n children.
 * These include not, between and all functions.
 */
public class FunctionExpression extends BaseExpression {
    protected Vector<Expression> children;
    protected ExpressionOperator operator;
    protected transient ExpressionOperator platformOperator;
    protected Class resultType;

    public FunctionExpression() {
        this.children = org.eclipse.persistence.internal.helper.NonSynchronizedVector.newInstance(2);
        this.resultType = null;
    }

    /**
     * INTERNAL:
     * Return if the expression is equal to the other.
     * This is used to allow dynamic expression's SQL to be cached.
     * This must be over written by each subclass.
     */
    @Override
    public boolean equals(Object object) {
        if (this == object) {
            return true;
        }
        if (!super.equals(object)) {
            return false;
        }
        FunctionExpression expression = (FunctionExpression) object;
        if ((this.operator != expression.getOperator()) && ((this.operator == null) || (!this.operator.equals(expression.getOperator())))) {
            return false;
        }
        List<?> children = getChildren();
        List<?> otherChildren = expression.getChildren();
        int size = children.size();
        if (size != otherChildren.size()) {
            return false;
        }
        for (int index = 0; index < size; index++) {
            if (!children.get(index).equals(otherChildren.get(index))) {
                return false;
            }
        }
        return true;
    }

    /**
     * INTERNAL:
     * Compute a consistent hash-code for the expression.
     * This is used to allow dynamic expression's SQL to be cached.
     */
    @Override
    public int computeHashCode() {
        int hashCode = super.computeHashCode();
        if (this.operator != null) {
            hashCode = hashCode + this.operator.hashCode();
        }
        List<?> children = getChildren();
        int size = children.size();
        for (int index = 0; index < size; index++) {
            hashCode = hashCode + children.get(index).hashCode();
        }
        return hashCode;
    }

    public void addChild(Expression child) {
        getChildren().addElement(child);
    }

    /**
     * INTERNAL:
     * Find the alias for a given table
     */
    @Override
    public DatabaseTable aliasForTable(DatabaseTable table) {
        return getBaseExpression().aliasForTable(table);
    }

    @Override
    public Expression asOf(AsOfClause clause) {
        final AsOfClause finalClause = clause;
        ExpressionIterator iterator = new ExpressionIterator() {
            @Override
            public void iterate(Expression each) {
                if (each.isDataExpression()) {
                    each.asOf(finalClause);
                }
            }

            @Override
            public boolean shouldIterateOverSubSelects() {
                return true;
            }
        };
        iterator.iterateOn(this);
        return this;
    }

    /**
     * INTERNAL:
     */
    @Override
    public Expression create(Expression base, Object singleArgument, ExpressionOperator anOperator) {
        baseExpression = base;
        addChild(base);
        Expression localBase = base;
        if(anOperator.isFunctionOperator()) {
            ExpressionBuilder builder = getBuilder();
            if(builder != null) {
                localBase = builder;
            }
        }
        Expression arg = Expression.from(singleArgument, localBase);
        addChild(arg);
        setOperator(anOperator);
        return this;
    }

    /**
     * INTERNAL:
     * added for Trim support.  TRIM([trim_character FROM] string_primary)
     */
    @Override
    public Expression createWithBaseLast(Expression base, Object singleArgument, ExpressionOperator anOperator) {
        baseExpression = base;
        Expression localBase = base;
        if(anOperator.isFunctionOperator()) {
            ExpressionBuilder builder = getBuilder();
            if(builder != null) {
                localBase = builder;
            }
        }
        Expression arg = Expression.from(singleArgument, localBase);
        addChild(arg);
        addChild(base);
        setOperator(anOperator);
        return this;
    }

    /**
     * INTERNAL:
     */
    @Override
    public Expression create(Expression base, List arguments, ExpressionOperator anOperator) {
        this.baseExpression = base;
        setOperator(anOperator);
        addChild(base);
        Expression localBase = base;
        if (anOperator.isFunctionOperator()) {
            ExpressionBuilder builder = getBuilder();
            if (builder != null) {
                localBase = builder;
            }
        }
        for (Object argument : arguments) {
            Expression arg = Expression.from(argument, localBase);
            addChild(arg);
        }
        return this;
    }

    /**
     * INTERNAL:
     * Used for debug printing.
     */
    @Override
    public String descriptionOfNodeType() {
        return "Function";
    }

    /**
     * INTERNAL:
     * Check if the object conforms to the expression in memory.
     * This is used for in-memory querying.
     * If the expression in not able to determine if the object conform throw a not supported exception.
     */
    @Override
    public boolean doesConform(Object object, AbstractSession session, AbstractRecord translationRow, int valueHolderPolicy, boolean isObjectUnregistered) {
        int selector = this.operator.getSelector();

        // Must check for NOT and negate entire base expression.
        if (selector == ExpressionOperator.Not) {
            return !getBaseExpression().doesConform(object, session, translationRow, valueHolderPolicy, isObjectUnregistered);
        }

        // Conform between or in function.
        if ((selector == ExpressionOperator.Between) || (selector == ExpressionOperator.NotBetween)
                || (selector == ExpressionOperator.In) || (selector == ExpressionOperator.NotIn)
                || (selector == ExpressionOperator.Like) || (selector == ExpressionOperator.Regexp)
                || (selector == ExpressionOperator.NotLike)) {
            // Extract the value from the left side.
            Object leftValue = getBaseExpression().valueFromObject(object, session, translationRow, valueHolderPolicy, isObjectUnregistered);

            // Extract the value from the arguments, skip the first child which is the base.
            int size = this.children.size();
            Vector rightValue = new Vector(size);
            for (int index = 1; index < size; index++) {
                Object valueFromRight;
                Expression child = this.children.get(index);
                if (child instanceof Expression) {
                    valueFromRight = child.valueFromObject(object, session, translationRow, valueHolderPolicy, isObjectUnregistered);
                } else {
                    valueFromRight = child;
                }
                //If valueFromRight is a Vector, then there is only one child other than the base, e.g. valueFromRight is a collection of constants.
                //Then it should be the vector to be compared with.  Don't add it to another collection.
                if (valueFromRight instanceof Vector) {
                    rightValue = (Vector)valueFromRight;
                //Single values should be added to the rightValue, which will be compared with leftValue.
                } else {
                    rightValue.add(valueFromRight);
                }
            }

            // If left is anyof collection of values, check each one.
            // If the right had an anyof not supported will be thrown from the operator.
            if (leftValue instanceof Vector) {
                for (Object tempLeft : (Vector)leftValue) {
                    if (this.operator.doesRelationConform(tempLeft, rightValue)) {
                        return true;
                    }
                }

                // Only return false if none of the values match.
                return false;
            } else {
                return this.operator.doesRelationConform(leftValue, rightValue);
            }
        } else if ((selector == ExpressionOperator.IsNull) || (selector == ExpressionOperator.NotNull)) {
            // Extract the value from the left side.
            Object leftValue = getBaseExpression().valueFromObject(object, session, translationRow, valueHolderPolicy, isObjectUnregistered);

            // If left is anyof collection of values, check each one.
            if (leftValue instanceof Vector) {
                for (Object tempLeft : (Vector)leftValue) {
                    if (this.operator.doesRelationConform(tempLeft, null)) {
                        return true;
                    }
                }

                // Only return false if none of the values match.
                return false;
            } else {
                return this.operator.doesRelationConform(leftValue, null);
            }
        }

        // No other relation functions are supported.
        // Non-relation functions are supported through valueFromObject().
        throw QueryException.cannotConformExpression();
    }

    public Vector<Expression> getChildren() {
        return this.children;
    }

    /**
     * INTERNAL: Not to be confused with the public getField(String)
     * This returns a collection of all fields associated with this object. Really
     * only applies to query keys representing an object or to expression builders.
     *
     */
    @Override
    public Vector getFields() {
        return getBaseExpression().getFields();
    }

    /**
     * INTERNAL:
     */
    @Override
    public List<DatabaseField> getSelectionFields(ReadQuery query) {
        return getBaseExpression().getSelectionFields(query);
    }

    @Override
    public ExpressionOperator getOperator() {
        return operator;
    }

    public ExpressionOperator getPlatformOperator(DatabasePlatform platform) {
        if (platformOperator == null) {
            initializePlatformOperator(platform);
        }
        return platformOperator;
    }

    public Class getResultType() {
        return resultType;
    }

    public boolean hasResultType() {
        return resultType != null;
    }

    /**
     * INTERNAL:
     */
    public void initializePlatformOperator(DatabasePlatform platform) {
        if (this.operator.isComplete()) {
            platformOperator = this.operator;
            return;
        }
        platformOperator = platform.getOperator(this.operator.getSelector());
        if (platformOperator == null) {
            throw QueryException.invalidOperator(this.operator.toString());
        }
    }

    @Override
    public boolean isFunctionExpression() {
        return true;
    }

    /**
     * INTERNAL:
     * Return if the represents an object comparison.
     */
    protected boolean isObjectComparison() {
        int selector = this.operator.getSelector();
        if (((selector != ExpressionOperator.IsNull) && (selector != ExpressionOperator.NotNull)) || (this.children.size() != 1)) {
            if (((selector != ExpressionOperator.InSubQuery) && (selector != ExpressionOperator.NotInSubQuery))
                    || (this.children.size() != 2)) {
                return false;
            }
        }

        Expression base = getBaseExpression();
        //bug 384641 - check that directCollections are not treated as object comparisons
        return (base.isObjectExpression() && (!((ObjectExpression)base).isAttribute()) &&
                !((ObjectExpression)base).isDirectCollection() );
    }

    /**
     * INTERNAL:
     * For iterating using an inner class
     */
    @Override
    public void iterateOn(ExpressionIterator iterator) {
        super.iterateOn(iterator);
        for (Enumeration<Expression> childrenEnum = this.children.elements(); childrenEnum.hasMoreElements();) {
            Expression child = childrenEnum.nextElement();
            child.iterateOn(iterator);
        }
    }

    /**
     * INTERNAL:
     * Normalize into a structure that is printable.
     * Also compute printing information such as outer joins.
     * This checks for object isNull, notNull, in and notIn comparisons.
     */
    @Override
    public Expression normalize(ExpressionNormalizer normalizer) {
        //This method has no validation but we should still make the method call for consistency
        //bug # 2956674
        //validation is moved into normalize to ensure that expressions are valid before we attempt to work with them
        validateNode();
        if (this.children.isEmpty()) {
            return this;
        }

        // Ensure session has been set.
        ExpressionBuilder builder = getBuilder();
        if ((builder != null) && (builder.getSession() == null)) {
            builder.setSession(normalizer.getSession().getRootSession(null));
        }

        if (this.operator.getSelector() == ExpressionOperator.Count) {
            // Attempting to count an Entity and not an attribute.  Need to augment this expression.
            // This is normally normalized in ReportQuery, but can get to here in a having clause.
            prepareObjectAttributeCount(normalizer, null, null, null);
        }

        if (!isObjectComparison()) {
            for (int index = 0; index < this.children.size(); index++) {
            	this.children.set(index, this.children.get(index).normalize(normalizer));
            }
            return this;
        } else {
            //if not normalizing we must still validate the corresponding node to make sure that they are valid
            //bug # 2956674
            for (int index = 0; index < this.children.size(); index++) {
            	this.children.get(index).validateNode();
            }
        }

        // This code is executed only in the case of an is[not]Null, or [not]in on an
        // object attribute.
        ObjectExpression base = (ObjectExpression)getBaseExpression();

        // For cr2334, fix code so that normalize is first called on base expressions.
        // I.e. if base itself had a base expression this expression would not be normalized.
        if (base.getBaseExpression() != null) {
            base.getBaseExpression().normalize(normalizer);
        }

        // Check for IN with objects, "e IN (Select e2 from Employee e2)".
        if ((this.operator.getSelector() == ExpressionOperator.InSubQuery)
                || (this.operator.getSelector() == ExpressionOperator.NotInSubQuery)) {
            // Switch object comparison to compare on primary key.
            if (this.children.size() != 2) {
                throw QueryException.invalidExpression(this);
            }
            // Check if the left is for a 1-1 mapping, then optimize to compare on foreign key to avoid join.
            DatabaseMapping mapping = null;
            if (base.isQueryKeyExpression()) {
                mapping = base.getMapping();
            }
            List<DatabaseField> sourceFields = null;
            List<DatabaseField> targetFields = null;
            if ((mapping != null) && mapping.isOneToOneMapping()
                    && (!((OneToOneMapping)mapping).hasRelationTableMechanism())
                    && (!((OneToOneMapping)mapping).hasCustomSelectionQuery())) {
                base = (ObjectExpression)base.getBaseExpression();
                Map<DatabaseField, DatabaseField> targetToSourceKeyFields = ((OneToOneMapping)mapping).getTargetToSourceKeyFields();
                sourceFields = new ArrayList(targetToSourceKeyFields.size());
                targetFields = new ArrayList(targetToSourceKeyFields.size());
                for (Map.Entry<DatabaseField, DatabaseField> entry : targetToSourceKeyFields.entrySet()) {
                    sourceFields.add(entry.getValue());
                    targetFields.add(entry.getKey());
                }
            } else {
                mapping = null;
                sourceFields = base.getDescriptor().getPrimaryKeyFields();
                targetFields = sourceFields;
            }
            if (sourceFields.size() != 1) {
                base = (ObjectExpression)getBaseExpression();
                // For composite ids an exists and subselect is used.
                SubSelectExpression subSelectExp = (SubSelectExpression)this.children.get(1);
                ReportQuery subQuery = subSelectExp.getSubQuery();

                // some db (derby) require that in EXIST(SELECT...) subquery returns a single column
                subQuery.getItems().clear();
                subQuery.addItem("one", new ConstantExpression(Integer.valueOf(1), subQuery.getExpressionBuilder()));

                Expression subSelectCriteria = subQuery.getSelectionCriteria();
                ExpressionBuilder subBuilder = subQuery.getExpressionBuilder();
                Expression newExp;
                // Any or Some
                if (this.operator.getSelector() == ExpressionOperator.InSubQuery) {
                    subSelectCriteria = subBuilder.equal(base).and(subSelectCriteria);
                } else {
                    subSelectCriteria = subBuilder.notEqual(base).and(subSelectCriteria);
                }
                subQuery.setSelectionCriteria(subSelectCriteria);
                newExp = builder.exists(subQuery);
                return newExp.normalize(normalizer);
            }
            Expression newBase = base.getField(sourceFields.get(0));
            setBaseExpression(newBase);
            this.children.set(0, newBase);
            Expression right = this.children.get(1);
            if (right.isSubSelectExpression()) {
                // Check for sub-select, need to replace sub-selects on object to select its id.
                ReportQuery query = ((SubSelectExpression)right).getSubQuery();
                if (query.getItems().size() != 1) {
                    throw QueryException.invalidExpression(this);
                }
                ReportItem item = query.getItems().get(0);
                item.setAttributeExpression(item.getAttributeExpression().getField(targetFields.get(0)));
            } else {
                throw QueryException.invalidExpression(this);
            }
            // Still need to normalize the children.
            for (int index = 0; index < this.children.size(); index++) {
                this.children.set(index, this.children.get(index).normalize(normalizer));
            }
            return this;
        }
        // else isNull/notNull

        Expression foreignKeyJoin = null;
        if (base.getMapping() == null) {
            // Is an expression builder, transform to a null primary key expression.
            foreignKeyJoin = base.getDescriptor().getObjectBuilder().buildPrimaryKeyExpressionFromKeys(null, getSession());
            foreignKeyJoin = foreignKeyJoin.rebuildOn(base);
        } else {
            // Switch to null foreign key comparison (i.e. get('c').isNull() to getField('C_ID').isNull()).
            // For bug 3105559 also must handle aggregates: get("period").isNull();
            foreignKeyJoin = base.getMapping().buildObjectJoinExpression(base, (Object)null, getSession());
        }

        if (this.operator.getSelector() == ExpressionOperator.NotNull) {
            foreignKeyJoin = foreignKeyJoin.not();
        }
        return foreignKeyJoin.normalize(normalizer);
    }

    /**
     * INTERNAL:
     * Used for cloning.
     */
    @Override
    protected void postCopyIn(Map alreadyDone) {
        super.postCopyIn(alreadyDone);
        Vector<Expression> oldChildren = this.children;
        this.children = org.eclipse.persistence.internal.helper.NonSynchronizedVector.newInstance();
        for (int i = 0; i < oldChildren.size(); i++) {
        	addChild((oldChildren.elementAt(i).copiedVersionFrom(alreadyDone)));
        }
    }

    /**
     * INTERNAL:
     * Print SQL using the operator.
     */
    @Override
    public void printSQL(ExpressionSQLPrinter printer) {
        // If all children are parameters, some databases don't allow binding.
        if (printer.getPlatform().isDynamicSQLRequiredForFunctions() && !this.children.isEmpty()) {
            boolean allParams = true;
            for (Iterator<Expression> iterator = this.children.iterator(); iterator.hasNext(); ) {
                Expression child = iterator.next();
                if (!(child.isParameterExpression() || child.isConstantExpression())) {
                    allParams = false;
                }
            }
            if (allParams) {
                printer.getCall().setUsesBinding(false);
            }
        }
        ExpressionOperator realOperator;
        realOperator = getPlatformOperator(printer.getPlatform());
        realOperator.printCollection(this.children, printer);
    }

    /**
     * INTERNAL:
     * Print java for project class generation
     */
    @Override
    public void printJava(ExpressionJavaPrinter printer) {
        ExpressionOperator realOperator = getPlatformOperator(printer.getPlatform());
        realOperator.printJavaCollection(this.children, printer);
    }

    /**
     * INTERNAL:
     * This expression is built on a different base than the one we want. Rebuild it and
     * return the root of the new tree
     */
    @Override
    public Expression rebuildOn(Expression newBase) {
        Expression newLocalBase = getBaseExpression().rebuildOn(newBase);
        Vector newChildren = org.eclipse.persistence.internal.helper.NonSynchronizedVector.newInstance(this.children.size());
        for (int i = 1; i < this.children.size(); i++) {// Skip the first one, since it's also the base
            newChildren.addElement(this.children.elementAt(i).rebuildOn(newBase));
        }
        newLocalBase.setSelectIfOrderedBy(getBaseExpression().selectIfOrderedBy());
        FunctionExpression rebuilt = (FunctionExpression) newLocalBase.performOperator(this.operator, newChildren);
        rebuilt.setResultType(this.getResultType()); //copy over result type.
        return rebuilt;
    }

    /**
     * INTERNAL:
     * Search the tree for any expressions (like SubSelectExpressions) that have been
     * built using a builder that is not attached to the query.  This happens in case of an Exists
     * call using a new ExpressionBuilder().  This builder needs to be replaced with one from the query.
     */
    @Override
    public void resetPlaceHolderBuilder(ExpressionBuilder queryBuilder){
        getBaseExpression().resetPlaceHolderBuilder(queryBuilder);
        for (int i = this.children.size()-1; i > 0; i--) {// Skip the first one, since it's also the base
            this.children.elementAt(i).resetPlaceHolderBuilder(queryBuilder);
        }
    }
    // Set the local base expression, ie the one on the other side of the operator
    // Most types will ignore this, since they don't need it.
    @Override
    public void setLocalBase(Expression exp) {
        getBaseExpression().setLocalBase(exp);
    }

    public void setOperator(ExpressionOperator theOperator) {
        operator = theOperator;
    }

    public void setResultType(Class resultType) {
        this.resultType = resultType;
    }

    /**
     * INTERNAL:
     * Rebuild myself against the base, with the values of parameters supplied by the context
     * expression. This is used for transforming a standalone expression (e.g. the join criteria of a mapping)
     * into part of some larger expression. You normally would not call this directly, instead calling twist
     * See the comment there for more details"
     */
    @Override
    public Expression twistedForBaseAndContext(Expression newBase, Expression context, Expression oldBase) {
        if (this.children.isEmpty()) {
            return (Expression)clone();
        }
        Vector newChildren = org.eclipse.persistence.internal.helper.NonSynchronizedVector.newInstance(this.children.size());

        // For functions the base is the first child, we only want the arguments so start at the second.
        for (int index = 1; index < this.children.size(); index++) {
            newChildren.addElement(this.children.elementAt(index).twistedForBaseAndContext(newBase, context, oldBase));
        }

        // Aply the function to the twisted old base.
        Expression oldBaseExp = this.children.elementAt(0);
        return oldBaseExp.twistedForBaseAndContext(newBase, context, oldBase).performOperator(this.operator, newChildren);
    }

    /**
     * INTERNAL:
     * Return the value for in memory comparison.
     * This is only valid for valueable expressions.
     */
    @Override
    public Object valueFromObject(Object object, AbstractSession session, AbstractRecord translationRow, int valueHolderPolicy, boolean isObjectUnregistered) {
        Object baseValue = getBaseExpression().valueFromObject(object, session, translationRow, valueHolderPolicy, isObjectUnregistered);
        Vector arguments = org.eclipse.persistence.internal.helper.NonSynchronizedVector.newInstance(this.children.size());
        for (int index = 1; index < this.children.size(); index++) {
            if (this.children.elementAt(index) instanceof Expression) {
                arguments.addElement((this.children.elementAt(index)).valueFromObject(object, session, translationRow, valueHolderPolicy, isObjectUnregistered));
            } else {
                arguments.addElement(this.children.elementAt(index));
            }
        }
        if (baseValue instanceof Vector) {// baseValue might be a vector, so the individual values must be extracted before applying the function call to them
            Vector baseVector = new Vector();
            for (Enumeration valuesToCompare = ((Vector)baseValue).elements();
                     valuesToCompare.hasMoreElements();) {
                Object baseObject = valuesToCompare.nextElement();
                if (baseObject == null) {
                    baseVector.addElement(null);
                } else {
                    baseVector.addElement(this.operator.applyFunction(baseObject, arguments));
                }
            }
            return baseVector;
        } else {
            // Do not apply functions to null, just leave as null.
            if (baseValue == null) {
                return null;
            } else {
                return this.operator.applyFunction(baseValue, arguments);
            }
        }
    }

    /**
     * INTERNAL:
     * Used to print a debug form of the expression tree.
     */
    @Override
    public void writeDescriptionOn(BufferedWriter writer) throws IOException {
        writer.write(operator.toString());
    }

    /**
     * INTERNAL: called from SQLSelectStatement.writeFieldsFromExpression(...)
     */
    @Override
    public void writeFields(ExpressionSQLPrinter printer, Vector newFields, SQLSelectStatement statement) {
        //print ", " before each selected field except the first one
        if (printer.isFirstElementPrinted()) {
            printer.printString(", ");
        } else {
            printer.setIsFirstElementPrinted(true);
        }

        if (getBaseExpression().isDataExpression()) {
            DatabaseField field = ((DataExpression)getBaseExpression()).getField();

            if (field == null) {
                // This means the select wants a *.
                field = new DatabaseField("*");
            } else {
                // Clone the field since we will change its type.
                field = field.clone();
            }

            // If the result type is set, use it.
            field.setSqlType(DatabaseField.NULL_SQL_TYPE);
            //we also cache the JDBC type now so reset it as well.
            if (hasResultType()) {
                field.setType(getResultType());
            } else {
                // If the function is anything but min or max, null out the
                // field type. The type will be calculated based on the
                // function.
                int selector = this.operator.getSelector();
                if (selector != ExpressionOperator.Maximum && selector != ExpressionOperator.Minimum) {
                    field.setType(null);
                }
            }

            newFields.addElement(field);
        } else {
            // This field is a complex function value so any name can be used.
            DatabaseField field = new DatabaseField("*");
            // If the result type is set, use it.
            field.setSqlType(DatabaseField.NULL_SQL_TYPE);
            field.setType(getResultType());
            newFields.addElement(field);
        }

        printSQL(printer);
    }

    /**
     * INTERNAL:
     * Used in SQL printing.
     */
    @Override
    public void writeSubexpressionsTo(BufferedWriter writer, int indent) throws IOException {
        if (baseExpression != null) {
            baseExpression.toString(writer, indent);
        }
    }

    /**
     * INTERNAL:
     * JPQL allows count([distinct] e), where e can be an object, not just a single field,
     * however the database only allows a single field, so object needs to be translated to a single field.
     * If the descriptor has a single pk, it is used, otherwise any pk is used if distinct, otherwise a subselect is used.
     * If the object was obtained through an outer join, then the subselect also will not work, so an error is thrown.
     */
    public void prepareObjectAttributeCount(ExpressionNormalizer normalizer, ReportItem item, ReportQuery query, Map clonedExpressions) {
        // ** Note that any of the arguments may be null depending on the caller.
        if (getOperator().getSelector() == ExpressionOperator.Count) {
            Expression baseExp = getBaseExpression();
            boolean distinctUsed = false;
            if (baseExp.isFunctionExpression() && (((FunctionExpression)baseExp).getOperator().getSelector() == ExpressionOperator.Distinct)) {
                distinctUsed = true;
                baseExp = ((FunctionExpression)baseExp).getBaseExpression();
            }
            boolean outerJoin = false;
            ClassDescriptor newDescriptor = null;
            AbstractSession session = null;
            if (query != null) {
                session = query.getSession();
            } else {
                session = normalizer.getSession();
            }
            if (baseExp.isQueryKeyExpression()) {
                // now need to find out if it is a direct to field or something else.
                ClassDescriptor descriptor = null;
                if (query == null) {
                    descriptor = ((QueryKeyExpression) baseExp).getDescriptor();
                } else {
                    descriptor = query.getDescriptor();
                }
                DatabaseMapping mapping = baseExp.getLeafMapping(query, descriptor, session);
                if ((mapping != null) && !mapping.isAbstractDirectMapping()) {
                    outerJoin = ((QueryKeyExpression)baseExp).shouldUseOuterJoin();
                    if (mapping.isAggregateMapping()){
                        newDescriptor = mapping.getDescriptor();
                        baseExp = ((QueryKeyExpression)baseExp).getBaseExpression();
                    } else {
                        newDescriptor = mapping.getReferenceDescriptor();
                    }
                } else {
                    QueryKey queryKey = getLeafQueryKeyFor(query, baseExp, descriptor, session);
                    if ((queryKey != null) && queryKey.isForeignReferenceQueryKey()){
                        outerJoin = ((QueryKeyExpression) baseExp).shouldUseOuterJoin();
                        newDescriptor = session.getDescriptor(((ForeignReferenceQueryKey)queryKey).getReferenceClass());
                    }
                }
            } else if (baseExp.isExpressionBuilder()) {
                if (((ExpressionBuilder)baseExp).getQueryClass() == null) {
                    if (item != null) {
                        item.setResultType(ClassConstants.INTEGER);
                    }
                } else {
                    newDescriptor = session.getDescriptor(((ExpressionBuilder)baseExp).getQueryClass());
                }
            }

            if (newDescriptor != null) {
                // At this point we are committed to rewriting the query.
                if ((newDescriptor.getPrimaryKeyFields().size() == 1) || !distinctUsed) {
                    // case 1: single PK =>
                    // treat COUNT(entity) as COUNT(entity.pk)
                    Expression countArg = baseExp.getField(newDescriptor.getPrimaryKeyFields().get(0));
                    if (distinctUsed) {
                        countArg = countArg.distinct();
                    }
                    setBaseExpression(countArg);
                    getChildren().set(0, countArg);
                } else if (((DatabasePlatform)session.getPlatform(newDescriptor.getJavaClass())).supportsCountDistinctWithMultipleFields()) {
                    // case 3, is database allows multiple fields, then just print them
                    // treat COUNT(distinct entity) as COUNT(distinct entity.pk1, entity.pk2)
                    List args = new ArrayList(newDescriptor.getPrimaryKeyFields().size());
                    Expression firstField = null;
                    for (DatabaseField field : newDescriptor.getPrimaryKeyFields()) {
                        if (firstField == null) {
                            firstField = baseExp.getField(field);
                        } else {
                            args.add(baseExp.getField(field));
                        }
                    }

                    ExpressionOperator anOperator = new ExpressionOperator();
                    anOperator.setType(ExpressionOperator.FunctionOperator);
                    Vector v = NonSynchronizedVector.newInstance(args.size());
                    v.addElement("DISTINCT ");
                    for (int index = 0; index < args.size(); index++) {
                        v.add(", ");
                    }
                    v.add("");
                    anOperator.printsAs(v);
                    anOperator.bePrefix();
                    anOperator.setNodeClass(ClassConstants.FunctionExpression_Class);
                    Expression distinctFunction = anOperator.expressionForArguments(firstField, args);

                    setBaseExpression(distinctFunction);
                    getChildren().set(0, distinctFunction);
                } else if (!outerJoin && (query != null)) {
                    // case 4: composite PK and DISTINCT, but no
                    // outer join => previous solution using
                    // COUNT(*) and EXISTS subquery
                    // TODO, this doesn't really work for most cases (joins, other things selected, group by),
                    // this should probably be removed and throw an error,
                    // or changed to just concat all the pks together.

                    // If this is a subselect baseExp is yet uncloned,
                    // and will miss out if moved now from items into a selection criteria.
                    if (clonedExpressions != null) {
                        if (clonedExpressions.get(baseExp.getBuilder()) != null) {
                            baseExp = baseExp.copiedVersionFrom(clonedExpressions);
                        } else {
                            baseExp = baseExp.rebuildOn(query.getExpressionBuilder());
                        }
                    }

                    // Now the reference class of the query needs to be reversed.
                    // See the bug description for an explanation.
                    ExpressionBuilder countBuilder = baseExp.getBuilder();
                    ExpressionBuilder outerBuilder ;

                    ReportQuery subSelect = new ReportQuery(query.getReferenceClass(), countBuilder);
                    query.getSession().getPlatform().retrieveFirstPrimaryKeyOrOne(subSelect);

                    // Make sure the outerBuilder does not appear on the left of the subselect.
                    // Putting a builder on the left is desirable to trigger an optimization.
                    if (query.getSelectionCriteria() != null) {
                        outerBuilder = new ExpressionBuilder(newDescriptor.getJavaClass());
                        query.setExpressionBuilder(outerBuilder);
                        subSelect.setSelectionCriteria(baseExp.equal(outerBuilder).and(query.getSelectionCriteria()));
                    } else {
                        outerBuilder = new ExpressionBuilder(newDescriptor.getJavaClass());
                        query.setExpressionBuilder(outerBuilder);
                        subSelect.setSelectionCriteria(baseExp.equal(outerBuilder));
                    }
                    query.setNonFetchJoinAttributeExpressions(null);
                    query.setSelectionCriteria(outerBuilder.exists(subSelect));
                    setBaseExpression(outerBuilder);
                    getChildren().set(0, outerBuilder);
                    query.setReferenceClass(newDescriptor.getJavaClass());
                    query.changeDescriptor(session);
                } else {
                    // case 4: composite PK, DISTINCT, outer join =>
                    // not supported, throw exception
                    DatabaseQuery reportQuery = query;
                    if (query == null) {
                        reportQuery = normalizer.getStatement().getQuery();
                    }
                    throw QueryException.distinctCountOnOuterJoinedCompositePK(newDescriptor, reportQuery);
                }
            }
        }
    }

    /**
     * INTERNAL:
     * Lookup the query key for this item.
     * If an aggregate of foreign mapping is found it is traversed.
     */
    protected QueryKey getLeafQueryKeyFor(DatabaseQuery query, Expression expression, ClassDescriptor rootDescriptor, AbstractSession session) throws QueryException {
        // Check for database field expressions or place holder
        if ((expression == null) || (expression.isFieldExpression())) {
            return null;
        }

        if (!(expression.isQueryKeyExpression())) {
            return null;
        }

        QueryKeyExpression qkExpression = (QueryKeyExpression)expression;
        Expression baseExpression = qkExpression.getBaseExpression();

        ClassDescriptor descriptor = baseExpression.getLeafDescriptor(query, rootDescriptor, session);
        return descriptor.getQueryKeyNamed(qkExpression.getName());
    }

    protected DatabaseMapping getMappingOfFirstPrimaryKey(ClassDescriptor descriptor) {
        if (descriptor != null) {
            for (Iterator i = descriptor.getMappings().iterator(); i.hasNext(); ) {
                DatabaseMapping m = (DatabaseMapping)i.next();
                if (m.isPrimaryKeyMapping()) {
                    return m;
                }
            }
        }
        return null;
    }

    /**
     * INTERNAL:
     * Lookup the mapping for this item by traversing its expression recursively.
     */
    @Override
    public DatabaseMapping getLeafMapping(DatabaseQuery query, ClassDescriptor rootDescriptor, AbstractSession session) {
        int selector = this.operator.getSelector();

        //MAX and MIN functions require mappings for their result value. See JPA 2.1; section 4.8.5
        if (this.baseExpression != null && ((selector == ExpressionOperator.Maximum) || (selector == ExpressionOperator.Minimum))) {
            return this.baseExpression.getLeafMapping(query, rootDescriptor, session);
        }
        return super.getLeafMapping(query, rootDescriptor, session);
    }
}
