diff --git a/lib/class.js b/lib/class.js
index 67c4bd9..c252659 100644
--- a/lib/class.js
+++ b/lib/class.js
@@ -22,14 +22,8 @@
* @package core
*/
-/**
- * Whether getters/setters are supported
- * @var {boolean}
- */
-var getset = ( Object.prototype.__defineGetter__ === undefined )
- ? false
- : true
-;
+var propCopy = require( './util' ).propCopy;
+
/**
* Whether the Object.freeze() method is available
@@ -133,7 +127,7 @@ var extend = ( function( extending )
var result_data = {
abstractMethods: ( base.abstractMethods || [] ).slice()
};
- prop_copy( props, prototype, result_data );
+ propCopy( props, prototype, result_data );
// reference to the parent prototype (for more experienced users)
prototype.parent = base.prototype;
@@ -198,203 +192,6 @@ var extend = ( function( extending )
} )();
-/**
- * Copies properties to the destination object
- *
- * If the method already exists, it will be overridden and accessible via either
- * the parent prototype or by invoking this.__super().
- *
- * The destination object is directly modified.
- *
- * The result data will be populated with information from the copy that may be
- * useful to the creation of the class (e.g. list of the abstract methods).
- *
- * @param {Object} props properties to copy
- * @param {Object} dest destination object
- * @param {Object} result_data object to store data regarding the copy in
- *
- * @return undefined
- */
-function prop_copy( props, dest, result_data )
-{
- result_data = result_data || {};
-
- // initialize result_data
- 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 )
- {
- // if the property already exists, then it's being overridden (we only
- // care about methods - properties will simply have their values
- // overwritten)
- var pre = dest[ property ],
- prop = props[ property ],
- getter = ( ( getset ) ? props.__lookupGetter__( property ) : null ),
- setter = ( ( getset ) ? props.__lookupSetter__( property ) : null );
-
- // check for getter/setter overrides
- if ( getter || setter )
- {
- if ( getter )
- {
- dest.__defineGetter__( property, getter );
- }
-
- if ( setter )
- {
- dest.__defineSetter__( property, setter );
- }
- }
- // check for method overrides
- else if ( ( pre !== undefined ) && ( pre instanceof Function ) )
- {
- var data = { abstractModified: false };
-
- dest[ property ] = method_override(
- pre,
- prop,
- property,
- abstract_map,
- abstract_methods,
- data
- );
-
- if ( data.abstractModified )
- {
- abstract_regen = true;
- }
- }
- // just copy over the property
- else
- {
- // if we were given an abstract method, add it to our list of
- // abstract methods
- if ( ( prop instanceof Function ) && ( prop.abstractFlag === true ) )
- {
- abstract_methods.push( property );
- }
-
- 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 )
- {
- result_data.abstractMethods = array_shrink( abstract_methods );
- }
-}
-
-
-/**
- * Overrides a method
- *
- * The given method must be a function or an exception will be thrown.
- *
- * @param {Function} super_method method to override
- * @param {Function} new_method method to override with
- * @param {string} name method name
- * @param {Object} abstract_map lookup table for abstract methods
- * @param {Array} abstract_methods list of abstract methods
- * @param {Object} data object in which to store result data
- *
- * @return {Function} overridden method
- */
-function method_override(
- super_method, new_method, name, abstract_map, abstract_methods, data
-)
-{
- // ensure we're overriding the method with another method
- if ( !( new_method instanceof Function ) )
- {
- throw new TypeError( "Cannot override method with non-method" );
- }
-
- // if we were given a concrete method to an abstract method,
- // then the method should no longer be considered abstract
- if ( ( abstract_map[ name ] !== undefined )
- && ( new_method.abstractFlag !== true )
- )
- {
- if ( super_method.definition instanceof Function )
- {
- // ensure the concrete definition is compatible with
- // that of its supertype
- if ( new_method.length < super_method.definition.length )
- {
- throw new Error(
- "Declaration of " + name + " must be compatiable" +
- "with that of its supertype"
- );
- }
- }
-
- delete abstract_methods[ abstract_map[ name ] ];
- data.abstractModified = true;
- }
-
- // this is the method that will be invoked when the requested
- // method is called, so note that in the context of this
- // function, `this` will represent the current class instance
- return function()
- {
- var tmp = this.__super;
-
- // assign _super temporarily for the method invocation so
- // that the method can call the parent method
- this.__super = super_method;
- var retval = new_method.apply( this, arguments );
- this.__super = tmp;
-
- return retval;
- }
-}
-
-
-/**
- * Shrinks an array, removing undefined elements
- *
- * Pushes all items onto a new array, removing undefined elements. This ensures
- * that the length of the array represents correctly the number of elements in
- * the array.
- *
- * @param {Array} items array to shrink
- *
- * @return {Array} shrunken array
- */
-function array_shrink( items )
-{
- // copy the methods into a new array by pushing them onto it, to ensure
- // the length property of the array will work properly
- var arr_new = [];
- for ( var i = 0, len = items.length; i < len; i++ )
- {
- var item = items[ i ];
- if ( item === undefined )
- {
- continue;
- }
-
- arr_new.push( item );
- }
-
- return arr_new;
-}
-
-
/**
* Sets up common properties for the provided function (class)
*
diff --git a/lib/util.js b/lib/util.js
new file mode 100644
index 0000000..11fc5e9
--- /dev/null
+++ b/lib/util.js
@@ -0,0 +1,231 @@
+/**
+ * Contains utilities functions shared by modules
+ *
+ * Copyright (C) 2010 Mike Gerwitz
+ *
+ * This file is part of ease.js.
+ *
+ * ease.js is free software: you can redistribute it and/or modify it under the
+ * terms of the GNU Lesser General Public License as published by the Free
+ * Software Foundation, either version 3 of the License, or (at your option)
+ * any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program. If not, see .
+ *
+ * @author Mike Gerwitz
+ * @package core
+ */
+
+
+/**
+ * Whether getters/setters are supported
+ * @var {boolean}
+ */
+var getset = ( Object.prototype.__defineGetter__ === undefined )
+ ? false
+ : true
+;
+
+
+/**
+ * Copies properties to the destination object
+ *
+ * If the method already exists, it will be overridden and accessible via either
+ * the parent prototype or by invoking this.__super().
+ *
+ * The destination object is directly modified.
+ *
+ * The result data will be populated with information from the copy that may be
+ * useful to the creation of the class (e.g. list of the abstract methods).
+ *
+ * @param {Object} props properties to copy
+ * @param {Object} dest destination object
+ * @param {Object} result_data object to store data regarding the copy in
+ *
+ * @return undefined
+ */
+exports.propCopy = function( props, dest, result_data )
+{
+ result_data = result_data || {};
+
+ // initialize result_data
+ 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 )
+ {
+ // if the property already exists, then it's being overridden (we only
+ // care about methods - properties will simply have their values
+ // overwritten)
+ var pre = dest[ property ],
+ prop = props[ property ],
+ getter = ( ( getset ) ? props.__lookupGetter__( property ) : null ),
+ setter = ( ( getset ) ? props.__lookupSetter__( property ) : null );
+
+ // check for getter/setter overrides
+ if ( getter || setter )
+ {
+ if ( getter )
+ {
+ dest.__defineGetter__( property, getter );
+ }
+
+ if ( setter )
+ {
+ dest.__defineSetter__( property, setter );
+ }
+ }
+ // check for method overrides
+ else if ( ( pre !== undefined ) && ( pre instanceof Function ) )
+ {
+ var data = { abstractModified: false };
+
+ dest[ property ] = method_override(
+ pre,
+ prop,
+ property,
+ abstract_map,
+ abstract_methods,
+ data
+ );
+
+ if ( data.abstractModified )
+ {
+ abstract_regen = true;
+ }
+ }
+ // just copy over the property
+ else
+ {
+ // if we were given an abstract method, add it to our list of
+ // abstract methods
+ if ( ( prop instanceof Function ) && ( prop.abstractFlag === true ) )
+ {
+ abstract_methods.push( property );
+ }
+
+ 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 )
+ {
+ result_data.abstractMethods = array_shrink( abstract_methods );
+ }
+}
+
+
+/**
+ * Overrides a method
+ *
+ * The given method must be a function or an exception will be thrown.
+ *
+ * @param {Function} super_method method to override
+ * @param {Function} new_method method to override with
+ * @param {string} name method name
+ * @param {Object} abstract_map lookup table for abstract methods
+ * @param {Array} abstract_methods list of abstract methods
+ * @param {Object} data object in which to store result data
+ *
+ * @return {Function} overridden method
+ */
+function method_override(
+ super_method, new_method, name, abstract_map, abstract_methods, data
+)
+{
+ // ensure we're overriding the method with another method
+ if ( !( new_method instanceof Function ) )
+ {
+ throw new TypeError( "Cannot override method with non-method" );
+ }
+
+ // if we were given a concrete method to an abstract method,
+ // then the method should no longer be considered abstract
+ if ( ( abstract_map[ name ] !== undefined )
+ && ( new_method.abstractFlag !== true )
+ )
+ {
+ if ( super_method.definition instanceof Function )
+ {
+ // ensure the concrete definition is compatible with
+ // that of its supertype
+ if ( new_method.length < super_method.definition.length )
+ {
+ throw new Error(
+ "Declaration of " + name + " must be compatiable" +
+ "with that of its supertype"
+ );
+ }
+ }
+
+ delete abstract_methods[ abstract_map[ name ] ];
+ data.abstractModified = true;
+ }
+
+ // this is the method that will be invoked when the requested
+ // method is called, so note that in the context of this
+ // function, `this` will represent the current class instance
+ return function()
+ {
+ var tmp = this.__super;
+
+ // assign _super temporarily for the method invocation so
+ // that the method can call the parent method
+ this.__super = super_method;
+ var retval = new_method.apply( this, arguments );
+ this.__super = tmp;
+
+ return retval;
+ }
+}
+
+
+/**
+ * Shrinks an array, removing undefined elements
+ *
+ * Pushes all items onto a new array, removing undefined elements. This ensures
+ * that the length of the array represents correctly the number of elements in
+ * the array.
+ *
+ * @param {Array} items array to shrink
+ *
+ * @return {Array} shrunken array
+ */
+function array_shrink( items )
+{
+ // copy the methods into a new array by pushing them onto it, to ensure
+ // the length property of the array will work properly
+ var arr_new = [];
+ for ( var i = 0, len = items.length; i < len; i++ )
+ {
+ var item = items[ i ];
+ if ( item === undefined )
+ {
+ continue;
+ }
+
+ arr_new.push( item );
+ }
+
+ return arr_new;
+}
+
diff --git a/test/test-util-prop-copy.js b/test/test-util-prop-copy.js
new file mode 100644
index 0000000..38e8054
--- /dev/null
+++ b/test/test-util-prop-copy.js
@@ -0,0 +1,42 @@
+/**
+ * Tests util.propCopy
+ *
+ * Copyright (C) 2010 Mike Gerwitz
+ *
+ * This file is part of ease.js.
+ *
+ * ease.js is free software: you can redistribute it and/or modify it under the
+ * terms of the GNU Lesser General Public License as published by the Free
+ * Software Foundation, either version 3 of the License, or (at your option)
+ * any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program. If not, see .
+ *
+ * @author Mike Gerwitz
+ * @package test
+ */
+
+require( './common' );
+
+var assert = require( 'assert' ),
+ propCopy = require( '../lib/util' ).propCopy;
+
+var props = {
+ one: 1,
+ two: 2,
+};
+
+var dest = {};
+
+propCopy( props, dest );
+assert.ok(
+ ( ( dest.one === props.one ) && ( dest.two === props.two ) ),
+ "All properties should be copied to the destination object"
+);
+