Source: core/criteria.js


var Montage = require("./core").Montage;

var parse = require("frb/parse"),
    stringify = require("frb/stringify"),
    evaluate = require("frb/evaluate"),
    precedence = require("frb/language").precedence,
    Scope = require("frb/scope"),
    compile = require("frb/compile-evaluator");

var Criteria = exports.Criteria = Montage.specialize({
    _expression: {
        value: null
    },

    /**
     * returns Criteria's expression, which is not expected to change after being
     * initialized
     *
     * @type {string}
     */
    expression: {
        get: function() {
            return this._expression;
        }
    },
    /**
     * @type {object}
     */
    parameters: {
        value: null
    },
    /**
     * @private
     * @type {object}
     */
    _syntax: {
        value: null
    },
    /**
     * The parsed expression, a syntactic tree.
     *
     * @type {object}
     */
   syntax: {
        get: function() {
            return this._syntax || (this._syntax = parse(this._expression));
        }
    },
    _compiledSyntax: {
        value: null
    },
    /**
     * The compiled expression, a function, that is used directly for evaluation.
     *
     * @type {function}
     */
   compiledSyntax: {
        get: function() {
            return this._compiledSyntax || (this._compiledSyntax = compile(this.syntax));
        }
    },

    /**
     * Initialize a Criteria with a compiled syntax.
     *
     * @method
     * @returns {Criteria} - The Criteria initialized.
     */
    initWithSyntax: {
        value: function (syntax, parameters) {
            this._syntax = syntax;
            this.parameters = parameters;
            return this;
        }
    },

    /**
     * Initialize a Criteria with an expression as string representation
     *
     * for example expression: "(firstName= $firstName) && (lastName = $lastName)"
     *             parameters: {
     *                  "firstName": "Han",
     *                  "lastName": "Solo"
     *             }
     *
     * @method
     * @argument {string} expression - A string representaton of the criteria
     *                                  expected to be a valid Montage expression.
     * @argument {object} parameters - Optional object containing value for an expressions' prameters
     *
     * @returns {Criteria} - The Criteria initialized.
     */
    initWithExpression: {
        value: function (expression,parameters) {
            this._expression = expression;
            this.parameters = parameters;
            return this;
        }
    },

    /**
     * Backward compatibility with selector.js
     *
     * @type {function}
     */
   initWithPath: {
        value: function (path) {
            return this.initWithExpression(path);
        }
    },


    criteriaWithParameters: {
        value: function (parameters) {
            var clone = (new this.constructor).initWithExpression(this.expression);
            clone.parameters = parameters;
            return clone;
        }
    },

    serializeSelf: {
        value: function (serializer) {
            serializer.setProperty("expression", this._expression || (this._expression = stringify(this.syntax)));
            serializer.setProperty("parameters", this.parameters);
        }
    },

    deserializeSelf: {
        value: function (deserializer) {
            var value;
            value = deserializer.getProperty("expression") || deserializer.getProperty("path");
            if (value !== void 0) {
                this._expression = value;
            }
            value = deserializer.getProperty("parameters");
            if (value !== void 0) {
                this.parameters = value;
            }
        }
    },
    __scope: {
        value: null
    },
    _scope: {
        get: function() {
            return this.__scope || (this.__scope = new Scope());
        }
    },
    evaluate: {
        value: function (value, parameters) {
            this._scope.parameters = parameters || this.parameters;
            this._scope.value = value;
            return this.compiledSyntax(this._scope);
        }
    }

},{
    forObjectsLike: {
        value: function(object) {
            var properties = Object.keys(object),
                expression = "",
                i, iKey, iValue,
                j, jValue, jExpression, jKey;

            for(i=0;(iKey = properties[i]);i++) {
                iValue = object[iKey];
                if(Array.isArray(iValue)) {
                    jExpression = "";

                    for(j=0;(jValue = iValue[j]);j++) {
                        jKey = iKey;
                        jKey += j;

                        if(jExpression.length > 0) {
                            jExpression += " && ";
                        }
                        jExpression += iKey;
                        jExpression += ".has($";
                        jExpression += jKey;
                        jExpression += ")";

                        //Now alias the value on object;
                        object[jKey] = jValue;
                    }

                    if(expression.length > 0) {
                        expression += " && ";
                    }
                    expression += jExpression;
                }
                else {
                    if(expression.length > 0) {
                        expression += " && ";
                    }

                    expression += iKey;
                    expression += "== $";
                    expression += iKey;
                }
            }

            return (new this).initWithExpression(expression,object);
        }
    },

    withExpression: {
        value: function(expression,parameters) {
            return (new this).initWithExpression(expression,parameters);
        }
    },
    withSyntax: {
        value: function(syntax,parameters) {
            return (new this).initWithSyntax(syntax,parameters);
        }
    }

});

// generate methods on Criteria for each of the tokens of the language.
// support invocation both as class and instance methods like
// Selector.and("a", "b") and aSelector.and("b")
precedence.forEach(function (value,type, precedence) {
    Montage.defineProperty(Criteria.prototype, type, {
        value: function () {
            var args = Array.prototype.map.call(arguments, function (argument) {
                if (typeof argument === "string") {
                    return parse(argument);
                } else if (argument.syntax) {
                    return argument.syntax;
                } else if (typeof argument === "object") {
                    return argument;
                }
            });
            // invoked as instance method
            return new (this.constructor)().initWithSyntax({
                type: type,
                args: [this.syntax].concat(args)
            });
        }
    });
    Montage.defineProperty(Criteria, type, {
        value: function () {
            var args = Array.prototype.map.call(arguments, function (argument) {
                if (typeof argument === "string") {
                    return parse(argument);
                } else if (argument.syntax) {
                    return argument.syntax;
                } else if (typeof argument === "object") {
                    return argument;
                }
            });
            // invoked as class method
            return new this().initWithSyntax({
                type: type,
                args: args
            });
        }
    });
});