joii
Version:
Javascript Object Inheritance Implementation
1,240 lines (1,077 loc) • 115 kB
JavaScript
/* 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) {