UNPKG

joii

Version:

Javascript Object Inheritance Implementation

1,240 lines (1,077 loc) 115 kB
/* Javascript Object Inheritance Implementation ______ ________ * (c) 2016 <harold@iedema.me> __ / / __ \/ _/ _/ * Licensed under MIT. / // / /_/ // /_/ / * ------------------------------------------------------ \___/\____/___/__*/ 'use strict'; (function (factory) { if (typeof define === 'function' && define.amd) { // AMD. Register as an anonymous module. define([], factory); } else if (typeof exports === 'object') { // Node/CommonJS factory(exports); } else { // Browser globals factory(window); } } (function (root) { // allows use as both raw source in the browser and compiled dist // easier to test/debug when using the raw source root = typeof (root) !== 'undefined' ? root : {}; var JOII = typeof (root.JOII) !== 'undefined' ? root.JOII : {}; /* Javascript Object Inheritance Implementation ______ ________ * (c) 2016 <harold@iedema.me> __ / / __ \/ _/ _/ * Licensed under MIT. / // / /_/ // /_/ / * ------------------------------------------------------ \___/\____/___/__*/ // Need this to be in scope for internal functions, but don't want to expose it outside // this will be inside the closure after compile, while still being available in global scope for src testing var inner_static_objects = {}; JOII = typeof (JOII) !== 'undefined' ? JOII : {}; JOII.Config = { constructors : ['__construct', 'construct', '->', '=>'], callables : ['__call', '<>'], /** * Adds a constructor method name. The first occurance of a function * named like one of these is executed. The rest is ignored to prevent * ambiguous behavior. * * @param {string} name */ addConstructor : function (name) { if (JOII.Config.constructors.indexOf(name) !== -1) { return; } JOII.Config.constructors.push(name); }, /** * Removes a constructor method name. The first occurance of a function * named like one of these is executed. The rest is ignored to prevent * ambiguous behavior. * * @param {string} name */ removeConstructor: function(name) { if (JOII.Config.constructors.indexOf(name) === -1) { return; } JOII.Config.constructors.splice(JOII.Config.constructors.indexOf(name), 1); }, /** * Adds a callable method name, like __call. Only one of these is * executed if more than one exist to prevent ambiguous behaviour. * * @param {string} name */ addCallable: function (name) { if (JOII.Config.callables.indexOf(name) !== -1) { return; } JOII.Config.callables.push(name); }, /** * Removes a callable method name, like __call. Only one of these is * executed if more than one exist to prevent ambiguous behaviour. * * @param {string} name */ removeCallable: function(name) { if (JOII.Config.callables.indexOf(name) === -1) { return; } JOII.Config.callables.splice(JOII.Config.callables.indexOf(name), 1); } }; /* Javascript Object Inheritance Implementation ______ ________ * (c) 2016 <harold@iedema.me> __ / / __ \/ _/ _/ * Licensed under MIT. / // / /_/ // /_/ / * ------------------------------------------------------ \___/\____/___/__*/ JOII = typeof (JOII) !== 'undefined' ? JOII : {}; JOII.Compat = {}; /** * Finds and returns the name of a JOII-generated object or false if it doesn't * exist. * * @param {Object|Function} e * @return {String|Boolean} */ JOII.Compat.findJOIIName = function(e) { var i, r; if (typeof (e) === 'string' || typeof (e) === 'number' || typeof (e) === 'undefined' || e === null ) { return false; } if (typeof (e.__joii__) !== 'undefined') { return e.__joii__.name; } if (typeof (e.prototype) !== 'undefined' && typeof (e.prototype.__joii__) !== 'undefined') { return e.prototype.__joii__.name; } // Chrome / FF // IE 11+ if (typeof (e.__proto__) !== 'undefined') { r = JOII.Compat.findJOIIName(e.__proto__); if (typeof (r) === 'string') { return r; } } if (typeof (e) === 'function') { e = e.prototype; } for (i in e) { if (e.hasOwnProperty(i) === false) continue; if (typeof (e[i]) === 'function' || typeof (e[i]) === 'object') { r = JOII.Compat.findJOIIName(e[i]); if (typeof (r) === 'string') { return r; } } } return false; }; /** * Array.indexOf implementation. * * @param {Array} array * @param {*} elt * @return {Number} */ JOII.Compat.indexOf = function(array, elt) { if (typeof (array.indexOf) === 'function') { return array.indexOf(elt); } var len = array.length >>> 0, from = Number(arguments[1]) || 0; from = (from < 0) ? Math.ceil(from) : Math.floor(from); from = (from < 0) ? from + len : from; for (; from < len; from++) { if (from in array && array[from] === elt) { return from; } } return -1; }; /** * Make a deep copy of an object. * * - original by jQuery (http://jquery.com/) */ JOII.Compat.extend = function() { var options, src, copy, copyIsArray = false, clone, target = arguments[0] || {}, i = 1, length = arguments.length, deep = false; if (typeof target === "boolean") { deep = target; target = arguments[i] || {}; i++; } if (typeof target !== "object" && typeof (target) !== "function") { target = {}; } for (; i < length; i++) { options = arguments[i]; if (options !== null && arguments[i] !== undefined) { if (typeof (options.__joii__) !== 'undefined') { JOII.CreateProperty(target, '__joii__', options.__joii__); } for (var name in options) { // Do NOT check 'hasOwnProperty' here. The universe will implode. src = target[name]; copy = options[name]; if (target === copy) { continue; } if (deep && copy && (JOII.Compat.isPlainObject(copy) || (copyIsArray = JOII.Compat.isArray(copy)))) { if (copyIsArray) { copyIsArray = false; clone = src && JOII.Compat.isArray(src) ? src : []; } else { clone = src && JOII.Compat.isPlainObject(src) ? src : {}; } target[name] = JOII.Compat.extend(deep, clone, copy); } else if (copy !== undefined) { target[name] = copy; } } } } return target; }; /** * Returns true if the given object is an array. * * @param {Object} obj * @return {Boolean} */ JOII.Compat.isArray = function(obj) { var length = obj.length, type = typeof (obj); if (type === "function" || (typeof (window) !== 'undefined' && obj === window)) { return false; } if (obj.nodeType === 1 && length) { return true; } return Object.prototype.toString.call(obj) === '[object Array]'; }; /** * Returns true if the given object is a plain object (not an array). * * @param {Object} obj * @return {Boolean} */ JOII.Compat.isPlainObject = function(obj) { var hasOwn = ({}).hasOwnProperty; if (typeof (obj) !== "object" || obj.nodeType || (typeof (window) !== 'undefined' && obj === window)) { return false; } return !(obj.constructor && !hasOwn.call(obj.constructor.prototype, "isPrototypeOf")); }; /** * JOII.Compat.CreateObject implementation * * @param {Object} o * @return {Object} */ JOII.Compat.CreateObject = function(o) { if (typeof (Object.create) === 'function') { return Object.create(o); } var c = (function() { function Class() { } return function(o) { if (arguments.length != 1) { throw new Error('JOII.Compat.CreateObject implementation only accepts one parameter.'); } Class.prototype = o; return new Class(); }; })(); return c(o); }; /** * Function.bind implementation. "bind" is part of ECMA-262, 5th edition * and therefore not available in all browsers. This polyfill is needed * to emulate the functionality of Function.bind * * @param {Function} fn * @param {Object} context * @return {Function} */ JOII.Compat.Bind = function(fn, context) { if (typeof fn !== "function") { // closest thing possible to the ECMAScript 5 internal IsCallable function throw new TypeError("Function.prototype.bind - argument #1 must be a function."); } // return fn.bind(context); return function bound() { return fn.apply(context, arguments); }; }; /** * http://www.ietf.org/rfc/rfc4122.txt * * @return string */ JOII.Compat.GenerateUUID = function() { var s = []; var hexDigits = "0123456789abcdef"; for (var i = 0; i < 36; i++) { s[i] = hexDigits.substr(Math.floor(Math.random() * 0x10), 1); } s[14] = "4"; // bits 12-15 of the time_hi_and_version field to 0010 s[19] = hexDigits.substr((s[19] & 0x3) | 0x8, 1); // bits 6-7 of the clock_seq_hi_and_reserved to 01 s[8] = s[13] = s[18] = s[23] = "-"; return s.join(""); }; /** * Returns an object consisting of name, parameters and body depending on * the amount of parameters given. * * If no name is specified (argument[0] === string), a generated UUID will * take its place. * * @param {Object} args * @return {Object} */ JOII.Compat.ParseArguments = function(args) { var result = { name: '', parameters: {}, body: {} }; switch (args.length) { // Zero-arguments. Unlikely, but valid for classes and interfaces. case 0: result.name = JOII.Compat.GenerateUUID(); break; // One argument. Name or body. case 1: if (typeof (args[0]) === 'string') { result.name = args[0]; } if (typeof (args[0]) === 'object') { result.name = JOII.Compat.GenerateUUID(); result.body = args[0]; } break; // Two arguments: Name & Body or Parameters & Body case 2: if (typeof (args[0]) === 'string') { result.name = args[0]; } if (typeof (args[0]) === 'object') { result.name = JOII.Compat.GenerateUUID(); result.parameters = args[0]; } result.body = args[1]; break; // Three parameters: pass them all. case 3: result.name = args[0]; result.parameters = args[1]; result.body = args[2]; case 4: result.name = args[0]; result.parameters = args[1]; result.body = args[2]; result.is_static_generated = args[3]; } // Validate the results. if (typeof (result.name) !== 'string' || typeof (result.parameters) !== 'object' || (typeof (result.body) !== 'object' && typeof (result.body) !== 'function')) { throw 'Invalid parameter types given. Expected: ([[[string], object], <object|function>]).'; } return result; }; /** * Some parameters can be passed as a string, object or array of both. This * function will parse the argument and return an array of actual objects. * * @param {*} arg * @param {Boolean} deep * @return {Object} */ JOII.Compat.flexibleArgumentToArray = function(arg, deep) { if (typeof (arg) === 'object' && !JOII.Compat.isArray(arg) && typeof (arg[0]) === 'undefined') { return [deep ? JOII.Compat.extend(true, {}, arg) : arg]; } else if (typeof (arg) === 'function') { return [deep ? JOII.Compat.extend(true, {}, arg.prototype) : arg.prototype]; } else if (typeof (arg) === 'object' && JOII.Compat.isArray(arg)) { var result = []; for (var i in arg) { result.push(JOII.Compat.flexibleArgumentToArray(arg[i], false)[0]); } return result; } else { throw 'Unable to read ' + typeof (arg) + '. Object, function or array expected.'; } }; JOII.Compat.canTypeBeCastTo = function(val, cast_to_type) { // InstanceOf validator (in case of interfaces & classes) if (typeof (JOII.InterfaceRegistry[cast_to_type]) !== 'undefined' || typeof (JOII.ClassRegistry[cast_to_type]) !== 'undefined') { if (JOII.Compat.findJOIIName(val) !== cast_to_type) { if (val !== null && (typeof (val.instanceOf) !== 'function' || (typeof (val) === 'object' && typeof (val.instanceOf) === 'function' && !val.instanceOf(cast_to_type)))) { return false; } } } else { // Native val validator if (typeof (JOII.EnumRegistry[cast_to_type]) !== 'undefined') { var _e = JOII.EnumRegistry[cast_to_type]; if (!_e.contains(val)) { return false; // Should we really be validating that it fits inside the enum? } } else { if (typeof (val) !== cast_to_type) { return false; } } } // nothing failed, so should be compatible return true; };/* Javascript Object Inheritance Implementation ______ ________ * (c) 2016 <harold@iedema.me> __ / / __ \/ _/ _/ * Licensed under MIT. / // / /_/ // /_/ / * ------------------------------------------------------ \___/\____/___/__*/ JOII = typeof (JOII) !== 'undefined' ? JOII : {}; JOII.InternalPropertyNames = ['__joii__', 'super', 'instanceOf', 'deserialize', 'serialize', 'getStatic', '__api__']; JOII.InternalTypeNames = [ 'undefined', 'object', 'boolean', 'number' , 'string', 'symbol', 'function' , 'const' ]; /** * The PrototypeBuilder is responsible for creating a prototype of the * final 'class'- or 'interface'-type. * * Parameters can consist of one or more of the following: * 'extends' : <class-type> Inherit a parent type * 'implements' : <array of interface-type> * * @param {String} name * @param {Object} parameters * @param {Object} body * @param {Boolean} is_interface * @return {Object} */ JOII.PrototypeBuilder = function(name, parameters, body, is_interface, is_static_generated) { is_static_generated = (is_static_generated === true); // Create a clean prototype of the class body. var prototype = {}, deep_copy = JOII.Compat.extend(true, {}, body); // Create the internal JOII-object. JOII.CreateProperty(prototype, '__joii__', { name : name, parent : undefined, metadata : {}, constants : {}, implementations : [name], is_abstract : parameters.abstract === true, is_final : parameters.final === true, is_static : parameters['static'] === true || is_static_generated }); // Apply traits / mix-ins if (typeof (parameters.uses) !== 'undefined') { var traits = JOII.Compat.flexibleArgumentToArray(parameters.uses); for (var t in traits) { deep_copy = JOII.Compat.extend(true, deep_copy, traits[t]); } } if (prototype.__joii__.is_abstract && prototype.__joii__.is_final) { throw 'A class cannot be both abstract and final simultaniously.'; } // Iterate over properties from the deep_copy, get the metadata of the // property and move them in the prototype. for (var i in deep_copy) { if (deep_copy.hasOwnProperty(i) === false) continue; var meta = JOII.ParseClassProperty(i, name); // make sure this prototype only has members that match it's static state if (prototype.__joii__['is_static'] !== true && meta.is_static && !is_interface) continue; if (prototype.__joii__['is_static'] && !meta.is_static) { if (is_static_generated) { continue; } else { throw 'Member ' + meta.name + ' is non-static. A static class cannot contain non-static members.'; } } if (typeof (deep_copy[i]) === 'function' || meta.parameters.length > 0 || (meta.name in prototype.__joii__.metadata && 'overloads' in prototype.__joii__.metadata[meta.name])) { if (typeof (deep_copy[i]) !== 'function') { if (meta.parameters.length > 0) { throw 'Member ' + meta.name + ' specifies parameters, but it\'s value isn\'t a function.'; } else { throw 'Member ' + meta.name + ' overloads an existing function, but it\'s value isn\'t a function.'; } } if (meta.name in prototype && typeof (prototype[meta.name]) !== 'function') { throw 'Member ' + meta.name + ' overloads an existing property, but the previous property isn\'t a function.'; } meta.class_name = name; JOII.addFunctionToPrototype(prototype, meta, deep_copy[i]); } else if (meta.is_constant) { prototype.__joii__.constants[meta.name] = deep_copy[i]; JOII.CreateProperty(prototype, meta.name, deep_copy[i], false); meta.class_name = name; prototype.__joii__.metadata[meta.name] = meta; } else { prototype[meta.name] = deep_copy[i]; meta.class_name = name; prototype.__joii__.metadata[meta.name] = meta; } // Don't create getters and setters if we are an interface. if (is_interface === true) { continue; } } if (typeof (parameters.abstract) !== 'undefined') { prototype.__joii__.is_abstract = true; if (is_interface) { throw 'An interface cannot be declared abstract.'; } } if (prototype.__joii__.is_static && is_interface) { throw 'An interface cannot be declared static.'; } // Create getters and setters for properties. We do this _after_ the // copying of the parent object because that prototype doesn't contain // the getter/setter methods yet. (Fixes issue #10) // NOTE: The comment implies that this will apply getters/setters for inherited properties // however, it's only looping over "deep_copy", which only contains information from this class // it doesn't contain parent properties... // Moved back above the parent implementations, so that the getter/setters for this class // take priority over inherited (applies to static overloaded accessor methods) for (var i in deep_copy) { if (deep_copy.hasOwnProperty(i) === false) continue; var meta = JOII.ParseClassProperty(i, name); if (typeof (deep_copy[i]) === 'function' || meta.parameters.length > 0 || 'overloads' in meta) { continue; } // make sure this prototype only has members that match it's static state if (prototype.__joii__['is_static'] !== true && meta.is_static && !is_interface) continue; if (prototype.__joii__['is_static'] && !meta.is_static) { if (is_static_generated) { continue; } else { throw 'Member ' + meta.name + 'is non-static. A static class cannot contain non-static members.'; } } // Generate getters and setters if we're not dealing with anything // that is a function or declared private. if (meta.visibility !== 'private') { var gs = JOII.CreatePropertyGetterSetter(deep_copy, meta, name); gs.getter.meta.class_name = name; if (typeof (prototype.__joii__.metadata[gs.getter.name]) == 'undefined' || !prototype.__joii__.metadata[gs.getter.name].has_parameterless) { JOII.addFunctionToPrototype(prototype, gs.getter.meta, gs.getter.fn, true); } //prototype[gs.getter.name] = gs.getter.fn; //prototype.__joii__.metadata[gs.getter.name] = gs.getter.meta; if (typeof (gs.setter.meta) !== 'undefined') { gs.setter.meta.class_name = name; if (typeof (prototype.__joii__.metadata[gs.getter.name]) == 'undefined' || !prototype.__joii__.metadata[gs.getter.name].has_parameterless) { JOII.addFunctionToPrototype(prototype, gs.setter.meta, gs.setter.fn, true); } //prototype[gs.setter.name] = gs.setter.fn; //prototype.__joii__.metadata[gs.setter.name] = gs.setter.meta; } } } // Apply the parent prototype. if (typeof (parameters['extends']) !== 'undefined') { var parent = parameters['extends']; // If the given parent is a function, use its prototype. if (typeof (parent) === 'function') { if (is_static_generated) { parent = parent.__joii__.prototype; } else { parent = parent.prototype; } } // Only Object-types can be used as a parent object. if (typeof (parent) !== 'object' && !is_static_generated) { throw (is_interface ? 'An interface' : 'A class') + ' may only extend on functions or object-types.'; } // Create a parent property in the prototype which contains a deep- // copy of the prototype of the given parent. prototype.__joii__.parent = JOII.Compat.extend(true, {}, parent); // If the parent is final, it cannot be extended upon. if (parent.__joii__.is_final === true) { throw 'Unable to extend on a final class.'; } // Iterate over parent classes and apply the implementations for the instanceOf verifications. var current = prototype.__joii__.parent; while (typeof current !== 'undefined') { prototype.__joii__.implementations.push(current.__joii__.name); // Move to the next underlying class. current = current.__joii__.parent; } // Clone the constants of the parent into this one. prototype.__joii__.constants = JOII.Compat.extend(true, prototype.__joii__.constants, parent.__joii__.constants); // The __joii__ property is usually hidden and not enumerable, so we // need to re-create it ourselves. JOII.CreateProperty(prototype.__joii__.parent, '__joii__', (parent.__joii__)); // Iterate over the properties of the parent object and apply the // contents in our own prototype where applicable. for (i in prototype.__joii__.parent) { // We're only interested in properties that really belong to // the object. So we'll skip any inherited things from the // native JavaScript's "Object". if (!prototype.__joii__.parent.hasOwnProperty(i)) { continue; } // If the property is an internal method, skip it. if (JOII.Compat.indexOf(JOII.InternalPropertyNames, i) !== -1) { continue; } var property = prototype.__joii__.parent[i]; var property_meta = prototype.__joii__.parent.__joii__.metadata[i]; var proto_meta = prototype.__joii__.metadata[i]; // make sure this prototype only has members that match it's static state if (prototype.__joii__['is_static'] !== true && property_meta.is_static && !is_interface) continue; if (prototype.__joii__['is_static'] && !property_meta.is_static) { if (is_static_generated) { continue; } else { throw 'Member ' + property_meta.name + 'is non-static. A static class cannot contain non-static members.'; } } if (typeof (proto_meta) === 'undefined') { proto_meta = prototype.__joii__.metadata[i] = JOII.Compat.extend(true, {}, property_meta); proto_meta.has_parameterless = false; if ('overloads' in proto_meta) { delete proto_meta.overloads; } } // If another property with the same name already exists within // our own prototype, skip its inherited implementation. if (typeof (prototype[i]) !== 'undefined' && typeof (property_meta) === 'object' && typeof (proto_meta) === 'object') { if (proto_meta.is_generated === false) { // Check for visibility change. if (property_meta.visibility !== proto_meta.visibility) { throw 'Member "' + i + '" must be ' + property_meta.visibility + ' as defined in the parent ' + (is_interface ? 'interface' : 'class') + '.'; } if (typeof (property) === 'function' || property_meta.parameters.length > 0 || 'overloads' in proto_meta || 'overloads' in property_meta) { // if it's a function, don't check the following. // is_final is checked when adding the function later // read_only and nullable don't apply to functions } else { // Check final properties. if (property_meta.is_final === true) { throw 'Final member "' + i + '" cannot be overwritten.'; } // Is the property read-only? if (property_meta.is_read_only !== proto_meta.is_read_only) { throw 'Member "' + i + '" must be read-only as defined in the parent ' + (is_interface ? 'interface' : 'class') + '.'; } // Is the property nullable? if (property_meta.is_nullable !== proto_meta.is_nullable) { throw 'Member "' + i + '" must be nullable as defined in the parent ' + (is_interface ? 'interface' : 'class') + '.'; } } } if (typeof (property) === 'function' || property_meta.parameters.length > 0 || 'overloads' in proto_meta || 'overloads' in property_meta) { // if it's a function, we still want to proceed } else { continue; } } if (typeof (property) === 'function' || property_meta.parameters.length > 0 || 'overloads' in proto_meta || 'overloads' in property_meta) { // if it's a function, we still want to proceed } else { // It's safe to apply non-function properties immediatly. if (typeof (property) !== 'function' || is_interface === true) { if (!property_meta.is_static) { prototype[i] = property; } // if it's static, we need to reference the original functions, since static members stay with the class they're defined in if (property_meta.is_static) { var gs = JOII.CreatePropertyGetterSetter(prototype, property_meta, name); gs.getter.fn = Function('\ var args = ["' + gs.getter.name + '"];\ for (var i in arguments) { args.push(arguments[i]); }\ return this[\'super\'].apply(this, args);\ '); gs.setter.fn = Function('\ var args = ["' + gs.setter.name + '"];\ for (var i in arguments) { args.push(arguments[i]); }\ return this[\'super\'].apply(this, args);\ '); if (typeof gs.getter.meta !== 'undefined') { gs.getter.meta.is_generated = true; gs.getter.meta.is_inherited = true; JOII.addFunctionToPrototype(prototype, gs.getter.meta, gs.getter.fn, true); //prototype[gs.getter.name] = gs.getter.fn; //prototype.__joii__.metadata[gs.getter.name] = gs.getter.meta; } if (typeof gs.setter.meta !== 'undefined') { gs.setter.meta.is_generated = true; gs.setter.meta.is_inherited = true; JOII.addFunctionToPrototype(prototype, gs.setter.meta, gs.setter.fn, true); //prototype[gs.setter.name] = gs.setter.fn; //prototype.__joii__.metadata[gs.setter.name] = gs.setter.meta; } } continue; } } // From this point on, the 'property' variable only contains // functions. This is where the funny business starts. Instead // of simply copying the 'function' into our own prototype, // we'll create our own function which calls the function from // the parent object. (Fixes issue #9) // The function "super" is implemented from the ClassBuilder. var generated_fn = null; if (property_meta.is_static) { generated_fn = function (prop_name, parent_name) { return function() { return inner_static_objects[parent_name][prop_name].apply(inner_static_objects[parent_name], arguments); }; }(i, property_meta.class_name); } else { generated_fn = Function('\ var args = ["' + i + '"];\ for (var i in arguments) { args.push(arguments[i]); }\ return this[\'super\'].apply(this, args);\ '); } var tmp_meta = JOII.Compat.extend(true, {}, property_meta); delete tmp_meta.overloads; tmp_meta.is_inherited = true; tmp_meta.has_parameterless = false; if (proto_meta.has_parameterless) { if (property_meta.is_final) { throw 'Final member "' + property_meta.name + '(' + property_meta.parameters.join(', ') + ')" cannot be overwritten.'; } } else { if (typeof (property) === 'function' || property_meta.parameters.length > 0 || 'overloads' in proto_meta || 'overloads' in property_meta) { if ('overloads' in property_meta && typeof (property_meta.overloads) === 'object' && property_meta.overloads.length > 1) { // parent has multiple overloads specified. Loop through them, and apply each. for (var idx = 0; idx < property_meta.overloads.length; idx++) { tmp_meta.parameters = property_meta.overloads[idx].parameters; tmp_meta.is_abstract = property_meta.overloads[idx].is_abstract; tmp_meta.has_parameters = property_meta.overloads[idx].has_parameters; tmp_meta.is_final = property_meta.overloads[idx].is_final; JOII.addFunctionToPrototype(prototype, tmp_meta, generated_fn, true); } } else { JOII.addFunctionToPrototype(prototype, tmp_meta, generated_fn, true); } } else { prototype[i] = generated_fn; } } } } if (is_interface !== true) { /** * Calls a method from the parent prototype (if it exists). * * @param {String} method * @return {*} */ prototype['super'] = function(method) { var args = Array.prototype.slice.call(arguments, 1), current_scope = this, original_prop = this.__joii__, call = function(scope, method, args) { if (typeof (scope) === 'undefined') { throw new Error('Parent method "' + method + '" does not exist.'); } if (typeof (scope.__joii__.parent) !== 'undefined' && typeof (scope.__joii__.parent[method]) === 'undefined') { return call(scope.__joii__.parent, method, args); } var parent = scope.__joii__.parent; if (typeof (scope.__joii__.parent) === 'undefined') { if (typeof (scope.__api__.__joii__.parent) !== 'undefined') { parent = scope.__api__.__joii__.parent; } else { throw new Error('Method "' + method + '" does not exist in the parent class. (called using \'super()\')'); } } var m = parent[method]; current_scope.__joii__ = parent.__joii__; var r = m.apply(current_scope, args); current_scope.__joii__ = original_prop; return r; }; return call(this, method, args); }; /** * Tests if the prototype implements an interface of the given name. * * @param {String} name * @return {Boolean} */ prototype.instanceOf = function(name) { // Find the JOII scope of the given object. if (typeof (name) === 'function') { name = name.prototype.__joii__.name; } else if (typeof (name) === 'object') { name = name.__joii__.name; } // Match against defined interfaces implemented in the class. var interfaces = this.__joii__.getInterfaces(); for (var i in interfaces) { if (interfaces.hasOwnProperty(i) === false) continue; if (interfaces[i].prototype.__joii__.name === name) { return true; } if (JOII.Compat.indexOf(interfaces[i].prototype.__joii__.implementations, name) !== -1) { return true; } } if (this.__joii__.name !== name) { // Attempt to validate by parent. if (typeof (this.__joii__.parent) !== 'undefined') { // Temporarily bind instanceOf to the parent scope. var cur_scope = this; var par_scope = this.__joii__.parent; JOII.Compat.Bind(par_scope.instanceOf, par_scope); var result = par_scope.instanceOf(name); // Restore the scope and return the result. JOII.Compat.Bind(par_scope.instanceOf, cur_scope); return result; } return false; } return true; }; } return prototype; }; /** * Parses a class property name and returns an object of property * metadata such as 'final', 'abstract', 'protected', etc. * * @param {String} str * @return {Object} */ JOII.ParseClassProperty = function(str, currently_defining) { // Parse the given string and set some defaults. var function_parameters = (/\(.*\)/).exec(str.toString()); var has_parameters = false; if (function_parameters !== null) { has_parameters = true; function_parameters = function_parameters[0].match(/[^\(,\s\)]+/g); } if (typeof (function_parameters) != 'object' || function_parameters === null) { function_parameters = []; } var data = str.toString().replace(/\s?\(.*\)\s?|^\s+|\s+(?=\s)|\s+$/g, '').split(/\s/), name = data[data.length - 1], types = JOII.InternalTypeNames, explicit_serialize = false, metadata = { 'name' : name, 'type' : null, // Allow all types by default. 'visibility' : 'public', // Can be one of: public, protected, private. 'is_abstract' : false, // Force implementation in child. 'is_final' : false, // Disallow implementation in child. 'is_nullable' : false, // Allow "null" or "undefined" in properties. 'is_read_only' : false, // Don't generate a setter for the property. 'is_constant' : false, // Is the property publicly accessible? 'is_static' : false, // Is the property static? 'is_enum' : false, // Is the property an enumerator? 'is_generated' : false, // Is the property generated? 'is_inherited' : false, // Is the property inherited? 'is_joii_object': false, // Does this represent a joii class/interface ? 'serializable' : false, // Is the property serializable? 'has_parameters': has_parameters, 'parameters' : function_parameters }, i; for (var c in JOII.Config.callables) { if (JOII.Config.callables[c] == name) { metadata.is_static = true; } } // Remove the name from the list. data.pop(); // If there are no flags set, simply return the defaults. if (data.length === 0) { return metadata; } var raw_data = []; // Make sure all property flags are lowercase. We don't use Array.map // for this because Internet Explorer 8 (and below) doesn't know it. for (i in data) { if (typeof (JOII.InterfaceRegistry[data[i]]) === 'undefined' && typeof (JOII.ClassRegistry[data[i]]) === 'undefined') { raw_data[i] = data[i].toString(); data[i] = raw_data[i].toLowerCase(); } } // Shorthand for validating other flags within the same declaration. // If args exists in data, msg is thrown. var metaHas = function(args, data, msg) { if (typeof (args) !== 'object') { args = [args]; } for (var i in args) { if (args.hasOwnProperty(i) === false) continue; if (JOII.Compat.indexOf(data, args[i]) !== -1) { throw msg; } } }; for (i in data) { switch (data[i]) { case 'public': metaHas('protected', data, 'Property "' + name + '" cannot be both public and protected at the same time.'); metaHas('private', data, 'Property "' + name + '" cannot be both public and private at the same time.'); metadata.visibility = 'public'; if (!explicit_serialize) { metadata.serializable = true; } break; case 'protected': metaHas('public', data, 'Property "' + name + '" cannot be both protected and public at the same time.'); metaHas('private', data, 'Property "' + name + '" cannot be both protected and private at the same time.'); metadata.visibility = 'protected'; break; case 'private': metaHas('public', data, 'Property "' + name + '" cannot be both private and public at the same time.'); metaHas('protected', data, 'Property "' + name + '" cannot be both private and protected at the same time.'); metadata.visibility = 'private'; break; case 'abstract': metaHas('final', data, 'Property "' + name + '" cannot be both abstract and final at the same time.'); metadata.is_abstract = true; break; case 'final': metaHas('abstract', data, 'Property "' + name + '" cannot be both abstract and final at the same time.'); metadata.is_final = true; break; case 'nullable': metadata.is_nullable = true; break; case 'static': metadata.is_static = true; break; case 'read': case 'immutable': metadata.is_read_only = true; break; case 'serializable': metadata.serializable = true; explicit_serialize = true; break; case 'notserializable': metadata.serializable = false; explicit_serialize = true; break; case 'const': metaHas(['private', 'protected', 'public'], data, 'A constant cannot have visibility modifiers.'); metaHas('final', data, 'A constant cannot be final.'); metaHas('abstract', data, 'A constant cannot be abstract.'); metaHas(['nullable', 'immutable', 'read'], data, 'A constant cannot be nullable or immutable.'); metadata.is_constant = true; break; default: if (JOII.Compat.indexOf(types, data[i]) !== -1) { if (metadata.type !== null) { throw 'Property "' + name + '" has multiple type defintions.'; } metadata.type = data[i]; break; } // Check for Interface-types if (typeof (JOII.InterfaceRegistry[data[i]]) !== 'undefined') { metadata.is_joii_object = true; metadata.type = JOII.InterfaceRegistry[data[i]].definition.__interface__.name; break; } // Check for Class-types if (typeof (JOII.ClassRegistry[data[i]]) !== 'undefined') { metadata.is_joii_object = true; metadata.type = JOII.ClassRegistry[data[i]].prototype.__joii__.name; break; } // Check for enumerators if (typeof (JOII.EnumRegistry[data[i]]) !== 'undefined') { metadata.is_enum = true; metadata.type = data[i]; break; } // Check for self reference (the class we're defining) if (currently_defining == raw_data[i]) { metadata.is_joii_object = true; metadata.type = data[i]; break; } throw 'Syntax error: unexpected "' + data[i] + '" in property declaration of "' + name + '".'; } } return metadata; }; JOII.CreatePropertyGetterSetter = function(deep_copy, meta, name) { "use strict"; // If the meta type is boolean, prefix the getter with 'is' // rather than 'get'. var getter, getter_meta, getter_fn; if (meta.type === 'boolean') { if (JOII.CamelcaseName(meta.name).substr(0, 2) === 'Is') { getter = JOII.CamelcaseName(meta.name); getter = getter.substring(0, 1).toLowerCase() + getter.substring(1); } else { getter = 'is' + JOII.CamelcaseName(meta.name); } } else { getter = 'get' + JOII.CamelcaseName(meta.name); } var setter = 'set' + JOII.CamelcaseName(meta.name), setter_meta, setter_fn; // Create a getter if (typeof (deep_copy[getter]) === 'undefined') { if (meta.is_static) { getter_fn = function (name, prop_name) { return function () { return inner_static_objects[name][prop_name]; }; }(name, meta.name); } else { getter_fn = new Function('return this["' + meta.name + '"];'); } getter_meta = JOII.ParseClassProperty(meta.visibility + ' function ' + getter + '()', name); getter_meta.visibility = meta.visibility; getter_meta.is_abstract = meta.is_abstract; getter_meta.is_final = meta.is_final; getter_meta.is_static = meta.is_static; getter_meta.class_name = meta.class_name; } // Create a setter if (typeof (deep_copy[setter]) === 'undefined' && meta.is_read_only === false) { var nullable = meta.is_nullable, validator; if (meta.type !== null) { // InstanceOf validator (in case of interfaces & classes) if (typeof (JOII.InterfaceRegistry[meta.type]) !== 'undefined' || typeof (JOII.ClassRegistry[meta.type]) !== 'undefined') { setter_fn = function (name, prop_name, prop_type, nullable, setter) { return function (v) { if (prop_type !== null) { if (JOII.Compat.findJOIIName(v[0]) === prop_type) {} else { if (v[0] !== null && typeof (v[0].instanceOf) !== 'function' || (typeof (v[0]) === 'object' && v[0] !== null && typeof (v[0].instanceOf) === 'function' && !v[0].instanceOf(prop_type)) || v[0] === null) { if (nullable === false || (nullable === true && v[0] !== null && typeof (v[0]) !== "undefined")) { throw setter + ' expects an instance of ' + prop_type + ', ' + (v[0] === null ? "null" : typeof (v[0])) + " given."; } }}; } this[prop_name] = v[0]; return this.__api__; }; }(name, meta.name, meta.type, nullable, setter); } else { // Native type validator setter_fn = function (name, prop_name, prop_type, nullable, setter) { return function (v) { if (prop_type !== null) { if (typeof (JOII.EnumRegistry[prop_type]) !== 'undefined') { var _e = JOII.EnumRegistry[prop_type]; if (!_e.contains(v[0])) { throw setter + ": '" + v[0] + "' is not a member of enum " + _e.getName() + "."; } } else { if (typeof (v[0]) !== prop_type) { if (nullable === false || (nullable === true && v[0] !== null && typeof (v[0]) !== "undefined")) { throw setter + " expects " + prop_type + ", " + typeof (v[0]) + " given."; } }; } } this[prop_name] = v[0]; return this.__api__; }; }(name, meta.name, meta.type, nullable, setter); } } else { setter_fn = function (prop_name) { return function (v) { this[prop_name] = v[0]; return this.__api__; }; }(meta.name); } // if it's static, make sure we're referring to it explicitly // so that we reference the same property, no matter which subclass it's called from // static properties stay with the class they're defined in. if (meta.is_static) { if (typeof (JOII.InterfaceRegistry[meta.type]) !== 'undefined' || typeof (JOII.ClassRegistry[meta.type]) !== 'undefined') { setter_fn = function (name, prop_name, prop_type, nullable, setter) { return function (v) { if (prop_type !== null) { if (JOII.Compat.findJOIIName(v[0]) === prop_type) {} else { if (v[0] !== null && typeof (v[0].instanceOf) !== 'function' || (typeof (v[0]) === 'object' && v[0] !== null && typeof (v[0].instanceOf) === 'function' && !v[0].instanceOf(prop_type)) || v[0] === null) { if (nullable === false || (nullable === true && v[0] !== null && typeof (v[0]) !== "undefined")) { throw setter + ' expects an instance of ' + prop_type + ', ' + (v[0] === null ? "null" : typeof (v[0])) + " given."; } }}; } inner_static_objects[name][prop_name] = v[0]; return this.__api__; }; }(name, meta.name, meta.type, nullable, setter); } else { // Native type validator setter_fn = function (name, prop_name, prop_type, nullable, setter) { return function (v) { if (prop_type !== null) {