Split concrete and abstract ctor generation into two separate functions
parent
5bb0269280
commit
8ba68b31dc
|
@ -41,7 +41,16 @@ var util = require( __dirname + '/util' ),
|
||||||
* Instance id counter, to be incremented on each new instance
|
* Instance id counter, to be incremented on each new instance
|
||||||
* @type {number}
|
* @type {number}
|
||||||
*/
|
*/
|
||||||
instance_id = 0
|
instance_id = 0,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set to TRUE when class is in the process of being extended to ensure that
|
||||||
|
* a constructor can be instantiated (to use as the prototype) without
|
||||||
|
* invoking the class construction logic
|
||||||
|
*
|
||||||
|
* @type {boolean}
|
||||||
|
*/
|
||||||
|
extending = false
|
||||||
;
|
;
|
||||||
|
|
||||||
|
|
||||||
|
@ -71,211 +80,241 @@ var enum_bug = (
|
||||||
exports.ClassBase = function Class() {};
|
exports.ClassBase = function Class() {};
|
||||||
|
|
||||||
|
|
||||||
exports.build = ( function( extending )
|
/**
|
||||||
|
* Mimics class inheritance
|
||||||
|
*
|
||||||
|
* This method will mimic inheritance by setting up the prototype with the
|
||||||
|
* provided base class (or, by default, Class) and copying the additional
|
||||||
|
* properties atop of it.
|
||||||
|
*
|
||||||
|
* The class to inherit from (the first argument) is optional. If omitted, the
|
||||||
|
* first argument will be considered to be the properties list.
|
||||||
|
*
|
||||||
|
* @return {Object} extended class
|
||||||
|
*/
|
||||||
|
exports.build = function extend()
|
||||||
{
|
{
|
||||||
/**
|
// ensure we'll be permitted to instantiate abstract classes for the base
|
||||||
* Mimics class inheritance
|
extending = true;
|
||||||
*
|
|
||||||
* This method will mimic inheritance by setting up the prototype with the
|
var args = Array.prototype.slice.call( arguments ),
|
||||||
* provided base class (or, by default, Class) and copying the additional
|
props = args.pop() || {},
|
||||||
* properties atop of it.
|
base = args.pop() || exports.ClassBase,
|
||||||
*
|
prototype = new base(),
|
||||||
* The class to inherit from (the first argument) is optional. If omitted, the
|
cname = '',
|
||||||
* first argument will be considered to be the properties list.
|
|
||||||
*
|
properties = {},
|
||||||
* @return {Object} extended class
|
prop_init = member_builder.initMembers(),
|
||||||
*/
|
members = member_builder.initMembers( prototype ),
|
||||||
return function extend()
|
|
||||||
|
abstract_methods =
|
||||||
|
util.clone( exports.getMeta( base ).abstractMethods )
|
||||||
|
|| { __length: 0 }
|
||||||
|
;
|
||||||
|
|
||||||
|
// grab the name, if one was provided
|
||||||
|
if ( cname = props.__name )
|
||||||
{
|
{
|
||||||
// ensure we'll be permitted to instantiate abstract classes for the base
|
// we no longer need it
|
||||||
extending = true;
|
delete props.__name;
|
||||||
|
}
|
||||||
|
|
||||||
var args = Array.prototype.slice.call( arguments ),
|
// IE has problems with toString()
|
||||||
props = args.pop() || {},
|
if ( enum_bug )
|
||||||
base = args.pop() || exports.ClassBase,
|
|
||||||
prototype = new base(),
|
|
||||||
cname = '',
|
|
||||||
|
|
||||||
properties = {},
|
|
||||||
prop_init = member_builder.initMembers(),
|
|
||||||
members = member_builder.initMembers( prototype ),
|
|
||||||
|
|
||||||
abstract_methods =
|
|
||||||
util.clone( exports.getMeta( base ).abstractMethods )
|
|
||||||
|| { __length: 0 }
|
|
||||||
;
|
|
||||||
|
|
||||||
// grab the name, if one was provided
|
|
||||||
if ( cname = props.__name )
|
|
||||||
{
|
|
||||||
// we no longer need it
|
|
||||||
delete props.__name;
|
|
||||||
}
|
|
||||||
|
|
||||||
// IE has problems with toString()
|
|
||||||
if ( enum_bug )
|
|
||||||
{
|
|
||||||
if ( props.toString !== Object.prototype.toString )
|
|
||||||
{
|
|
||||||
props.__toString = props.toString;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// increment class identifier
|
|
||||||
class_id++;
|
|
||||||
|
|
||||||
// build the various class components (xxx: this is temporary; needs
|
|
||||||
// refactoring)
|
|
||||||
buildMembers( props,
|
|
||||||
class_id,
|
|
||||||
base,
|
|
||||||
prop_init,
|
|
||||||
abstract_methods,
|
|
||||||
properties,
|
|
||||||
members,
|
|
||||||
getMethodInstance
|
|
||||||
);
|
|
||||||
|
|
||||||
// reference to the parent prototype (for more experienced users)
|
|
||||||
prototype.___$$parent$$ = base.prototype;
|
|
||||||
|
|
||||||
// set up the new class
|
|
||||||
var new_class = createCtor( cname, abstract_methods, members );
|
|
||||||
|
|
||||||
attachPropInit( prototype, prop_init, members, class_id );
|
|
||||||
|
|
||||||
new_class.prototype = prototype;
|
|
||||||
new_class.constructor = new_class;
|
|
||||||
new_class.___$$props$$ = prop_init;
|
|
||||||
new_class.___$$methods$$ = members;
|
|
||||||
|
|
||||||
// create internal metadata for the new class
|
|
||||||
var meta = createMeta( new_class, base );
|
|
||||||
meta.abstractMethods = abstract_methods;
|
|
||||||
meta.name = cname;
|
|
||||||
|
|
||||||
// we're done with the extension process
|
|
||||||
extending = false;
|
|
||||||
|
|
||||||
return {
|
|
||||||
'class': new_class,
|
|
||||||
abstractMethods: abstract_methods,
|
|
||||||
classId: class_id,
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates the constructor for a new class
|
|
||||||
*
|
|
||||||
* This constructor will call the __constructor method for concrete classes
|
|
||||||
* and throw an exception for abstract classes (to prevent instantiation).
|
|
||||||
*
|
|
||||||
* @param {string} cname class name (may be empty)
|
|
||||||
* @param {Array.<string>} abstract_methods list of abstract methods
|
|
||||||
* @param {Object} members class members
|
|
||||||
*
|
|
||||||
* @return {Function} constructor
|
|
||||||
*/
|
|
||||||
function createCtor( cname, abstract_methods, members )
|
|
||||||
{
|
{
|
||||||
// concrete class
|
if ( props.toString !== Object.prototype.toString )
|
||||||
if ( abstract_methods.__length === 0 )
|
|
||||||
{
|
{
|
||||||
var args = null;
|
props.__toString = props.toString;
|
||||||
|
|
||||||
// constructor function to be returned
|
|
||||||
var __self = function()
|
|
||||||
{
|
|
||||||
if ( !( this instanceof __self ) )
|
|
||||||
{
|
|
||||||
// store arguments to be passed to constructor and
|
|
||||||
// instantiate new object
|
|
||||||
args = arguments;
|
|
||||||
return new __self();
|
|
||||||
}
|
|
||||||
|
|
||||||
// generate and store unique instance id
|
|
||||||
attachInstanceId( this, ++instance_id, __self );
|
|
||||||
|
|
||||||
initInstance( instance_id, this );
|
|
||||||
this.__initProps();
|
|
||||||
|
|
||||||
// call the constructor, if one was provided
|
|
||||||
if ( this.__construct instanceof Function )
|
|
||||||
{
|
|
||||||
// note that since 'this' refers to the new class (even
|
|
||||||
// subtypes), and since we're using apply with 'this', the
|
|
||||||
// constructor will be applied to subtypes without a problem
|
|
||||||
this.__construct.apply( this, ( args || arguments ) );
|
|
||||||
args = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
// attach any instance properties/methods (done after
|
|
||||||
// constructor to ensure they are not overridden)
|
|
||||||
attachInstanceOf( this );
|
|
||||||
|
|
||||||
// Provide a more intuitive string representation of the class
|
|
||||||
// instance. If a toString() method was already supplied for us,
|
|
||||||
// use that one instead.
|
|
||||||
if ( !( Object.prototype.hasOwnProperty.call(
|
|
||||||
members[ 'public' ], 'toString'
|
|
||||||
) ) )
|
|
||||||
{
|
|
||||||
// use __toString if available (see enum_bug), otherwise use
|
|
||||||
// our own defaults
|
|
||||||
this.toString = members[ 'public' ].__toString
|
|
||||||
|| ( ( cname )
|
|
||||||
? function()
|
|
||||||
{
|
|
||||||
return '#<' + cname + '>';
|
|
||||||
}
|
|
||||||
: function()
|
|
||||||
{
|
|
||||||
return '#<anonymous>';
|
|
||||||
}
|
|
||||||
)
|
|
||||||
;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// provide a more intuitive string representation
|
|
||||||
__self.toString = ( cname )
|
|
||||||
? function() { return cname; }
|
|
||||||
: function() { return '(Class)'; }
|
|
||||||
;
|
|
||||||
|
|
||||||
return __self;
|
|
||||||
}
|
|
||||||
// abstract class
|
|
||||||
else
|
|
||||||
{
|
|
||||||
var __abstract_self = function()
|
|
||||||
{
|
|
||||||
if ( !extending )
|
|
||||||
{
|
|
||||||
throw Error(
|
|
||||||
"Abstract class " + ( cname || '(anonymous)' ) +
|
|
||||||
" cannot be instantiated"
|
|
||||||
);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
__abstract_self.toString = ( cname )
|
|
||||||
? function()
|
|
||||||
{
|
|
||||||
return cname;
|
|
||||||
}
|
|
||||||
: function()
|
|
||||||
{
|
|
||||||
return '(AbstractClass)';
|
|
||||||
}
|
|
||||||
;
|
|
||||||
|
|
||||||
return __abstract_self;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} )( false );
|
|
||||||
|
// increment class identifier
|
||||||
|
class_id++;
|
||||||
|
|
||||||
|
// build the various class components (xxx: this is temporary; needs
|
||||||
|
// refactoring)
|
||||||
|
buildMembers( props,
|
||||||
|
class_id,
|
||||||
|
base,
|
||||||
|
prop_init,
|
||||||
|
abstract_methods,
|
||||||
|
properties,
|
||||||
|
members,
|
||||||
|
getMethodInstance
|
||||||
|
);
|
||||||
|
|
||||||
|
// reference to the parent prototype (for more experienced users)
|
||||||
|
prototype.___$$parent$$ = base.prototype;
|
||||||
|
|
||||||
|
// set up the new class
|
||||||
|
var new_class = createCtor( cname, abstract_methods, members );
|
||||||
|
|
||||||
|
attachPropInit( prototype, prop_init, members, class_id );
|
||||||
|
|
||||||
|
new_class.prototype = prototype;
|
||||||
|
new_class.constructor = new_class;
|
||||||
|
new_class.___$$props$$ = prop_init;
|
||||||
|
new_class.___$$methods$$ = members;
|
||||||
|
|
||||||
|
// create internal metadata for the new class
|
||||||
|
var meta = createMeta( new_class, base );
|
||||||
|
meta.abstractMethods = abstract_methods;
|
||||||
|
meta.name = cname;
|
||||||
|
|
||||||
|
// we're done with the extension process
|
||||||
|
extending = false;
|
||||||
|
|
||||||
|
return {
|
||||||
|
'class': new_class,
|
||||||
|
abstractMethods: abstract_methods,
|
||||||
|
classId: class_id,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates the constructor for a new class
|
||||||
|
*
|
||||||
|
* This constructor will call the __constructor method for concrete classes
|
||||||
|
* and throw an exception for abstract classes (to prevent instantiation).
|
||||||
|
*
|
||||||
|
* @param {string} cname class name (may be empty)
|
||||||
|
* @param {Array.<string>} abstract_methods list of abstract methods
|
||||||
|
* @param {Object} members class members
|
||||||
|
*
|
||||||
|
* @return {Function} constructor
|
||||||
|
*/
|
||||||
|
function createCtor( cname, abstract_methods, members )
|
||||||
|
{
|
||||||
|
// concrete class
|
||||||
|
if ( abstract_methods.__length === 0 )
|
||||||
|
{
|
||||||
|
return createConcreteCtor( cname, members );
|
||||||
|
}
|
||||||
|
// abstract class
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return createAbstractCtor( cname );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates the constructor for a new concrete class
|
||||||
|
*
|
||||||
|
* This constructor will call the __constructor method of the class, if
|
||||||
|
* available.
|
||||||
|
*
|
||||||
|
* @param {string} cname class name (may be empty)
|
||||||
|
* @param {Object} members class members
|
||||||
|
*
|
||||||
|
* @return {function()} constructor
|
||||||
|
*/
|
||||||
|
function createConcreteCtor( cname, members )
|
||||||
|
{
|
||||||
|
var args = null;
|
||||||
|
|
||||||
|
// constructor function to be returned
|
||||||
|
var __self = function()
|
||||||
|
{
|
||||||
|
if ( !( this instanceof __self ) )
|
||||||
|
{
|
||||||
|
// store arguments to be passed to constructor and
|
||||||
|
// instantiate new object
|
||||||
|
args = arguments;
|
||||||
|
return new __self();
|
||||||
|
}
|
||||||
|
|
||||||
|
// generate and store unique instance id
|
||||||
|
attachInstanceId( this, ++instance_id, __self );
|
||||||
|
|
||||||
|
initInstance( instance_id, this );
|
||||||
|
this.__initProps();
|
||||||
|
|
||||||
|
// call the constructor, if one was provided
|
||||||
|
if ( this.__construct instanceof Function )
|
||||||
|
{
|
||||||
|
// note that since 'this' refers to the new class (even
|
||||||
|
// subtypes), and since we're using apply with 'this', the
|
||||||
|
// constructor will be applied to subtypes without a problem
|
||||||
|
this.__construct.apply( this, ( args || arguments ) );
|
||||||
|
args = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// attach any instance properties/methods (done after
|
||||||
|
// constructor to ensure they are not overridden)
|
||||||
|
attachInstanceOf( this );
|
||||||
|
|
||||||
|
// Provide a more intuitive string representation of the class
|
||||||
|
// instance. If a toString() method was already supplied for us,
|
||||||
|
// use that one instead.
|
||||||
|
if ( !( Object.prototype.hasOwnProperty.call(
|
||||||
|
members[ 'public' ], 'toString'
|
||||||
|
) ) )
|
||||||
|
{
|
||||||
|
// use __toString if available (see enum_bug), otherwise use
|
||||||
|
// our own defaults
|
||||||
|
this.toString = members[ 'public' ].__toString
|
||||||
|
|| ( ( cname )
|
||||||
|
? function()
|
||||||
|
{
|
||||||
|
return '#<' + cname + '>';
|
||||||
|
}
|
||||||
|
: function()
|
||||||
|
{
|
||||||
|
return '#<anonymous>';
|
||||||
|
}
|
||||||
|
)
|
||||||
|
;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// provide a more intuitive string representation
|
||||||
|
__self.toString = ( cname )
|
||||||
|
? function() { return cname; }
|
||||||
|
: function() { return '(Class)'; }
|
||||||
|
;
|
||||||
|
|
||||||
|
return __self;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates the constructor for a new abstract class
|
||||||
|
*
|
||||||
|
* Calling this constructor will cause an exception to be thrown, as abstract
|
||||||
|
* classes cannot be instantiated.
|
||||||
|
*
|
||||||
|
* @param {string} cname class name (may be empty)
|
||||||
|
*
|
||||||
|
* @return {function()} constructor
|
||||||
|
*/
|
||||||
|
function createAbstractCtor( cname )
|
||||||
|
{
|
||||||
|
var __abstract_self = function()
|
||||||
|
{
|
||||||
|
if ( !extending )
|
||||||
|
{
|
||||||
|
throw Error(
|
||||||
|
"Abstract class " + ( cname || '(anonymous)' ) +
|
||||||
|
" cannot be instantiated"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
__abstract_self.toString = ( cname )
|
||||||
|
? function()
|
||||||
|
{
|
||||||
|
return cname;
|
||||||
|
}
|
||||||
|
: function()
|
||||||
|
{
|
||||||
|
return '(AbstractClass)';
|
||||||
|
}
|
||||||
|
;
|
||||||
|
|
||||||
|
return __abstract_self;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
function buildMembers(
|
function buildMembers(
|
||||||
|
|
Loading…
Reference in New Issue