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
|
||||
* @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.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()
|
||||
{
|
||||
/**
|
||||
* 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
|
||||
*/
|
||||
return function extend()
|
||||
// ensure we'll be permitted to instantiate abstract classes for the base
|
||||
extending = true;
|
||||
|
||||
var args = Array.prototype.slice.call( arguments ),
|
||||
props = args.pop() || {},
|
||||
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 )
|
||||
{
|
||||
// ensure we'll be permitted to instantiate abstract classes for the base
|
||||
extending = true;
|
||||
// we no longer need it
|
||||
delete props.__name;
|
||||
}
|
||||
|
||||
var args = Array.prototype.slice.call( arguments ),
|
||||
props = args.pop() || {},
|
||||
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 )
|
||||
// IE has problems with toString()
|
||||
if ( enum_bug )
|
||||
{
|
||||
// concrete class
|
||||
if ( abstract_methods.__length === 0 )
|
||||
if ( props.toString !== Object.prototype.toString )
|
||||
{
|
||||
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;
|
||||
}
|
||||
// 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;
|
||||
props.__toString = props.toString;
|
||||
}
|
||||
}
|
||||
} )( 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(
|
||||
|
|
Loading…
Reference in New Issue