1
0
Fork 0

Split concrete and abstract ctor generation into two separate functions

closure/master
Mike Gerwitz 2011-03-27 23:16:19 -04:00
parent 5bb0269280
commit 8ba68b31dc
1 changed files with 238 additions and 199 deletions

View File

@ -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(