Source: data/converter/raw-property-value-to-object-converter.js

var Converter = require("core/converter/converter").Converter,
    Criteria = require("core/criteria").Criteria,
    DataQuery = require("data/model/data-query").DataQuery,
    ObjectDescriptorReference = require("core/meta/object-descriptor-reference").ObjectDescriptorReference,
    Promise = require("core/promise").Promise,
    Scope = require("frb/scope"),
    parse = require("frb/parse");

/**
 * @class RawPropertyValueToObjectConverter
 * @classdesc Converts a property value of raw data to the referenced object.
 * @extends Converter
 */
exports.RawPropertyValueToObjectConverter = Converter.specialize( /** @lends RawPropertyValueToObjectConverter# */ {


    /*********************************************************************
     * Serialization
     */

    serializeSelf: {
        value: function (serializer) {

            serializer.setProperty("convertExpression", this.convertExpression);

            serializer.setProperty("foreignDescriptor", this._foreignDescriptorReference);

            serializer.setProperty("revertExpression", this.revertExpression);

            serializer.setProperty("root", this.owner);

            serializer.setProperty("serviceIdentifier", this.serviceIdentifier);
            serializer.setProperty("service", this.service);

        }
    },

    deserializeSelf: {
        value: function (deserializer) {
            var value = deserializer.getProperty("convertExpression");
            if (value) {
                this.convertExpression = value;
            }

            value = deserializer.getProperty("revertExpression");
            if (value) {
                this.revertExpression = value;
            }

            value = deserializer.getProperty("foreignDescriptor");
            if (value) {
                this._foreignDescriptorReference = value;
            }

            value = deserializer.getProperty("service");
            if (value) {
                this.service = value;
            }

            value = deserializer.getObjectByLabel("root");
            if (value) {
                this.owner = value;
            }

            value = deserializer.getProperty("serviceIdentifier");
            if (value) {
                this.serviceIdentifier = value;
            }

            deserializer.deserializeUnit("bindings");
        }
    },

    /*********************************************************************
     * Initialization
     */

    /**
     * @param {string} convertExpression the expression to be used for building a criteria to obtain the object corresponding to the value to convert.
     * @return itself
     */
    initWithConvertExpression: {
        value: function (convertExpression) {
            this.convertExpression = convertExpression;
            return this;
        }
    },

    /*********************************************************************
     * Properties
     */


    _convertExpression: {
        value: null
    },

    /**
     * The expression used to convert a raw value into a modeled one, for example a foreign property value into the objet it represents.
     * @type {string}
     * */
    convertExpression: {
        get: function() {
            return this._convertExpression;
        },
        set: function(value) {
            if(value !== this._convertExpression) {
                this._convertExpression = value;
                this._convertSyntax = undefined;
            }
        }
    },

    _convertSyntax: {
        value: undefined
    },

    /**
     * Object created by parsing .convertExpression using frb/grammar.js that will
     * be used to initialize the convert query criteria
     * @type {Object}
     * */

    convertSyntax: {
        get: function() {
            return this._convertSyntax || (this._convertSyntax = parse(this.convertExpression));
        }
    },




    _revertExpression: {
        value: null
    },

    /**
     * The expression used to revert the modeled value into a raw one. For example,
     * reverting an object into it's primary key.
     * @type {string}
     * */
    revertExpression: {
        get: function() {
            return this._revertExpression;
        },
        set: function(value) {
            if(value !== this._revertExpression) {
                this._revertExpression = value;
                this._revertSyntax = undefined;
            }
        }
    },

    _revertSyntax: {
        value: undefined
    },

    /**
     * Object created by parsing .revertExpression using frb/grammar.js that will
     * be used to revert the modeled value into a raw one
     * @type {Object}
     * */
    revertSyntax: {
        get: function() {
            return this._revertSyntax || (this._revertSyntax = parse(this.revertExpression));
        }
    },


    /**
     * The descriptor of the destination object. If one is not provided,
     * .objectDescriptor will be used. If .objectDescriptor is not provided,
     * the value descriptor of the property descriptor that defines the
     * relationship will be used.
     * @type {?ObjectDescriptorReference}
     * */
    foreignDescriptor: {
        serializable: false,
        get: function () {
            return this._foreignDescriptorReference && this._foreignDescriptorReference.promise(require);
        },
        set: function (descriptor) {
            this._foreignDescriptorReference = new ObjectDescriptorReference().initWithValue(descriptor);
        }
    },

    /**
     * The descriptor of the source object. It will be used only if it is provided and
     * .foreignDescriptor is not provided.
     * @type {?ObjectDescriptorReference}
     **/
    objectDescriptor: {
        get: function () {
            return  this._objectDescriptor                      ?   Promise.resolve(this._objectDescriptor) :
                    this.owner && this.owner.objectDescriptor   ?   Promise.resolve(this.owner.objectDescriptor) :
                    this.owner && this.owner instanceof Promise ?   this._objectDescriptorReference :
                                                                    undefined;
        },
        set: function (value) {
            this._objectDescriptor = value;
        }
    },

    _objectDescriptorReference: {
        get: function () {
            var self = this;
            return this.owner.then(function (object) {
                var objectDescriptor = object.objectDescriptor;
                self.objectDescriptor = objectDescriptor;
                return objectDescriptor;
            });
        }
    },


    /**
     * The descriptor for which to perform the fetch.
     * This returns foreignDescriptor, if it exists, and otherwise
     * returns objectDescriptor.
     * @type {?ObjectDescriptorReference}
     **/
    _descriptorToFetch: {
        get: function () {
            if (!this.__descriptorToFetch) {
                var self = this;
                this.__descriptorToFetch = this.foreignDescriptor ? this.foreignDescriptor.then(function (descriptor) {
                    return descriptor || self.objectDescriptor;
                }) : Promise.resolve(this.objectDescriptor);
            }
            return this.__descriptorToFetch;
        }
    },


    owner: {
        get: function () {
            return this._owner ? this._owner.then ? this._owner : Promise.resolve(this._owner) : undefined;
        },
        set: function (value) {
            this._owner = value;
        }
    },

    __scope: {
        value: null
    },

    /**
     * Scope with which convert and revert expressions are evaluated.
     * @type {?Scope}
     **/
    scope: {
        get: function() {
            return this.__scope || (this.__scope = new Scope(this));
        }
    },

    /**
     * The service to use to make requests.
     */
    service: {
        get: function () {
            return  this._service ? this._service :
                    this.owner    ? this.owner.then(function (object) { return object.service; }) :
                                                       undefined;
        },
        set: function (value) {
            this._service = !value || value.then ? value : Promise.resolve(value);
        }
    },

    /**
     * Identifier of the child of .service that the query should be routed to
     */
    serviceIdentifier: {
        value: undefined
    },

    /*********************************************************************
     * Public API
     */

    /**
     * Converts the fault for the relationship to an actual object that has an ObjectDescriptor.
     * @function
     * @param {Property} v The value to format.
     * @returns {Promise} A promise for the referenced object.  The promise is
     * fulfilled after the object is successfully fetched.
     */
    convert: {
        value: function (v) {
            var self = this,
                criteria = new Criteria().initWithSyntax(self.convertSyntax, v),
                query;

            return this._descriptorToFetch.then(function (typeToFetch) {
                var type = [typeToFetch.module.id, typeToFetch.name].join("/");

                if (self.serviceIdentifier) {
                    criteria.parameters.serviceIdentifier = self.serviceIdentifier;
                }

                query = DataQuery.withTypeAndCriteria(type, criteria);

                return self.service ? self.service.then(function (service) {
                    return service.rootService.fetchData(query);
                }) : null;
            });
        }
    },



    /**
     * Reverts the relationship back to raw data.
     * @function
     * @param {Scope} v The value to revert.
     * @returns {string} v
     */
    revert: {
        value: function (v) {
            if (v) {
                if (!this._revertSyntax) {
                    return Promise.resolve(v);
                } else {
                    var scope = this.scope;
                    //Parameter is what is accessed as $ in expressions
                    scope.parameters = v;
                    return Promise.resolve(this._revertSyntax(scope));
                }

            }
            return Promise.resolve(undefined);
        }
    }

});