/** * 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+')' + '
'; } 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]; } };