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