1 /* This file is part of OWL JavaScript Utilities. 2 3 OWL JavaScript Utilities is free software: you can redistribute it and/or 4 modify it under the terms of the GNU Lesser General Public License 5 as published by the Free Software Foundation, either version 3 of 6 the License, or (at your option) any later version. 7 8 OWL JavaScript Utilities is distributed in the hope that it will be useful, 9 but WITHOUT ANY WARRANTY; without even the implied warranty of 10 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 11 GNU Lesser General Public License for more details. 12 13 You should have received a copy of the GNU Lesser General Public 14 License along with OWL JavaScript Utilities. If not, see 15 <http://www.gnu.org/licenses/>. 16 */ 17 18 // check if owl is included in banana 19 goog.provide('Banana.thirdParty.OWLClone'); 20 21 owl = (function() { 22 23 // the re-usable constructor function used by clone(). 24 function Clone() {} 25 26 // clone objects, skip other types. 27 function clone(target) { 28 if ( typeof target == 'object' ) { 29 Clone.prototype = target; 30 return new Clone(); 31 } else { 32 return target; 33 } 34 } 35 36 37 // Shallow Copy 38 function copy(target) { 39 if (typeof target !== 'object' ) { 40 return target; // non-object have value sematics, so target is already a copy. 41 } else if (target === null) { 42 return null; 43 } else { 44 var value = target.valueOf(); 45 if (target != value) { 46 // the object is a standard object wrapper for a native type, say String. 47 // we can make a copy by instantiating a new object around the value. 48 return new target.constructor(value); 49 } else { 50 // ok, we have a normal object. If possible, we'll clone the original's prototype 51 // (not the original) to get an empty object with the same prototype chain as 52 // the original. If just copy the instance properties. Otherwise, we have to 53 // copy the whole thing, property-by-property. 54 if ( target instanceof target.constructor && target.constructor !== Object ) { 55 var c = clone(target.constructor.prototype); 56 57 // give the copy all the instance properties of target. It has the same 58 // prototype as target, so inherited properties are already there. 59 for ( var property in target) { 60 if (target.hasOwnProperty(property)) { 61 c[property] = target[property]; 62 } 63 } 64 } else { 65 var c = {}; 66 for ( var property in target ) c[property] = target[property]; 67 } 68 69 return c; 70 } 71 } 72 } 73 74 // Deep Copy 75 var deepCopiers = []; 76 77 function DeepCopier(config) { 78 for ( var key in config ) this[key] = config[key]; 79 } 80 DeepCopier.prototype = { 81 constructor: DeepCopier, 82 83 // determines if this DeepCopier can handle the given object. 84 canCopy: function(source) { return false; }, 85 86 // starts the deep copying process by creating the copy object. You 87 // can initialize any properties you want, but you can't call recursively 88 // into the DeeopCopyAlgorithm. 89 create: function(source) { }, 90 91 // Completes the deep copy of the source object by populating any properties 92 // that need to be recursively deep copied. You can do this by using the 93 // provided deepCopyAlgorithm instance's deepCopy() method. This will handle 94 // cyclic references for objects already deepCopied, including the source object 95 // itself. The "result" passed in is the object returned from create(). 96 populate: function(deepCopyAlgorithm, source, result) {} 97 }; 98 99 function DeepCopyAlgorithm() { 100 // copiedObjects keeps track of objects already copied by this 101 // deepCopy operation, so we can correctly handle cyclic references. 102 this.copiedObjects = []; 103 thisPass = this; 104 this.recursiveDeepCopy = function(source) { 105 return thisPass.deepCopy(source); 106 }; 107 this.depth = 0; 108 } 109 DeepCopyAlgorithm.prototype = { 110 constructor: DeepCopyAlgorithm, 111 112 maxDepth: 256, 113 114 // add an object to the cache. No attempt is made to filter duplicates; 115 // we always check getCachedResult() before calling it. 116 cacheResult: function(source, result) { 117 this.copiedObjects.push([source, result]); 118 }, 119 120 // Returns the cached copy of a given object, or undefined if it's an 121 // object we haven't seen before. 122 getCachedResult: function(source) { 123 var copiedObjects = this.copiedObjects; 124 var length = copiedObjects.length; 125 for ( var i=0; i<length; i++ ) { 126 if ( copiedObjects[i][0] === source ) { 127 return copiedObjects[i][1]; 128 } 129 } 130 return undefined; 131 }, 132 133 // deepCopy handles the simple cases itself: non-objects and object's we've seen before. 134 // For complex cases, it first identifies an appropriate DeepCopier, then calls 135 // applyDeepCopier() to delegate the details of copying the object to that DeepCopier. 136 deepCopy: function(source) { 137 // null is a special case: it's the only value of type 'object' without properties. 138 if ( source === null ) return null; 139 140 // All non-objects use value semantics and don't need explict copying. 141 if ( typeof source !== 'object' ) return source; 142 143 var cachedResult = this.getCachedResult(source); 144 145 // we've already seen this object during this deep copy operation 146 // so can immediately return the result. This preserves the cyclic 147 // reference structure and protects us from infinite recursion. 148 if ( cachedResult ) return cachedResult; 149 150 // objects may need special handling depending on their class. There is 151 // a class of handlers call "DeepCopiers" that know how to copy certain 152 // objects. There is also a final, generic deep copier that can handle any object. 153 for ( var i=0; i<deepCopiers.length; i++ ) { 154 var deepCopier = deepCopiers[i]; 155 if ( deepCopier.canCopy(source) ) { 156 return this.applyDeepCopier(deepCopier, source); 157 } 158 } 159 // the generic copier can handle anything, so we should never reach this line. 160 throw new Error("no DeepCopier is able to copy " + source); 161 }, 162 163 // once we've identified which DeepCopier to use, we need to call it in a very 164 // particular order: create, cache, populate. This is the key to detecting cycles. 165 // We also keep track of recursion depth when calling the potentially recursive 166 // populate(): this is a fail-fast to prevent an infinite loop from consuming all 167 // available memory and crashing or slowing down the browser. 168 applyDeepCopier: function(deepCopier, source) { 169 // Start by creating a stub object that represents the copy. 170 var result = deepCopier.create(source); 171 172 // start pushing the objectmapper into the clone 173 // TODO: This is ObjectMapper specific code. Be aware when updating this file 174 if (source instanceof Banana.Data.BaseObject) 175 { 176 // the source object is an object with functions 177 // recreate the functions 178 var resultObject = Banana.Data.ObjectMapper.getObject(source.getClass()); 179 var x; 180 for (x in result) 181 { 182 resultObject[x] = result[x]; 183 } 184 result = resultObject; 185 } 186 187 // we now know the deep copy of source should always be result, so if we encounter 188 // source again during this deep copy we can immediately use result instead of 189 // descending into it recursively. 190 this.cacheResult(source, result); 191 192 // only DeepCopier::populate() can recursively deep copy. So, to keep track 193 // of recursion depth, we increment this shared counter before calling it, 194 // and decrement it afterwards. 195 this.depth++; 196 if ( this.depth > this.maxDepth ) { 197 throw new Error("Exceeded max recursion depth in deep copy."); 198 } 199 200 // It's now safe to let the deepCopier recursively deep copy its properties. 201 deepCopier.populate(this.recursiveDeepCopy, source, result); 202 203 this.depth--; 204 205 return result; 206 } 207 }; 208 209 // entry point for deep copy. 210 // source is the object to be deep copied. 211 // maxDepth is an optional recursion limit. Defaults to 256. 212 function deepCopy(source, maxDepth) { 213 var deepCopyAlgorithm = new DeepCopyAlgorithm(); 214 if ( maxDepth ) deepCopyAlgorithm.maxDepth = maxDepth; 215 return deepCopyAlgorithm.deepCopy(source); 216 } 217 218 // publicly expose the DeepCopier class. 219 deepCopy.DeepCopier = DeepCopier; 220 221 // publicly expose the list of deepCopiers. 222 deepCopy.deepCopiers = deepCopiers; 223 224 // make deepCopy() extensible by allowing others to 225 // register their own custom DeepCopiers. 226 deepCopy.register = function(deepCopier) { 227 if ( !(deepCopier instanceof DeepCopier) ) { 228 deepCopier = new DeepCopier(deepCopier); 229 } 230 deepCopiers.unshift(deepCopier); 231 }; 232 233 // Generic Object copier 234 // the ultimate fallback DeepCopier, which tries to handle the generic case. This 235 // should work for base Objects and many user-defined classes. 236 deepCopy.register({ 237 canCopy: function(source) { return true; }, 238 239 create: function(source) { 240 if ( source instanceof source.constructor ) { 241 return clone(source.constructor.prototype); 242 } else { 243 return {}; 244 } 245 }, 246 247 populate: function(deepCopy, source, result) { 248 for ( var key in source ) { 249 if ( source.hasOwnProperty(key) ) { 250 result[key] = deepCopy(source[key]); 251 } 252 } 253 return result; 254 } 255 }); 256 257 // Array copier 258 deepCopy.register({ 259 canCopy: function(source) { 260 return ( source instanceof Array ); 261 }, 262 263 create: function(source) { 264 return new source.constructor(); 265 }, 266 267 populate: function(deepCopy, source, result) { 268 for ( var i=0; i<source.length; i++) { 269 result.push( deepCopy(source[i]) ); 270 } 271 return result; 272 } 273 }); 274 275 // Date copier 276 deepCopy.register({ 277 canCopy: function(source) { 278 return ( source instanceof Date ); 279 }, 280 281 create: function(source) { 282 return new Date(source); 283 } 284 }); 285 286 // HTML DOM Node 287 288 // utility function to detect Nodes. In particular, we're looking 289 // for the cloneNode method. The global document is also defined to 290 // be a Node, but is a special case in many ways. 291 function isNode(source) { 292 if ( window.Node ) { 293 return source instanceof Node; 294 } else { 295 // the document is a special Node and doesn't have many of 296 // the common properties so we use an identity check instead. 297 if ( source === document ) return true; 298 return ( 299 typeof source.nodeType === 'number' && 300 source.attributes && 301 source.childNodes && 302 source.cloneNode 303 ); 304 } 305 } 306 307 // Node copier 308 deepCopy.register({ 309 canCopy: function(source) { return isNode(source); }, 310 311 create: function(source) { 312 // there can only be one (document). 313 if ( source === document ) return document; 314 315 // start with a shallow copy. We'll handle the deep copy of 316 // its children ourselves. 317 return source.cloneNode(false); 318 }, 319 320 populate: function(deepCopy, source, result) { 321 // we're not copying the global document, so don't have to populate it either. 322 if ( source === document ) return document; 323 324 // if this Node has children, deep copy them one-by-one. 325 if ( source.childNodes && source.childNodes.length ) { 326 for ( var i=0; i<source.childNodes.length; i++ ) { 327 var childCopy = deepCopy(source.childNodes[i]); 328 result.appendChild(childCopy); 329 } 330 } 331 } 332 }); 333 334 return { 335 DeepCopyAlgorithm: DeepCopyAlgorithm, 336 copy: copy, 337 clone: clone, 338 deepCopy: deepCopy 339 }; 340 })(); 341