From 8ba68b31dc14d18f965ba2660313fe3fe76d3371 Mon Sep 17 00:00:00 2001 From: Mike Gerwitz Date: Sun, 27 Mar 2011 23:16:19 -0400 Subject: [PATCH] Split concrete and abstract ctor generation into two separate functions --- lib/class_builder.js | 437 +++++++++++++++++++++++-------------------- 1 file changed, 238 insertions(+), 199 deletions(-) diff --git a/lib/class_builder.js b/lib/class_builder.js index 4b5b29c..41c9f66 100644 --- a/lib/class_builder.js +++ b/lib/class_builder.js @@ -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.} 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 '#'; - } - ) - ; - } - }; - - // 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.} 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 '#'; + } + ) + ; + } + }; + + // 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(