CocktailJS/cocktail

View on GitHub
lib/processor/annotation/Merge.js

Summary

Maintainability
A
35 mins
Test Coverage
/*
 *
 * Copyright (c) 2013 - 2016 Maximiliano Fierro
 * Licensed under the MIT license.
 */
'use strict';

var sequence = require('../sequence');

var _STRATEGIES_ = {
    'single'     : '_mergeMine',
    'mine'       : '_mergeMine',
    'their'      : '_mergeTheir',
    'deep-mine'  : '_mergeDeepMine',
    'deep-their' : '_mergeDeepTheir',
    'properties' : '_mergeOnlyProperties'
};

/**
 * @constructor
 */
function Merge (options) {
    var useProto;
    if (options) {
        useProto = options.usePrototypeWhenSubjectIsClass;
        this._usePrototypeWhenSubjectIsClass = (useProto === false) ? useProto : true;
    }
}

Merge.prototype = {
    retain   : false,
    priority : sequence.MERGE,
    name     : '@merge',

    _parameter   : undefined,

    _usePrototypeWhenSubjectIsClass: true,


    setParameter: function (value) {
        this._parameter = value;
    },

    getParameter: function () {
        return this._parameter;
    },

    _doMerge : function (mine, their, method) {
        var key;

        for (key in their) {
            if (their.hasOwnProperty(key)) {
                method.call(this, key);
            }
        }
    },

    /**
     * mine merge strategy: mine params over their. If params is already defined it gets overriden.
     */
    _mergeMine : function (mine, their) {
        this._doMerge(mine, their, function(k){
            mine[k] = their[k];
        });

        return mine;
    },

    _mergeOnlyProperties : function (mine, their) {
        this._doMerge(mine, their, function(k){
            if (typeof their[k] !== 'function'){
                mine[k] = their[k];
            }
        });

        return mine;
    },

     /**
     * deepMine merge strategy: mine params over their.
     * If params is already defined and it is an object it is merged with strategy mine,
     * if params is already defined and it is an array it is concatenated,
     * otherwise it gets overriden with mine.
     */
     _mergeDeepMine : function (mine, their) {
        return this._mergeDeep(mine, their, '_mergeMine');
    },

    /**
     * their merge strategy: their params over mine. If params is already defined it doesn't get overriden.
     */
    _mergeTheir : function (mine, their) {
        this._doMerge(mine, their, function(k){
            if (mine[k] === undefined) {
                mine[k] = their[k];
            }
        });

        return mine;
    },


     /**
     * deepMine merge strategy: their params over mine.
     * If params is already defined and it is an object it is merged with strategy their,
     * if params is already defined and it is an array it is concatenated,
     * otherwise it gets overriden with mine.
     */
     _mergeDeepTheir : function (mine, their) {
        return this._mergeDeep(mine, their, '_mergeTheir');
    },

    /**
     * runs the deep merge using the given strategy
     */
    _mergeDeep: function (mine, their, strategy) {
        this._doMerge(mine, their, function(key){
            if (typeof their[key] === 'object') {
                if (their[key] instanceof Array) {
                    mine[key] = [].concat(mine[key], their[key]);
                } else {
                    mine[key] = this[strategy](mine[key], their[key]);
                }
            } else if (mine[key] === undefined ) {
                mine[key] = their[key];
            }
        });

        return mine;
    },

    _shouldUsePrototypeWhenSubjectIsClass: function () {
        return this._usePrototypeWhenSubjectIsClass;
    },

    process: function (subject, options) {
        var their = options,
            useProto = this._shouldUsePrototypeWhenSubjectIsClass(),
            mine = (useProto && subject.prototype) || subject,
            strategy = _STRATEGIES_[this.getParameter()];

        this[strategy](mine, their);
    }
};

module.exports = Merge;