UNPKG

@qooxdoo/framework

Version:

The JS Framework for Coders

380 lines (330 loc) 12 kB
/* ************************************************************************ qooxdoo - the new era of web development http://qooxdoo.org Copyright: 2004-2008 1&1 Internet AG, Germany, http://www.1und1.de License: MIT: https://opensource.org/licenses/MIT See the LICENSE file in the project's top-level directory for details. Authors: * Sebastian Werner (wpbasti) * Andreas Ecker (ecker) ====================================================================== This class contains code based on the following work: * Underscore.js http://underscorejs.org Copyright: 2009-2014 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors License: MIT: http://www.opensource.org/licenses/mit-license.php ************************************************************************ */ /** * Helper functions to handle Object as a Hash map. * * @require(qx.lang.normalize.Object) * @ignore(qx.core.Assert) */ qx.Bootstrap.define("qx.lang.Object", { statics : { /** * Clears the map from all values * * @param map {Object} the map to clear */ empty : function(map) { if (qx.core.Environment.get("qx.debug")) { qx.core.Assert && qx.core.Assert.assertMap(map, "Invalid argument 'map'"); } for (var key in map) { if (map.hasOwnProperty(key)) { delete map[key]; } } }, /** * Check if the hash has any keys * * @signature function(map) * @param map {Object} the map to check * @return {Boolean} whether the map has any keys * @lint ignoreUnused(key) */ isEmpty : function(map) { if (qx.core.Environment.get("qx.debug")) { qx.core.Assert && qx.core.Assert.assertMap(map, "Invalid argument 'map'"); } for (var key in map) { return false; } return true; }, /** * Get the number of objects in the map * * @signature function(map) * @param map {Object} the map * @return {Integer} number of objects in the map */ getLength : qx.Bootstrap.objectGetLength, /** * Get the values of a map as array * * @deprecated {6.0} Please use Object instance values method instead * * @param map {Object} the map * @return {Array} array of the values of the map */ getValues : function(map) { return Object.values(map); }, /** * Inserts all keys of the source object into the * target objects. Attention: The target map gets modified. * * @signature function(target, source, overwrite) * @param target {Object} target object * @param source {Object} object to be merged * @param overwrite {Boolean ? true} If enabled existing keys will be overwritten * @return {Object} Target with merged values from the source object */ mergeWith : qx.Bootstrap.objectMergeWith, /** * Return a copy of an Object * * @param source {Object} Object to copy * @param deep {Boolean} If the clone should be a deep clone. * @return {Object} A copy of the object */ clone : function(source, deep) { if (qx.lang.Type.isObject(source)) { var clone = {}; for (var key in source) { if (deep) { clone[key] = qx.lang.Object.clone(source[key], deep); } else { clone[key] = source[key]; } } return clone; } else if (qx.lang.Type.isArray(source)) { var clone = []; for (var i=0; i < source.length; i++) { if (deep) { clone[i] = qx.lang.Object.clone(source[i], deep); } else { clone[i] = source[i]; } }; return clone; } return source; }, /** * Perform a deep comparison to check if two objects are equal * * @param object1 {Object} the object that is compared to * @param object2 {Object} the object that is compared with * @return {Boolean} The result of the comparison */ equals : function(object1, object2){ return qx.lang.Object.__equals(object1, object2, [], []); }, /** * Internal recursive comparison function for equals * * @param object1 {Object} the object that is compared to * @param object2 {Object} the object that is compared with * @param aStack {Object} Stack of object1 sub-objects to be traversed * @param bStack {Object} Stack of object2 sub-objects to be traversed * @return {Boolean} The result of the comparison * */ __equals : function(object1, object2, aStack, bStack){ // Identical objects are equal. `0 === -0`, but they aren't identical. // See the [Harmony `egal` proposal](http://wiki.ecmascript.org/doku.php?id=harmony:egal). if (object1 === object2){ return object1 !== 0 || 1 / object1 == 1 / object2; } // A strict comparison is necessary because `null == undefined`. if (object1 == null || object2 == null){ return object1 === object2; } // Compare `[[Class]]` names. var className = Object.prototype.toString.call(object1); if (className != Object.prototype.toString.call(object2)){ return false; } switch (className) { // Strings, numbers, dates, and booleans are compared by value. case '[object String]': // Primitives and their corresponding object wrappers are equivalent; thus, `"5"` is // equivalent to `new String("5")`. return object1 == String(object2); case '[object Number]': // `NaN`s are equivalent, but non-reflexive. An `egal` comparison is performed for // other numeric values. return object1 != +object1 ? object2 != +object2 : (object1 == 0 ? 1 / object1 == 1 / object2 : object1 == +object2); case '[object Date]': case '[object Boolean]': // Coerce dates and booleans to numeric primitive values. Dates are compared by their // millisecond representations. Note that invalid dates with millisecond representations // of `NaN` are not equivalent. return +object1 == +object2; // RegExps are compared by their source patterns and flags. case '[object RegExp]': return object1.source == object2.source && object1.global == object2.global && object1.multiline == object2.multiline && object1.ignoreCase == object2.ignoreCase; } if (typeof object1 != 'object' || typeof object2 != 'object'){ return false; } // Assume equality for cyclic structures. The algorithm for detecting cyclic // structures is adapted from ES 5.1 section 15.12.3, abstract operation `JO`. var length = aStack.length; while (length--) { // Linear search. Performance is inversely proportional to the number of // unique nested structures. if (aStack[length] == object1){ return bStack[length] == object2; } } // Objects with different constructors are not equivalent, but `Object`s // from different frames are. var aCtor = object1.constructor, bCtor = object2.constructor; if (aCtor !== bCtor && !(qx.Bootstrap.isFunction(aCtor) && (aCtor instanceof aCtor) && qx.Bootstrap.isFunction(bCtor) && (bCtor instanceof bCtor)) && ('constructor' in object1 && 'constructor' in object2)) { return false; } // Add the first object to the stack of traversed objects. aStack.push(object1); bStack.push(object2); var size = 0, result = true; // Recursively compare objects and arrays. if (className == '[object Array]') { // Compare array lengths to determine if a deep comparison is necessary. size = object1.length; result = size == object2.length; if (result) { // Deep compare the contents, ignoring non-numeric properties. while (size--) { if (!(result = qx.lang.Object.__equals(object1[size], object2[size], aStack, bStack))){ break; } } } } else { // Deep compare objects. for (var key in object1) { if (Object.prototype.hasOwnProperty.call(object1, key)) { // Count the expected number of properties. size++; // Deep compare each member. if (!(result = Object.prototype.hasOwnProperty.call(object2, key) && qx.lang.Object.__equals(object1[key], object2[key], aStack, bStack))){ break; } } } // Ensure that both objects contain the same number of properties. if (result) { for (key in object2) { if (Object.prototype.hasOwnProperty.call(object2, key) && !(size--)){ break; } } result = !size; } } // Remove the first object from the stack of traversed objects. aStack.pop(); bStack.pop(); return result; }, /** * Inverts a map by exchanging the keys with the values. * * If the map has the same values for different keys, information will get lost. * The values will be converted to strings using the toString methods. * * @param map {Object} Map to invert * @return {Object} inverted Map */ invert : function(map) { if (qx.core.Environment.get("qx.debug")) { qx.core.Assert && qx.core.Assert.assertMap(map, "Invalid argument 'map'"); } var result = {}; for (var key in map) { result[map[key].toString()] = key; } return result; }, /** * Get the key of the given value from a map. * If the map has more than one key matching the value, the first match is returned. * If the map does not contain the value, <code>null</code> is returned. * * @param map {Object} Map to search for the key * @param value {var} Value to look for * @return {String|null} Name of the key (null if not found). */ getKeyFromValue: function(map, value) { if (qx.core.Environment.get("qx.debug")) { qx.core.Assert && qx.core.Assert.assertMap(map, "Invalid argument 'map'"); } for (var key in map) { if (map.hasOwnProperty(key) && map[key] === value) { return key; } } return null; }, /** * Whether the map contains the given value. * * @param map {Object} Map to search for the value * @param value {var} Value to look for * @return {Boolean} Whether the value was found in the map. */ contains : function(map, value) { if (qx.core.Environment.get("qx.debug")) { qx.core.Assert && qx.core.Assert.assertMap(map, "Invalid argument 'map'"); } return this.getKeyFromValue(map, value) !== null; }, /** * Convert an array into a map. * * All elements of the array become keys of the returned map by * calling <code>toString</code> on the array elements. The values of the * map are set to <code>true</code> * * @param array {Array} array to convert * @return {Map} the array converted to a map. */ fromArray: function(array) { if (qx.core.Environment.get("qx.debug")) { qx.core.Assert && qx.core.Assert.assertArray(array, "Invalid argument 'array'"); } var obj = {}; for (var i=0, l=array.length; i<l; i++) { if (qx.core.Environment.get("qx.debug")) { switch(typeof array[i]) { case "object": case "function": case "undefined": throw new Error("Could not convert complex objects like " + array[i] + " at array index "+ i +" to map syntax"); } } obj[array[i].toString()] = true; } return obj; } } });