Path: blob/main/website/GAUSS/js/deepCopy.js
2941 views
/* This file is part of OWL JavaScript Utilities.12OWL JavaScript Utilities is free software: you can redistribute it and/or3modify it under the terms of the GNU Lesser General Public License4as published by the Free Software Foundation, either version 3 of5the License, or (at your option) any later version.67OWL JavaScript Utilities is distributed in the hope that it will be useful,8but WITHOUT ANY WARRANTY; without even the implied warranty of9MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the10GNU Lesser General Public License for more details.1112You should have received a copy of the GNU Lesser General Public13License along with OWL JavaScript Utilities. If not, see14<http://www.gnu.org/licenses/>.15*/1617owl = (function() {1819// the re-usable constructor function used by clone().20function Clone() {}2122// clone objects, skip other types.23function clone(target) {24if ( typeof target == 'object' ) {25Clone.prototype = target;26return new Clone();27} else {28return target;29}30}313233// Shallow Copy34function copy(target) {35if (typeof target !== 'object' ) {36return target; // non-object have value sematics, so target is already a copy.37} else {38var value = target.valueOf();39if (target != value) {40// the object is a standard object wrapper for a native type, say String.41// we can make a copy by instantiating a new object around the value.42return new target.constructor(value);43} else {44// ok, we have a normal object. If possible, we'll clone the original's prototype45// (not the original) to get an empty object with the same prototype chain as46// the original. If just copy the instance properties. Otherwise, we have to47// copy the whole thing, property-by-property.48if ( target instanceof target.constructor && target.constructor !== Object ) {49var c = clone(target.constructor.prototype);5051// give the copy all the instance properties of target. It has the same52// prototype as target, so inherited properties are already there.53for ( var property in target) {54if (target.hasOwnProperty(property)) {55c[property] = target[property];56}57}58} else {59var c = {};60for ( var property in target ) c[property] = target[property];61}6263return c;64}65}66}6768// Deep Copy69var deepCopiers = [];7071function DeepCopier(config) {72for ( var key in config ) this[key] = config[key];73}74DeepCopier.prototype = {75constructor: DeepCopier,7677// determines if this DeepCopier can handle the given object.78canCopy: function(source) { return false; },7980// starts the deep copying process by creating the copy object. You81// can initialize any properties you want, but you can't call recursively82// into the DeeopCopyAlgorithm.83create: function(source) { },8485// Completes the deep copy of the source object by populating any properties86// that need to be recursively deep copied. You can do this by using the87// provided deepCopyAlgorithm instance's deepCopy() method. This will handle88// cyclic references for objects already deepCopied, including the source object89// itself. The "result" passed in is the object returned from create().90populate: function(deepCopyAlgorithm, source, result) {}91};9293function DeepCopyAlgorithm() {94// copiedObjects keeps track of objects already copied by this95// deepCopy operation, so we can correctly handle cyclic references.96this.copiedObjects = [];97thisPass = this;98this.recursiveDeepCopy = function(source) {99return thisPass.deepCopy(source);100}101this.depth = 0;102}103DeepCopyAlgorithm.prototype = {104constructor: DeepCopyAlgorithm,105106maxDepth: 256,107108// add an object to the cache. No attempt is made to filter duplicates;109// we always check getCachedResult() before calling it.110cacheResult: function(source, result) {111this.copiedObjects.push([source, result]);112},113114// Returns the cached copy of a given object, or undefined if it's an115// object we haven't seen before.116getCachedResult: function(source) {117var copiedObjects = this.copiedObjects;118var length = copiedObjects.length;119for ( var i=0; i<length; i++ ) {120if ( copiedObjects[i][0] === source ) {121return copiedObjects[i][1];122}123}124return undefined;125},126127// deepCopy handles the simple cases itself: non-objects and object's we've seen before.128// For complex cases, it first identifies an appropriate DeepCopier, then calls129// applyDeepCopier() to delegate the details of copying the object to that DeepCopier.130deepCopy: function(source) {131// null is a special case: it's the only value of type 'object' without properties.132if ( source === null ) return null;133134// All non-objects use value semantics and don't need explict copying.135if ( typeof source !== 'object' ) return source;136137var cachedResult = this.getCachedResult(source);138139// we've already seen this object during this deep copy operation140// so can immediately return the result. This preserves the cyclic141// reference structure and protects us from infinite recursion.142if ( cachedResult ) return cachedResult;143144// objects may need special handling depending on their class. There is145// a class of handlers call "DeepCopiers" that know how to copy certain146// objects. There is also a final, generic deep copier that can handle any object.147for ( var i=0; i<deepCopiers.length; i++ ) {148var deepCopier = deepCopiers[i];149if ( deepCopier.canCopy(source) ) {150return this.applyDeepCopier(deepCopier, source);151}152}153// the generic copier can handle anything, so we should never reach this line.154throw new Error("no DeepCopier is able to copy " + source);155},156157// once we've identified which DeepCopier to use, we need to call it in a very158// particular order: create, cache, populate. This is the key to detecting cycles.159// We also keep track of recursion depth when calling the potentially recursive160// populate(): this is a fail-fast to prevent an infinite loop from consuming all161// available memory and crashing or slowing down the browser.162applyDeepCopier: function(deepCopier, source) {163// Start by creating a stub object that represents the copy.164var result = deepCopier.create(source);165166// we now know the deep copy of source should always be result, so if we encounter167// source again during this deep copy we can immediately use result instead of168// descending into it recursively.169this.cacheResult(source, result);170171// only DeepCopier::populate() can recursively deep copy. So, to keep track172// of recursion depth, we increment this shared counter before calling it,173// and decrement it afterwards.174this.depth++;175if ( this.depth > this.maxDepth ) {176throw new Error("Exceeded max recursion depth in deep copy.");177}178179// It's now safe to let the deepCopier recursively deep copy its properties.180deepCopier.populate(this.recursiveDeepCopy, source, result);181182this.depth--;183184return result;185}186};187188// entry point for deep copy.189// source is the object to be deep copied.190// maxDepth is an optional recursion limit. Defaults to 256.191function deepCopy(source, maxDepth) {192var deepCopyAlgorithm = new DeepCopyAlgorithm();193if ( maxDepth ) deepCopyAlgorithm.maxDepth = maxDepth;194return deepCopyAlgorithm.deepCopy(source);195}196197// publicly expose the DeepCopier class.198deepCopy.DeepCopier = DeepCopier;199200// publicly expose the list of deepCopiers.201deepCopy.deepCopiers = deepCopiers;202203// make deepCopy() extensible by allowing others to204// register their own custom DeepCopiers.205deepCopy.register = function(deepCopier) {206if ( !(deepCopier instanceof DeepCopier) ) {207deepCopier = new DeepCopier(deepCopier);208}209deepCopiers.unshift(deepCopier);210}211212// Generic Object copier213// the ultimate fallback DeepCopier, which tries to handle the generic case. This214// should work for base Objects and many user-defined classes.215deepCopy.register({216canCopy: function(source) { return true; },217218create: function(source) {219if ( source instanceof source.constructor ) {220return clone(source.constructor.prototype);221} else {222return {};223}224},225226populate: function(deepCopy, source, result) {227for ( var key in source ) {228if ( source.hasOwnProperty(key) ) {229result[key] = deepCopy(source[key]);230}231}232return result;233}234});235236// Array copier237deepCopy.register({238canCopy: function(source) {239return ( source instanceof Array );240},241242create: function(source) {243return new source.constructor();244},245246populate: function(deepCopy, source, result) {247for ( var i=0; i<source.length; i++) {248result.push( deepCopy(source[i]) );249}250return result;251}252});253254// Date copier255deepCopy.register({256canCopy: function(source) {257return ( source instanceof Date );258},259260create: function(source) {261return new Date(source);262}263});264265// HTML DOM Node266267// utility function to detect Nodes. In particular, we're looking268// for the cloneNode method. The global document is also defined to269// be a Node, but is a special case in many ways.270function isNode(source) {271if ( window.Node ) {272return source instanceof Node;273} else {274// the document is a special Node and doesn't have many of275// the common properties so we use an identity check instead.276if ( source === document ) return true;277return (278typeof source.nodeType === 'number' &&279source.attributes &&280source.childNodes &&281source.cloneNode282);283}284}285286// Node copier287deepCopy.register({288canCopy: function(source) { return isNode(source); },289290create: function(source) {291// there can only be one (document).292if ( source === document ) return document;293294// start with a shallow copy. We'll handle the deep copy of295// its children ourselves.296return source.cloneNode(false);297},298299populate: function(deepCopy, source, result) {300// we're not copying the global document, so don't have to populate it either.301if ( source === document ) return document;302303// if this Node has children, deep copy them one-by-one.304if ( source.childNodes && source.childNodes.length ) {305for ( var i=0; i<source.childNodes.length; i++ ) {306var childCopy = deepCopy(source.childNodes[i]);307result.appendChild(childCopy);308}309}310}311});312313return {314DeepCopyAlgorithm: DeepCopyAlgorithm,315copy: copy,316clone: clone,317deepCopy: deepCopy318};319})();320321322