1
0
Fork 0

Properly implemented abstract methods list

closure/master
Mike Gerwitz 2010-11-14 20:30:33 -05:00
parent ad4b317955
commit 113e3b974f
2 changed files with 79 additions and 6 deletions

View File

@ -98,7 +98,18 @@ function prop_copy( props, dest, result_data )
result_data = result_data || {};
// initialize result_data
result_data.abstractMethods = result_data.abstractMethods || [];
var abstract_methods =
result_data.abstractMethods = result_data.abstractMethods || [];
// it's much faster to lookup a hash than it is to iterate through an entire
// array each time we need to find an existing abstract method
var abstract_map = {},
abstract_regen = false;
for ( var i = 0, len = abstract_methods.length; i < len; i++ )
{
var method = abstract_methods[ i ];
abstract_map[ method ] = i;
}
// copy each of the properties to the destination object
for ( property in props )
@ -112,9 +123,22 @@ function prop_copy( props, dest, result_data )
setter = ( ( getset ) ? props.__lookupSetter__( property ) : null );
// did we find an abstract method?
if ( ( prop instanceof Function ) && ( prop.abstractFlag === true ) )
if ( prop instanceof Function )
{
result_data.abstractMethods.push( property );
if ( prop.abstractFlag === true )
{
abstract_methods.push( property );
}
else
{
// if we were given a concrete method to an abstract method,
// then the method should no longer be considered abstract
if ( abstract_map[ property ] !== undefined )
{
delete abstract_methods[ abstract_map[ property ] ];
abstract_regen = true;
}
}
}
// check for getter/setter overrides
@ -163,6 +187,27 @@ function prop_copy( props, dest, result_data )
dest[ property ] = prop;
}
}
// should we regenerate the array of abstract methods (this must be done
// because the length of the array remains the same after deleting elements)
if ( abstract_regen )
{
// copy the methods into a new array by pushing them onto it, to ensure
// the length property of the array will work properly
var methods_new = [];
for ( var i = 0, len = abstract_methods.length; i < len; i++ )
{
var method = abstract_methods[ i ];
if ( method === undefined )
{
continue;
}
methods_new.push( method );
}
result_data.abstractMethods = methods_new;
}
}
@ -195,7 +240,7 @@ function extend()
// copy the given properties into the new prototype
var result_data = {
abstractMethods: base.abstractMethods || [],
abstractMethods: ( base.abstractMethods || [] ).slice()
};
prop_copy( props, prototype, result_data );
@ -281,8 +326,9 @@ function attach_abstract( func, methods )
return is_abstract;
};
// attach the list of abstract methods to the class
// attach the list of abstract methods to the class (make the copy of the
// methods to ensure that they won't be gc'd or later modified and screw up
// the value)
define_secure_prop( func, 'abstractMethods', methods );
}

View File

@ -81,6 +81,11 @@ assert.ok(
"All classes should have an isAbstract() method"
);
assert.ok(
( Foo.abstractMethods instanceof Array ),
"All classes should provide a list of their abstract methods as an array"
);
assert.equal(
Foo.isAbstract(),
false,
@ -100,6 +105,28 @@ assert.equal(
"concrete implementation for all abstract methods"
);
assert.ok(
( ( AbstractFoo.abstractMethods[ 0 ] == 'method' )
&& ( AbstractFoo.abstractMethods[ 1 ] == 'second' )
),
"Abstract classes should provide a list of their abstract methods' names"
);
assert.ok(
( ( SubAbstractFoo.abstractMethods[ 0 ] == 'method' )
&& ( SubAbstractFoo.abstractMethods[ 1 ] == undefined )
),
"Abstract subclasses should not have their concrete methods within the " +
"the abstract method list if the concrete method was abstract in " +
"its supertype"
);
assert.equal(
ConcreteFoo.abstractMethods.length,
0,
"Concrete classes should not have any abstract methods listed"
);
assert.equal(
ConcreteFoo.isAbstract(),
false,