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