blob: 2f68631e68269a9e57a865991d85a5aa69df816b [file] [log] [blame]
/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
var stringUtils = require('utils/string_utils');
var types = {
'get': function(prop) {
return Object.prototype.toString.call(prop);
},
'object': '[object Object]',
'array': '[object Array]',
'string': '[object String]',
'boolean': '[object Boolean]',
'number': '[object Number]'
};
module.exports = {
isChild: function(obj)
{
for (var k in obj) {
if (obj.hasOwnProperty(k)) {
if (obj[k] instanceof Object) {
return false;
}
}
}
return true;
},
recursiveKeysCount: function(obj) {
if (!(obj instanceof Object)) {
return null;
}
var self = this;
function r(obj) {
var count = 0;
for (var k in obj) {
if(self.isChild(obj[k])){
count++;
} else {
count += r(obj[k]);
}
}
return count;
}
return r(obj);
},
deepEqual: function() {
var i, l, leftChain, rightChain;
var values = arguments;
function compare2Objects (x, y) {
var p;
if (isNaN(x) && isNaN(y) && typeof x === 'number' && typeof y === 'number') {
return true;
}
if (x === y) {
return true;
}
if ((typeof x === 'function' && typeof y === 'function') ||
(x instanceof Date && y instanceof Date) ||
(x instanceof RegExp && y instanceof RegExp) ||
(x instanceof String && y instanceof String) ||
(x instanceof Number && y instanceof Number)) {
return x.toString() === y.toString();
}
if (!(x instanceof Object && y instanceof Object)) {
return false;
}
if (x.isPrototypeOf(y) || y.isPrototypeOf(x)) {
return false;
}
if (x.constructor !== y.constructor) {
return false;
}
if (x.prototype !== y.prototype) {
return false;
}
if (leftChain.indexOf(x) > -1 || rightChain.indexOf(y) > -1) {
return false;
}
for (p in y) {
if (y.hasOwnProperty(p) !== x.hasOwnProperty(p)) {
return false;
}
else if (typeof y[p] !== typeof x[p]) {
return false;
}
}
for (p in x) {
if (y.hasOwnProperty(p) !== x.hasOwnProperty(p)) {
return false;
}
else if (typeof y[p] !== typeof x[p]) {
return false;
}
switch (typeof (x[p])) {
case 'object':
case 'function':
leftChain.push(x);
rightChain.push(y);
if (!compare2Objects (x[p], y[p])) {
return false;
}
leftChain.pop();
rightChain.pop();
break;
default:
if (x[p] !== y[p]) {
return false;
}
break;
}
}
return true;
}
if (arguments.length < 1) {
return true;
}
for (i = 1, l = arguments.length; i < l; i++) {
leftChain = [];
rightChain = [];
if (!compare2Objects(arguments[0], arguments[i])) {
return false;
}
}
return true;
},
recursiveTree: function(obj) {
if (!(obj instanceof Object)) {
return null;
}
var self = this;
function r(obj,parent) {
var leaf = '';
for (var k in obj) {
if(self.isChild(obj[k])){
leaf += k + ' ('+parent+')' + '<br/>';
} else {
leaf += r(obj[k],parent +'/' + k);
}
}
return leaf;
}
return r(obj,'');
},
/**
*
* @param {object|array|object[]} target
* @param {object|array|object[]} source
* @param {function} handler
* @returns {object|array|object[]}
*/
deepMerge: function(target, source, handler) {
if (typeof target !== 'object' || typeof source !== 'object') return target;
var handlerOpts = Array.prototype.slice.call(arguments, 3);
var isArray = Em.isArray(source);
var ret = handler && typeof handler.apply(this, [target, source].concat(handlerOpts)) !== 'undefined' ?
handler(target, source) :
isArray ? [] : {};
var self = this;
// handle array
if (isArray) {
target = target || [];
ret = ret.concat(target);
if (types.object === types.get(target[0])) {
ret = self.smartArrayObjectMerge(target, source);
} else {
for(var i = 0; i < source.length; i++) {
if (typeof ret[i] === 'undefined') {
ret[i] = source[i];
} else if (typeof source[i] === 'object') {
ret[i] = this.deepMerge(target[i], source[i], handler, target, source);
} else {
if (target.indexOf(source[i]) === -1) {
ret.push(source[i]);
}
}
}
}
} else {
if (target && typeof target === 'object') {
Em.keys(target).forEach(function(key) {
ret[key] = target[key];
});
}
Em.keys(source).forEach(function(key) {
// handle value which is not Array or Object
if (typeof source[key] !== 'object' || !source[key]) {
ret[key] = source[key];
} else {
if (!target[key]) {
ret[key] = source[key];
} else {
ret[key] = self.deepMerge(target[key], source[key], handler, target, source);
}
}
});
}
return ret;
},
/**
* Find objects by index key (@see detectIndexedKey) and merge them.
*
* @param {object[]} target
* @param {object[]} source
* @returns {object[]}
*/
smartArrayObjectMerge: function(target, source) {
// keep the first object and take all keys that contains primitive value
var id = this.detectIndexedKey(target);
var self = this;
// when uniq key not found let's merge items by the key itself
if (!id) {
source.forEach(function(obj) {
Em.keys(obj).forEach(function(objKey) {
var ret = self.objectByRoot(objKey, target);
if (!Em.isNone(ret)) {
if ([types.object, types.array].contains(types.get(ret))) {
target[objKey] = self.deepMerge(obj[objKey], ret);
} else {
target[objKey] = ret;
}
} else {
var _obj = {};
_obj[objKey] = obj[objKey];
target.push(_obj);
}
});
});
return target;
}
return target.mapProperty(id).concat(source.mapProperty(id)).uniq().map(function(value) {
if (!target.someProperty(id, value)) {
return source.findProperty(id, value);
} else if (!source.someProperty(id, value)) {
return target.findProperty(id, value);
}
return self.deepMerge(target.findProperty(id, value), source.findProperty(id, value));
});
},
/**
* Determines key with uniq value. This key will be used to find correct objects in target and source to merge.
*
* @param {object} target
* @returns {string|undefined}
*/
detectIndexedKey: function(target) {
var keys = Em.keys(target[0]).map(function(key) {
if ([types.object, types.array].contains(types.get(target[0][key]))) {
return null;
}
return key;
}).compact();
return keys.filter(function(key) {
var values = target.mapProperty(key);
return values.length === values.uniq().length;
})[0];
},
/**
*
* @param {string} rootKey
* @param {object[]} target
*/
objectByRoot: function(rootKey, target) {
return target.map(function(item) {
return item[rootKey] || null;
}).compact()[0];
}
};