1
0
Fork 0

Initial refactoring of class_builder module into ClassBuilder ctor (#25)

closure/master
Mike Gerwitz 2011-08-09 17:27:26 -04:00
parent a401c31996
commit 7a579ab2aa
9 changed files with 323 additions and 275 deletions

View File

@ -27,42 +27,26 @@
* class tests in tact for a higher-level test.
*/
var util = require( __dirname + '/util' ),
warn = require( __dirname + '/warn' ),
member_builder = require( __dirname + '/member_builder' ),
propobj = require( __dirname + '/propobj' ),
var util = require( __dirname + '/util' ),
warn = require( __dirname + '/warn' ),
propobj = require( __dirname + '/propobj' ),
Warning = warn.Warning,
/**
* Class id counter, to be increment on each new definition
* @type {number}
*/
class_id = 0,
/**
* Instance id counter, to be incremented on each new instance
* @type {number}
*/
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
* IE contains a nasty enumeration "bug" (poor implementation) that makes
* toString unenumerable. This means that, if you do obj.toString = foo,
* toString will NOT show up in `for` or hasOwnProperty(). This is a problem.
*
* @type {boolean}
* This test will determine if this poor implementation exists.
*/
extending = false,
/**
* A flag to let the system know that we are currently attempting to access
* a static property from within a method. This means that the caller should
* be given access to additional levels of visibility.
*
* @type {boolean}
*/
sprop_internal = false,
enum_bug = (
Object.prototype.propertyIsEnumerable.call(
{ toString: function() {} },
'toString'
) === false
)
? true
: false,
/**
* Hash of reserved members
@ -88,26 +72,62 @@ var util = require( __dirname + '/util' ),
'__construct': true,
'toString': true,
'__toString': true,
}
;
};
/**
* IE contains a nasty enumeration "bug" (poor implementation) that makes
* toString unenumerable. This means that, if you do obj.toString = foo,
* toString will NOT show up in `for` or hasOwnProperty(). This is a problem.
* Initializes class builder with given member builder
*
* This test will determine if this poor implementation exists.
* The 'new' keyword is not required when instantiating this constructor.
*
* @param {Object} member_builder member builder
*/
var enum_bug = (
Object.prototype.propertyIsEnumerable.call(
{ toString: function() {} },
'toString'
) === false
)
? true
: false
;
module.exports = exports =
function ClassBuilder( member_builder )
{
// allow ommitting the 'new' keyword
if ( !( this instanceof exports ) )
{
return new exports( member_builder );
}
/**
* Used for building class members
* @type {Object}
*/
this._memberBuilder = member_builder;
/**
* Class id counter, to be increment on each new definition
* @type {number}
*/
this._classId = 0;
/**
* Instance id counter, to be incremented on each new instance
* @type {number}
*/
this._instanceId = 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}
*/
this._extending = false;
/**
* A flag to let the system know that we are currently attempting to access
* a static property from within a method. This means that the caller should
* be given access to additional levels of visibility.
*
* @type {boolean}
*/
this._spropInternal = false;
};
/**
@ -117,6 +137,7 @@ var enum_bug = (
*/
exports.ClassBase = function Class() {};
/**
* Default static property method
*
@ -168,6 +189,73 @@ exports.getForcedPublicMethods = function()
};
/**
* Returns reference to metadata for the requested class
*
* Since a reference is returned (rather than a copy), the returned object can
* be modified to alter the metadata.
*
* @param {Class} cls class from which to retrieve metadata
*
* @return {Object}
*/
exports.getMeta = function( cls )
{
return cls.___$$meta$$ || {};
}
/**
* Determines if the class is an instance of the given type
*
* The given type can be a class, interface, trait or any other type of object.
* It may be used in place of the 'instanceof' operator and contains additional
* enhancements that the operator is unable to provide due to prototypal
* restrictions.
*
* @param {Object} type expected type
* @param {Object} instance instance to check
*
* @return {boolean} true if instance is an instance of type, otherwise false
*/
exports.isInstanceOf = function( type, instance )
{
var meta, implemented, i;
try
{
// check prototype chain (will throw an error if type is not a
// constructor (function)
if ( instance instanceof type )
{
return true;
}
}
catch ( e ) {}
// if no metadata is available, then our remaining checks cannot be
// performed
if ( !instance.__cid || !( meta = exports.getMeta( instance ) ) )
{
return false;
}
implemented = meta.implemented;
i = implemented.length;
// check implemented interfaces
while ( i-- )
{
if ( implemented[ i ] === type )
{
return true;
}
}
return false;
};
/**
* Mimics class inheritance
*
@ -180,10 +268,10 @@ exports.getForcedPublicMethods = function()
*
* @return {Object} extended class
*/
exports.build = function extend()
exports.prototype.build = function extend()
{
// ensure we'll be permitted to instantiate abstract classes for the base
extending = true;
this._extending = true;
var args = Array.prototype.slice.call( arguments ),
props = args.pop() || {},
@ -191,11 +279,11 @@ exports.build = function extend()
prototype = new base(),
cname = '',
prop_init = member_builder.initMembers(),
members = member_builder.initMembers( prototype ),
prop_init = this._memberBuilder.initMembers(),
members = this._memberBuilder.initMembers( prototype ),
static_members = {
methods: member_builder.initMembers(),
props: member_builder.initMembers(),
methods: this._memberBuilder.initMembers(),
props: this._memberBuilder.initMembers(),
}
abstract_methods =
@ -235,14 +323,14 @@ exports.build = function extend()
}
// increment class identifier
class_id++;
this._classId++;
// build the various class components (xxx: this is temporary; needs
// refactoring)
try
{
buildMembers( props,
class_id,
this.buildMembers( props,
this._classId,
base,
prop_init,
abstract_methods,
@ -271,7 +359,7 @@ exports.build = function extend()
prototype.___$$parent$$ = base.prototype;
// set up the new class
var new_class = createCtor( cname, abstract_methods, members );
var new_class = this.createCtor( cname, abstract_methods, members );
// closure to hold static initialization to be used later by subtypes
initStaticVisibilityObj( new_class, static_members );
@ -281,7 +369,7 @@ exports.build = function extend()
}
staticInit( new_class, false );
attachPropInit( prototype, prop_init, members, new_class, class_id );
attachPropInit( prototype, prop_init, members, new_class, this._classId );
new_class.prototype = prototype;
new_class.constructor = new_class;
@ -307,15 +395,128 @@ exports.build = function extend()
meta.name = cname;
attachAbstract( new_class, abstract_methods );
attachId( new_class, class_id );
attachId( new_class, this._classId );
// we're done with the extension process
extending = false;
this._extending = false;
return new_class;
};
exports.prototype.buildMembers = function buildMembers(
props, class_id, base, prop_init, abstract_methods, members,
static_members, staticInstLookup
)
{
var hasOwn = Array.prototype.hasOwnProperty,
defs = {},
smethods = static_members.methods,
sprops = static_members.props,
_self = this
;
util.propParse( props, {
each: function( name, value, keywords )
{
// disallow use of our internal __initProps() method
if ( reserved_members[ name ] === true )
{
throw Error(
( ( cname ) ? cname + '::' : '' ) +
( name + " is reserved" )
);
}
// if a member was defined multiple times in the same class
// declaration, throw an error
if ( hasOwn.call( defs, name ) )
{
throw Error(
"Cannot redefine method '" + name + "' in same declaration"
);
}
// keep track of the definitions (only during class declaration)
// to catch duplicates
defs[ name ] = 1;
},
property: function( name, value, keywords )
{
var dest = ( keywordStatic( keywords ) ) ? sprops : prop_init;
// build a new property, passing in the other members to compare
// against for preventing nonsensical overrides
_self._memberBuilder.buildProp(
dest, null, name, value, keywords, base
);
},
getter: function( name, value, keywords )
{
var dest = ( keywordStatic( keywords ) ) ? smethods : members;
_self._memberBuilder.buildGetter(
dest, null, name, value, keywords, base
);
},
setter: function( name, value, keywords )
{
var dest = ( keywordStatic( keywords ) ) ? smethods : members;
_self._memberBuilder.buildSetter(
dest, null, name, value, keywords, base
);
},
method: function( name, func, is_abstract, keywords )
{
var is_static = keywordStatic( keywords ),
dest = ( is_static ) ? smethods : members,
instLookup = ( is_static )
? staticInstLookup
: getMethodInstance
;
// constructor check
if ( public_methods[ name ] === true )
{
if ( keywords[ 'protected' ] || keywords[ 'private' ] )
{
throw TypeError(
name + " must be public"
);
}
}
_self._memberBuilder.buildMethod(
dest, null, name, func, keywords, instLookup,
class_id, base
);
if ( is_abstract )
{
abstract_methods[ name ] = true;
abstract_methods.__length++;
}
else if ( ( hasOwn.call( abstract_methods, name ) )
&& ( is_abstract === false )
)
{
// if this was a concrete method, then it should no longer
// be marked as abstract
delete abstract_methods[ name ];
abstract_methods.__length--;
}
},
} );
}
/**
* Validates abstract class requirements
*
@ -362,17 +563,17 @@ function validateAbstract( ctor, cname, abstract_methods )
*
* @return {Function} constructor
*/
function createCtor( cname, abstract_methods, members )
exports.prototype.createCtor = function( cname, abstract_methods, members )
{
// concrete class
if ( abstract_methods.__length === 0 )
{
return createConcreteCtor( cname, members );
return this.createConcreteCtor( cname, members );
}
// abstract class
else
{
return createAbstractCtor( cname );
return this.createAbstractCtor( cname );
}
}
@ -388,9 +589,10 @@ function createCtor( cname, abstract_methods, members )
*
* @return {function()} constructor
*/
function createConcreteCtor( cname, members )
exports.prototype.createConcreteCtor = function( cname, members )
{
var args = null;
var args = null,
_self = this;
// constructor function to be returned (the name is set to ClassInstance
// because some debuggers (e.g. v8) will show the name of this function for
@ -411,13 +613,13 @@ function createConcreteCtor( cname, members )
// If we're extending, we don't actually want to invoke any class
// construction logic. The above is sufficient to use this class in a
// prototype, so stop here.
if ( extending )
if ( _self._extending )
{
return;
}
// generate and store unique instance id
attachInstanceId( this, ++instance_id );
attachInstanceId( this, ++_self._instanceId );
// call the constructor, if one was provided
if ( this.__construct instanceof Function )
@ -477,11 +679,13 @@ function createConcreteCtor( cname, members )
*
* @return {function()} constructor
*/
function createAbstractCtor( cname )
exports.prototype.createAbstractCtor = function( cname )
{
var _self = this;
var __abstract_self = function()
{
if ( !extending )
if ( !_self._extending )
{
throw Error(
"Abstract class " + ( cname || '(anonymous)' ) +
@ -505,117 +709,6 @@ function createAbstractCtor( cname )
}
function buildMembers(
props, class_id, base, prop_init, abstract_methods, members,
static_members, staticInstLookup
)
{
var hasOwn = Array.prototype.hasOwnProperty,
defs = {},
smethods = static_members.methods,
sprops = static_members.props
;
util.propParse( props, {
each: function( name, value, keywords )
{
// disallow use of our internal __initProps() method
if ( reserved_members[ name ] === true )
{
throw Error(
( ( cname ) ? cname + '::' : '' ) +
( name + " is reserved" )
);
}
// if a member was defined multiple times in the same class
// declaration, throw an error
if ( hasOwn.call( defs, name ) )
{
throw Error(
"Cannot redefine method '" + name + "' in same declaration"
);
}
// keep track of the definitions (only during class declaration)
// to catch duplicates
defs[ name ] = 1;
},
property: function( name, value, keywords )
{
var dest = ( keywordStatic( keywords ) ) ? sprops : prop_init;
// build a new property, passing in the other members to compare
// against for preventing nonsensical overrides
member_builder.buildProp(
dest, null, name, value, keywords, base
);
},
getter: function( name, value, keywords )
{
var dest = ( keywordStatic( keywords ) ) ? smethods : members;
member_builder.buildGetter(
dest, null, name, value, keywords, base
);
},
setter: function( name, value, keywords )
{
var dest = ( keywordStatic( keywords ) ) ? smethods : members;
member_builder.buildSetter(
dest, null, name, value, keywords, base
);
},
method: function( name, func, is_abstract, keywords )
{
var is_static = keywordStatic( keywords ),
dest = ( is_static ) ? smethods : members,
instLookup = ( is_static )
? staticInstLookup
: getMethodInstance
;
// constructor check
if ( public_methods[ name ] === true )
{
if ( keywords[ 'protected' ] || keywords[ 'private' ] )
{
throw TypeError(
name + " must be public"
);
}
}
member_builder.buildMethod(
dest, null, name, func, keywords, instLookup,
class_id, base
);
if ( is_abstract )
{
abstract_methods[ name ] = true;
abstract_methods.__length++;
}
else if ( ( hasOwn.call( abstract_methods, name ) )
&& ( is_abstract === false )
)
{
// if this was a concrete method, then it should no longer
// be marked as abstract
delete abstract_methods[ name ];
abstract_methods.__length--;
}
},
} );
}
/**
* Determines if the given keywords should result in a static member
*
@ -714,6 +807,8 @@ function attachPropInit( prototype, properties, members, ctor, cid )
*/
function initStaticVisibilityObj( ctor )
{
var _self = this;
// the object will simply be another layer in the prototype chain to
// prevent protected/private members from being mixed in with the public
var sobj = function() {};
@ -731,9 +826,9 @@ function initStaticVisibilityObj( ctor )
// internal flag cannot be modified by conventional means.
sobji.$ = function()
{
sprop_internal = true;
_self._spropInternal = true;
var val = ctor.$.apply( ctor, arguments );
sprop_internal = false;
_self._spropInternal = false;
return val;
};
@ -761,7 +856,9 @@ function initStaticVisibilityObj( ctor )
function attachStatic( ctor, members, base, inheriting )
{
var methods = members.methods,
props = members.props;
props = members.props,
_self = this
;
// "Inherit" the parent's static methods by running the parent's static
// initialization method. It is important that we do this before anything,
@ -805,7 +902,7 @@ function attachStatic( ctor, members, base, inheriting )
// to check other levels of visibility. `found` will contain the
// visibility level the property was found in, or false.
found = has.call( props[ 'public' ], prop ) && 'public';
if ( !found && sprop_internal )
if ( !found && _self._spropInternal )
{
// Check for protected/private. We only check for private
// properties if we are not currently checking the properties of
@ -898,22 +995,6 @@ function createMeta( func, cparent )
}
/**
* Returns reference to metadata for the requested class
*
* Since a reference is returned (rather than a copy), the returned object can
* be modified to alter the metadata.
*
* @param {Class} cls class from which to retrieve metadata
*
* @return {Object}
*/
exports.getMeta = function( cls )
{
return cls.___$$meta$$ || {};
}
/**
* Attaches an instance identifier to a class instance
*
@ -1014,57 +1095,6 @@ function getMethodInstance( inst, cid )
}
/**
* Determines if the class is an instance of the given type
*
* The given type can be a class, interface, trait or any other type of object.
* It may be used in place of the 'instanceof' operator and contains additional
* enhancements that the operator is unable to provide due to prototypal
* restrictions.
*
* @param {Object} type expected type
* @param {Object} instance instance to check
*
* @return {boolean} true if instance is an instance of type, otherwise false
*/
exports.isInstanceOf = function( type, instance )
{
var meta, implemented, i;
try
{
// check prototype chain (will throw an error if type is not a
// constructor (function)
if ( instance instanceof type )
{
return true;
}
}
catch ( e ) {}
// if no metadata is available, then our remaining checks cannot be
// performed
if ( !instance.__cid || !( meta = exports.getMeta( instance ) ) )
{
return false;
}
implemented = meta.implemented;
i = implemented.length;
// check implemented interfaces
while ( i-- )
{
if ( implemented[ i ] === type )
{
return true;
}
}
return false;
};
/**
* Attaches isAbstract() method to the class
*

View File

@ -22,8 +22,12 @@
* @package core
*/
var util = require( __dirname + '/util' ),
class_builder = require( __dirname + '/class_builder' )
var util = require( __dirname + '/util' ),
ClassBuilder = require( __dirname + '/ClassBuilder' ),
class_builder = ClassBuilder(
require( __dirname + '/member_builder' )
)
;
@ -111,7 +115,7 @@ module.exports.isClass = function( obj )
{
obj = obj || {};
return ( obj.prototype instanceof class_builder.ClassBase )
return ( obj.prototype instanceof ClassBuilder.ClassBase )
? true
: false
;
@ -131,7 +135,7 @@ module.exports.isClassInstance = function( obj )
{
obj = obj || {};
return ( obj instanceof class_builder.ClassBase )
return ( obj instanceof ClassBuilder.ClassBase )
? true
: false;
};
@ -150,7 +154,7 @@ module.exports.isClassInstance = function( obj )
*
* @return {boolean} true if instance is an instance of type, otherwise false
*/
module.exports.isInstanceOf = class_builder.isInstanceOf;
module.exports.isInstanceOf = ClassBuilder.isInstanceOf;
/**
@ -353,7 +357,7 @@ function createImplement( base, ifaces, cname )
function extend()
{
// set up the new class
var new_class = class_builder.build.apply( null, arguments );
var new_class = class_builder.build.apply( class_builder, arguments );
// set up some additional convenience props
setupProps( new_class );
@ -413,7 +417,7 @@ var implement = function()
// create a new class with the implemented abstract methods
var class_new = module.exports.extend( base, dest );
class_builder.getMeta( class_new ).implemented = implemented;
ClassBuilder.getMeta( class_new ).implemented = implemented;
return class_new;
}

View File

@ -24,7 +24,9 @@
var common = require( './common' ),
assert = require( 'assert' ),
builder = common.require( 'class_builder' )
builder = common.require( 'ClassBuilder' )(
common.require( 'member_builder' )
)
;

View File

@ -24,7 +24,9 @@
var common = require( './common' ),
assert = require( 'assert' ),
builder = common.require( 'class_builder' ),
builder = common.require( 'ClassBuilder' )(
common.require( 'member_builder' )
),
Class = common.require( 'class' )
FinalClass = common.require( 'class_final' )

View File

@ -24,7 +24,11 @@
var common = require( './common' ),
assert = require( 'assert' ),
builder = common.require( 'class_builder' )
ClassBuilder = common.require( 'ClassBuilder' ),
builder = ClassBuilder(
common.require( 'member_builder' )
)
;
@ -35,7 +39,7 @@ var common = require( './common' ),
*/
( function testCanRetrieveListOfReservedMembers()
{
var reserved = builder.getReservedMembers();
var reserved = ClassBuilder.getReservedMembers();
assert.ok( reserved instanceof Object,
"Can retrieve hash of reserved members"
@ -60,10 +64,10 @@ var common = require( './common' ),
var val = 'foo';
// attempt to add to list
builder.getReservedMembers().foo = val;
ClassBuilder.getReservedMembers().foo = val;
assert.notEqual(
builder.getReservedMembers().foo,
ClassBuilder.getReservedMembers().foo,
val,
"Cannot alter internal list of reserved members"
);
@ -76,7 +80,7 @@ var common = require( './common' ),
*/
( function testAllReservedMembersAreActuallyReserved()
{
var reserved = builder.getReservedMembers(),
var reserved = ClassBuilder.getReservedMembers(),
count = 0;
// test each of the reserved members
@ -126,7 +130,7 @@ var common = require( './common' ),
*/
( function testCanRetrieveListOfForcedPublicMethods()
{
var pub = builder.getForcedPublicMethods(),
var pub = ClassBuilder.getForcedPublicMethods(),
count = 0;
assert.ok( pub instanceof Object,
@ -153,10 +157,10 @@ var common = require( './common' ),
var val = 'foo';
// attempt to add to list
builder.getForcedPublicMethods().foo = val;
ClassBuilder.getForcedPublicMethods().foo = val;
assert.notEqual(
builder.getForcedPublicMethods().foo,
ClassBuilder.getForcedPublicMethods().foo,
val,
"Cannot alter internal list of forced-public methods"
);
@ -169,7 +173,7 @@ var common = require( './common' ),
*/
( function testAllForcedPublicMethodsAreForcedToPublic()
{
var pub = builder.getForcedPublicMethods();
var pub = ClassBuilder.getForcedPublicMethods();
// test each of the reserved members
for ( name in pub )

View File

@ -22,10 +22,12 @@
* @package test
*/
var common = require( './common' ),
assert = require( 'assert' ),
builder = common.require( 'class_builder' ),
fallback = common.require( 'util' ).definePropertyFallback()
var common = require( './common' ),
assert = require( 'assert' ),
fallback = common.require( 'util' ).definePropertyFallback()
builder = common.require( 'ClassBuilder' )(
common.require( 'member_builder' )
)
;

View File

@ -27,7 +27,9 @@
var common = require( './common' ),
assert = require( 'assert' ),
util = common.require( 'util' ),
builder = common.require( 'class_builder' )
builder = common.require( 'ClassBuilder' )(
common.require( 'member_builder' )
)
;

View File

@ -24,8 +24,10 @@
var common = require( './common' ),
assert = require( 'assert' ),
builder = common.require( 'class_builder' ),
warn = common.require( 'warn' )
builder = common.require( 'ClassBuilder' )(
common.require( 'member_builder' )
)
;

View File

@ -29,7 +29,7 @@ TPL_VAR='/**{CONTENT}**/'
RMTRAIL="$PATH_TOOLS/rmtrail"
# order matters
CAT_MODULES="warn prop_parser util propobj member_builder class_builder"
CAT_MODULES="warn prop_parser util propobj member_builder ClassBuilder"
CAT_MODULES="$CAT_MODULES class class_final class_abstract interface"
##