Issues (204)

src/Compatibility.js (1 issue)

1
/* Javascript Object Inheritance Implementation                ______  ________
2
 * (c) 2016 <[email protected]>                             __ / / __ \/  _/  _/
3
 * Licensed under MIT.                                    / // / /_/ // /_/ /
4
 * ------------------------------------------------------ \___/\____/___/__*/
5
6
JOII = typeof (JOII) !== 'undefined' ? JOII : {};
7
JOII.Compat = {};
8
9
/**
10
 * Finds and returns the name of a JOII-generated object or false if it doesn't
11
 * exist.
12
 *
13
 * @param  {Object|Function} e
14
 * @return {String|Boolean}
15
 */
16
JOII.Compat.findJOIIName = function(e, selfReferenced) {
17
    var i, r;
18
19
    if (typeof (e) === 'string' ||
20
        typeof (e) === 'number' ||
21
        typeof (e) === 'undefined' ||
22
        e === null
23
    ) {
24
        return false;
25
    }
26
27
    if (typeof (e.__joii__) !== 'undefined' && e.__joii__ !== null) {
28
        return e.__joii__.name;
29
    }
30
    if (typeof (e.prototype) !== 'undefined' && typeof (e.prototype.__joii__) !== 'undefined') {
31
        return e.prototype.__joii__.name;
32
    }
33
    
34
    // prevent infinite loops. Shouldn't need to go more than one deep.
35
    if (selfReferenced) {
36
        return false;
37
    }
38
39
    // Chrome / FF // IE 11+
40
    if (typeof (e.__proto__) !== 'undefined' && e.__proto__ !== null) {
41
        r = JOII.Compat.findJOIIName(e.__proto__, true);
42
        if (typeof (r) === 'string') {
43
            return r;
44
        }
45
    }
46
    
47
    if (typeof (e) === 'function') {
48
        e = e.prototype;
49
    }
50
51
    for (i in e) {
52
        if (e.hasOwnProperty(i) === false) continue;
53
        if (typeof (e[i]) === 'function' || typeof (e[i]) === 'object') {
54
            r = JOII.Compat.findJOIIName(e[i], true);
55
            if (typeof (r) === 'string') {
56
                return r;
57
            }
58
        }
59
    }
60
61
    return false;
62
};
63
64
/**
65
 * Array.indexOf implementation.
66
 *
67
 * @param  {Array} array
68
 * @param  {*}     elt
69
 * @return {Number}
70
 */
71
JOII.Compat.indexOf = function(array, elt) {
72
73
    if (typeof (array.indexOf) === 'function') {
74
        return array.indexOf(elt);
75
    }
76
77
    var len = array.length >>> 0,
78
        from = Number(arguments[1]) || 0;
79
80
    from = (from < 0) ? Math.ceil(from) : Math.floor(from);
81
    from = (from < 0) ? from + len : from;
82
83
    for (; from < len; from++) {
84
        if (from in array && array[from] === elt) {
85
            return from;
86
        }
87
    }
88
89
    return -1;
90
};
91
92
/**
93
 * Make a deep copy of an object.
94
 *
95
 * - original by jQuery (http://jquery.com/)
96
 */
97
JOII.Compat.extend = function() {
98
    var options, src, copy, copyIsArray = false, clone,
99
        target = arguments[0] || {},
100
        i = 1,
101
        length = arguments.length,
102
        deep = false;
103
    if (typeof target === "boolean") {
104
        deep = target; target = arguments[i] || {}; i++;
105
    }
106
    if (typeof target !== "object" && typeof (target) !== "function") {
107
        target = {};
108
    }
109
    for (; i < length; i++) {
110
        options = arguments[i];
111
        if (options !== null && arguments[i] !== undefined) {
112
113
            if (typeof (options.__joii__) !== 'undefined') {
114
                JOII.CreateProperty(target, '__joii__', options.__joii__);
115
            }
116
117
            for (var name in options) {
118
                // Do NOT check 'hasOwnProperty' here. The universe will implode.
119
                src = target[name];
120
                copy = options[name];
121
                if (target === copy) { continue; }
122
                if (deep && copy && (JOII.Compat.isPlainObject(copy) || (copyIsArray = JOII.Compat.isArray(copy)))) {
123
                    if (copyIsArray) {
124
                        copyIsArray = false;
125
                        clone = src && JOII.Compat.isArray(src) ? src : [];
126
                    } else {
127
                        clone = src && JOII.Compat.isPlainObject(src) ? src : {};
128
                    }
129
                    target[name] = JOII.Compat.extend(deep, clone, copy);
130
                } else if (copy !== undefined) {
131
                    target[name] = copy;
132
                }
133
            }
134
        }
135
    }
136
    return target;
137
};
138
139
140
/**
141
 * Recursively walks through a normal object, and flattens any JOII objects it finds, to prepare them for serialization
142
 *
143
 * @param  {Object} current_obj
144
 * @param  {Object} obj_base
145
 * @return {Boolean}
146
 */
147
JOII.Compat.flattenObject = function(current_obj) {
148
    var obj = null;
149
                    
150
    if (JOII.Compat.isArray(current_obj)) {
151
        obj = [];
152
    } else {
153
        obj = {};
154
    }
155
156
    for (var key in current_obj) {
157
        if (current_obj.hasOwnProperty(key) === false) continue;
158
159
        var currentValue = current_obj[key];
160
161
        if (typeof (currentValue) === 'object' && currentValue !== null && 'serialize' in currentValue && typeof (currentValue.serialize) === 'function') {
162
            try {
163
                obj[key] = currentValue.serialize(true);
164
165
                if (typeof(obj[key]) === 'string') {
166
                    // wasn't our serialize method. Try to deserialize back to an object to continue.
167
                    obj[key] = JSON.parse(obj[key]);
168
                }
169
            } catch (e) {
170
                // something went wrong with calling the object's serialize method. Fall back to normal crawling.
171
                obj[key] = JOII.Compat.flattenObject(currentValue);
172
            }
173
        } else if (typeof (currentValue) === 'object' && currentValue != null) {
174
            obj[key] = JOII.Compat.flattenObject(currentValue);
175
        } else {
176
            obj[key] = currentValue;
177
        }
178
    }
179
180
    return obj;
181
};
182
183
/**
184
 * Recursively walks through a normal object, and deserializes any JOII objects it finds,
185
 * optionally restoring to a current object while maintaining object references
186
 *
187
 * @param  {Object} current_obj
188
 * @param  {Object} obj_base
189
 * @return {Boolean}
190
 */
191
JOII.Compat.inflateObject = function(current_obj, obj_base) {
192
    var obj = obj_base || null;
193
    
194
    if (typeof (obj) !== 'object' || obj == null) {         
195
        if (JOII.Compat.isArray(current_obj)) {
196
            obj = [];
197
        } else {
198
            obj = {};
199
        }
200
    }
201
202
    for (var key in current_obj) {
203
        if (current_obj.hasOwnProperty(key) === false) continue;
204
205
        var currentValue = current_obj[key];
206
        
207
        if (typeof (currentValue) === 'object' && currentValue !== null && '__joii_type' in currentValue && typeof (currentValue.__joii_type) === 'string') {
208
            var name = currentValue.__joii_type;
209
            // Check for Interface-types
210
            if (typeof (JOII.InterfaceRegistry[name]) !== 'undefined') {
211
                throw 'Cannot instantiate an interface.';
212
            }
213
            // Check for Class-types
214
            else if (typeof (JOII.ClassRegistry[name]) !== 'undefined') {
215
                var oldValue = obj[key];
216
                if (typeof (oldValue) === 'object' && oldValue !== null && '__joii__' in oldValue && typeof (oldValue.__joii__) === 'object' && oldValue.__joii__ !== null && oldValue.__joii__.name === name) {
217
                    // try to deserialize in place if the object already exists. This avoids breaking object references.
218
                    oldValue.deserialize(currentValue);
219
                } else {
220
                    obj[key] = JOII.ClassRegistry[name].deserialize(currentValue);
221
                }
222
            } else {
223
                throw 'Class ' + name + ' not currently in scope!';
224
            }
225
        } else if (typeof (currentValue) === 'object' && currentValue != null) {
226
            obj[key] = JOII.Compat.inflateObject(currentValue, obj[key]);
227
        } else {
228
            obj[key] = currentValue;
229
        }
230
    }
231
232
    return obj;
233
};
234
235
236
/**
237
 * Returns true if the given object is an array.
238
 *
239
 * @param  {Object} obj
240
 * @return {Boolean}
241
 */
242
JOII.Compat.isArray = function(obj) {
243
    var length = obj.length,
244
        type = typeof (obj);
245
246
    if (type === "function" || (typeof (window) !== 'undefined' && obj === window)) {
247
        return false;
248
    }
249
    if (obj.nodeType === 1 && length) {
250
        return true;
251
    }
252
    return Object.prototype.toString.call(obj) === '[object Array]';
253
};
254
255
/**
256
 * Returns true if the given object is a plain object (not an array).
257
 *
258
 * @param  {Object} obj
259
 * @return {Boolean}
260
 */
261
JOII.Compat.isPlainObject = function(obj) {
262
    var hasOwn = ({}).hasOwnProperty;
263
    if (typeof (obj) !== "object" || obj.nodeType || (typeof (window) !== 'undefined' && obj === window)) {
264
        return false;
265
    }
266
267
    return !(obj.constructor && !hasOwn.call(obj.constructor.prototype, "isPrototypeOf"));
268
};
269
270
/**
271
 * JOII.Compat.CreateObject implementation
272
 *
273
 * @param  {Object} o
274
 * @return {Object}
275
 */
276
JOII.Compat.CreateObject = function(o) {
277
278
    if (typeof (Object.create) === 'function') {
279
        return Object.create(o);
280
    }
281
282
    var c = (function() {
283
        function Class() { }
284
        return function(o) {
285
            if (arguments.length != 1) {
286
                throw new Error('JOII.Compat.CreateObject implementation only accepts one parameter.');
287
            }
288
            Class.prototype = o;
289
            return new Class();
290
        };
291
    })();
292
293
    return c(o);
294
};
295
296
/**
297
 * Function.bind implementation. "bind" is part of ECMA-262, 5th edition
298
 * and therefore not available in all browsers. This polyfill is needed
299
 * to emulate the functionality of Function.bind
300
 *
301
 * @param  {Function} fn
302
 * @param  {Object}   context
303
 * @return {Function}
304
 */
305
JOII.Compat.Bind = function(fn, context) {
306
    if (typeof fn !== "function") {
307
        // closest thing possible to the ECMAScript 5 internal IsCallable function
308
        throw new TypeError("Function.prototype.bind - argument #1 must be a function.");
309
    }
310
311
    // return fn.bind(context);
312
313
    return function bound() {
314
        return fn.apply(context, arguments);
315
    };
316
};
317
318
/**
319
 * http://www.ietf.org/rfc/rfc4122.txt
320
 *
321
 * @return string
322
 */
323
JOII.Compat.GenerateUUID = function() {
324
    var s = [];
325
    var hexDigits = "0123456789abcdef";
326
    for (var i = 0; i < 36; i++) {
327
        s[i] = hexDigits.substr(Math.floor(Math.random() * 0x10), 1);
328
    }
329
    s[14] = "4";  // bits 12-15 of the time_hi_and_version field to 0010
330
    s[19] = hexDigits.substr((s[19] & 0x3) | 0x8, 1);  // bits 6-7 of the clock_seq_hi_and_reserved to 01
331
    s[8] = s[13] = s[18] = s[23] = "-";
332
333
    return s.join("");
334
};
335
336
/**
337
 * Returns an object consisting of name, parameters and body depending on
338
 * the amount of parameters given.
339
 *
340
 * If no name is specified (argument[0] === string), a generated UUID will
341
 * take its place.
342
 *
343
 * @param  {Object} args
344
 * @return {Object}
345
 */
346
JOII.Compat.ParseArguments = function(args) {
347
    var result = { name: '', parameters: {}, body: {} };
348
349
    switch (args.length) {
350
        // Zero-arguments. Unlikely, but valid for classes and interfaces.
351
        case 0:
352
            result.name = JOII.Compat.GenerateUUID();
353
            break;
354
        // One argument. Name or body.
355
        case 1:
356
            if (typeof (args[0]) === 'string') {
357
                result.name = args[0];
358
            }
359
            if (typeof (args[0]) === 'object') {
360
                result.name = JOII.Compat.GenerateUUID();
361
                result.body = args[0];
362
            }
363
            break;
364
        // Two arguments: Name & Body or Parameters & Body
365
        case 2:
366
            if (typeof (args[0]) === 'string') {
367
                result.name = args[0];
368
            }
369
            if (typeof (args[0]) === 'object') {
370
                result.name = JOII.Compat.GenerateUUID();
371
                result.parameters = args[0];
372
            }
373
            result.body = args[1];
374
            break;
375
        // Three parameters: pass them all.
376
        case 3:
377
            result.name       = args[0];
378
            result.parameters = args[1];
379
            result.body = args[2];
0 ignored issues
show
Expected a 'break' statement before 'case'.
Loading history...
380
        case 4:
381
            result.name = args[0];
382
            result.parameters = args[1];
383
            result.body = args[2];
384
            result.is_static_generated = args[3];
385
    }
386
387
    // Validate the results.
388
    if (typeof (result.name) !== 'string' ||
389
        typeof (result.parameters) !== 'object' ||
390
        (typeof (result.body) !== 'object' && typeof (result.body) !== 'function')) {
391
        throw 'Invalid parameter types given. Expected: ([[[string], object], <object|function>]).';
392
    }
393
394
    return result;
395
};
396
397
/**
398
 * Some parameters can be passed as a string, object or array of both. This
399
 * function will parse the argument and return an array of actual objects.
400
 *
401
 * @param  {*} arg
402
 * @param  {Boolean} deep
403
 * @return {Object}
404
 */
405
JOII.Compat.flexibleArgumentToArray = function(arg, deep) {
406
    if (typeof (arg) === 'object' && !JOII.Compat.isArray(arg) && typeof (arg[0]) === 'undefined') {
407
        return [deep ? JOII.Compat.extend(true, {}, arg) : arg];
408
    } else if (typeof (arg) === 'function') {
409
        return [deep ? JOII.Compat.extend(true, {}, arg.prototype) : arg.prototype];
410
    } else if (typeof (arg) === 'object' && JOII.Compat.isArray(arg)) {
411
        var result = [];
412
        for (var i in arg) {
413
            result.push(JOII.Compat.flexibleArgumentToArray(arg[i], false)[0]);
414
        }
415
        return result;
416
    } else {
417
        throw 'Unable to read ' + typeof (arg) + '. Object, function or array expected.';
418
    }
419
};
420
421
422
JOII.Compat.canTypeBeCastTo = function(val, cast_to_type) {
423
    // InstanceOf validator (in case of interfaces & classes)
424
    if (typeof (JOII.InterfaceRegistry[cast_to_type]) !== 'undefined' ||
425
        typeof (JOII.ClassRegistry[cast_to_type]) !== 'undefined') {
426
427
        if (JOII.Compat.findJOIIName(val) !== cast_to_type) {
428
            if (val !== null && (typeof (val.instanceOf) !== 'function' || (typeof (val) === 'object' && typeof (val.instanceOf) === 'function' && !val.instanceOf(cast_to_type)))) {
429
                return false;
430
            }
431
        }
432
    } else {
433
        // Native val validator
434
        if (typeof (JOII.EnumRegistry[cast_to_type]) !== 'undefined') {
435
            var _e = JOII.EnumRegistry[cast_to_type];
436
            if (!_e.contains(val)) {
437
                return false; // Should we really be validating that it fits inside the enum?
438
            }
439
        } else {
440
            if (typeof (val) !== cast_to_type) {
441
                return false;
442
            }
443
        }
444
    }
445
    // nothing failed, so should be compatible
446
    return true;
447
};