2011-03-27 01:57:17 -04:00
|
|
|
/**
|
|
|
|
* Handles building of classes
|
|
|
|
*
|
2013-12-20 00:49:06 -05:00
|
|
|
* Copyright (C) 2011, 2012, 2013 Mike Gerwitz
|
2011-03-27 01:57:17 -04:00
|
|
|
*
|
2013-12-22 09:37:21 -05:00
|
|
|
* This file is part of GNU ease.js.
|
2011-03-27 01:57:17 -04:00
|
|
|
*
|
2014-01-15 23:56:00 -05:00
|
|
|
* ease.js is free software: you can redistribute it and/or modify
|
|
|
|
* it under the terms of the GNU General Public License as published by
|
|
|
|
* the Free Software Foundation, either version 3 of the License, or
|
|
|
|
* (at your option) any later version.
|
2011-03-27 01:57:17 -04:00
|
|
|
*
|
2014-01-15 23:56:00 -05:00
|
|
|
* 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 General Public License for more details.
|
2011-03-27 01:57:17 -04:00
|
|
|
*
|
2014-01-15 23:56:00 -05:00
|
|
|
* You should have received a copy of the GNU General Public License
|
|
|
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
2011-03-27 01:57:17 -04:00
|
|
|
*
|
2014-01-15 23:56:00 -05:00
|
|
|
* TODO: This module is currently being tested /indirectly/ by the class
|
|
|
|
* tests. This is because of a refactoring. All of this logic used to
|
|
|
|
* be part of the class module. Test this module directly, but keep
|
|
|
|
* the existing class tests in tact for a higher-level test.
|
2011-03-27 01:57:17 -04:00
|
|
|
*/
|
|
|
|
|
2011-08-09 17:27:26 -04:00
|
|
|
var util = require( __dirname + '/util' ),
|
|
|
|
warn = require( __dirname + '/warn' ),
|
2011-07-06 19:34:35 -04:00
|
|
|
Warning = warn.Warning,
|
|
|
|
|
2011-03-27 23:04:40 -04:00
|
|
|
/**
|
2011-08-09 17:27:26 -04:00
|
|
|
* 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.
|
2011-03-27 23:16:19 -04:00
|
|
|
*
|
2011-08-09 17:27:26 -04:00
|
|
|
* This test will determine if this poor implementation exists.
|
2011-03-27 23:16:19 -04:00
|
|
|
*/
|
2011-08-09 17:27:26 -04:00
|
|
|
enum_bug = (
|
|
|
|
Object.prototype.propertyIsEnumerable.call(
|
|
|
|
{ toString: function() {} },
|
|
|
|
'toString'
|
|
|
|
) === false
|
|
|
|
)
|
|
|
|
? true
|
|
|
|
: false,
|
2011-05-12 00:05:50 -04:00
|
|
|
|
2011-03-28 18:18:40 -04:00
|
|
|
/**
|
|
|
|
* Hash of reserved members
|
|
|
|
*
|
|
|
|
* These methods cannot be defined in the class. They are for internal use
|
2011-03-28 19:17:38 -04:00
|
|
|
* only. We must check both properties and methods to ensure that neither is
|
|
|
|
* defined.
|
2011-03-28 18:18:40 -04:00
|
|
|
*
|
|
|
|
* @type {Object.<string,boolean>}
|
|
|
|
*/
|
2012-01-17 23:36:01 -05:00
|
|
|
reserved_members = {
|
|
|
|
'__initProps': true,
|
|
|
|
'constructor': true,
|
|
|
|
},
|
2011-03-28 18:21:06 -04:00
|
|
|
|
|
|
|
/**
|
2011-03-28 19:17:38 -04:00
|
|
|
* Hash of methods that must be public
|
|
|
|
*
|
|
|
|
* Notice that this is a list of /methods/, not members, because this check
|
|
|
|
* is performed only for methods. This is for performance reasons. We do not
|
|
|
|
* have a situation where we will want to check for properties as well.
|
2011-03-28 18:21:06 -04:00
|
|
|
*
|
2011-12-10 11:06:34 -05:00
|
|
|
* @type {Object.<string,boolean>}
|
2011-03-28 18:21:06 -04:00
|
|
|
*/
|
2011-03-28 19:52:16 -04:00
|
|
|
public_methods = {
|
|
|
|
'__construct': true,
|
|
|
|
'toString': true,
|
|
|
|
'__toString': true,
|
2011-08-09 17:27:26 -04:00
|
|
|
};
|
2011-03-27 23:04:40 -04:00
|
|
|
|
|
|
|
|
|
|
|
/**
|
2011-08-09 17:27:26 -04:00
|
|
|
* Initializes class builder with given member builder
|
2011-03-27 23:04:40 -04:00
|
|
|
*
|
2011-08-09 17:27:26 -04:00
|
|
|
* The 'new' keyword is not required when instantiating this constructor.
|
|
|
|
*
|
2011-08-13 23:58:08 -04:00
|
|
|
* @param {Object} member_builder member builder
|
|
|
|
*
|
2011-12-13 21:19:14 -05:00
|
|
|
* @param {VisibilityObjectFactory} visibility_factory visibility object
|
2011-08-13 23:58:08 -04:00
|
|
|
* generator
|
2011-12-13 21:19:14 -05:00
|
|
|
*
|
|
|
|
* @constructor
|
2011-03-27 23:04:40 -04:00
|
|
|
*/
|
2011-08-09 17:27:26 -04:00
|
|
|
module.exports = exports =
|
2011-08-13 23:58:08 -04:00
|
|
|
function ClassBuilder( member_builder, visibility_factory )
|
2011-08-09 17:27:26 -04:00
|
|
|
{
|
|
|
|
// allow ommitting the 'new' keyword
|
|
|
|
if ( !( this instanceof exports ) )
|
|
|
|
{
|
2011-12-13 21:19:14 -05:00
|
|
|
// module.exports for Closure Compiler
|
|
|
|
return new module.exports( member_builder, visibility_factory );
|
2011-08-09 17:27:26 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Used for building class members
|
|
|
|
* @type {Object}
|
|
|
|
*/
|
|
|
|
this._memberBuilder = member_builder;
|
|
|
|
|
2011-08-13 23:58:08 -04:00
|
|
|
/**
|
|
|
|
* Generates visibility object
|
|
|
|
* @type {VisibilityObjectFactory}
|
|
|
|
*/
|
|
|
|
this._visFactory = visibility_factory;
|
|
|
|
|
2011-08-09 17:27:26 -04:00
|
|
|
|
|
|
|
/**
|
|
|
|
* 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;
|
|
|
|
};
|
2011-03-27 01:57:17 -04:00
|
|
|
|
|
|
|
|
2011-03-27 23:04:40 -04:00
|
|
|
/**
|
|
|
|
* Default class implementation
|
|
|
|
*
|
|
|
|
* @return undefined
|
|
|
|
*/
|
|
|
|
exports.ClassBase = function Class() {};
|
|
|
|
|
2011-08-09 17:27:26 -04:00
|
|
|
|
2011-04-13 22:38:05 -04:00
|
|
|
/**
|
|
|
|
* Default static property method
|
|
|
|
*
|
|
|
|
* This simply returns undefined, signifying that the property was not found.
|
|
|
|
*
|
|
|
|
* @param {string} prop requested property
|
|
|
|
*
|
|
|
|
* @return {undefined}
|
|
|
|
*/
|
2011-04-13 23:45:26 -04:00
|
|
|
exports.ClassBase.$ = function( prop, val )
|
2011-04-13 22:38:05 -04:00
|
|
|
{
|
2011-04-13 23:45:26 -04:00
|
|
|
if ( val !== undefined )
|
|
|
|
{
|
|
|
|
throw ReferenceError(
|
|
|
|
"Cannot set value of undeclared static property '" + prop + "'"
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
2011-04-13 22:38:05 -04:00
|
|
|
return undefined;
|
|
|
|
};
|
|
|
|
|
2011-03-27 23:04:40 -04:00
|
|
|
|
2011-03-28 18:58:23 -04:00
|
|
|
/**
|
|
|
|
* Returns a hash of the reserved members
|
|
|
|
*
|
|
|
|
* The returned object is a copy of the original. It cannot be used to modify
|
|
|
|
* the internal list of reserved members.
|
|
|
|
*
|
2011-03-28 19:17:38 -04:00
|
|
|
* @return {Object.<string,boolean>} reserved members
|
2011-03-28 18:58:23 -04:00
|
|
|
*/
|
|
|
|
exports.getReservedMembers = function()
|
|
|
|
{
|
|
|
|
// return a copy of the reserved members
|
2011-03-28 19:01:58 -04:00
|
|
|
return util.clone( reserved_members, true );
|
2011-03-28 18:58:23 -04:00
|
|
|
};
|
|
|
|
|
|
|
|
|
2011-03-28 19:17:38 -04:00
|
|
|
/**
|
|
|
|
* Returns a hash of the forced-public methods
|
|
|
|
*
|
|
|
|
* The returned object is a copy of the original. It cannot be used to modify
|
|
|
|
* the internal list of reserved members.
|
|
|
|
*
|
|
|
|
* @return {Object.<string,boolean>} forced-public methods
|
|
|
|
*/
|
|
|
|
exports.getForcedPublicMethods = function()
|
|
|
|
{
|
|
|
|
return util.clone( public_methods, true );
|
|
|
|
};
|
|
|
|
|
|
|
|
|
2011-08-09 17:27:26 -04:00
|
|
|
/**
|
|
|
|
* 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.
|
|
|
|
*
|
2011-12-13 21:19:14 -05:00
|
|
|
* @param {Function|Object} cls class from which to retrieve metadata
|
2011-08-09 17:27:26 -04:00
|
|
|
*
|
2011-12-13 21:19:14 -05:00
|
|
|
* @return {__class_meta}
|
2011-08-09 17:27:26 -04:00
|
|
|
*/
|
|
|
|
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;
|
|
|
|
|
2011-11-15 22:22:24 -05:00
|
|
|
if ( !( type && instance ) )
|
|
|
|
{
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2011-08-09 17:27:26 -04:00
|
|
|
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;
|
|
|
|
};
|
|
|
|
|
|
|
|
|
2011-03-27 23:16:19 -04:00
|
|
|
/**
|
|
|
|
* 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.
|
|
|
|
*
|
2011-12-13 21:19:14 -05:00
|
|
|
* @param {Function|Object} _ parent or definition object
|
|
|
|
* @param {Object=} __ definition object if parent was provided
|
|
|
|
*
|
|
|
|
* @return {Function} extended class
|
2011-03-27 23:16:19 -04:00
|
|
|
*/
|
2011-12-13 21:19:14 -05:00
|
|
|
exports.prototype.build = function extend( _, __ )
|
2011-03-27 23:04:40 -04:00
|
|
|
{
|
2011-03-27 23:16:19 -04:00
|
|
|
// ensure we'll be permitted to instantiate abstract classes for the base
|
2011-08-09 17:27:26 -04:00
|
|
|
this._extending = true;
|
2011-03-27 23:16:19 -04:00
|
|
|
|
|
|
|
var args = Array.prototype.slice.call( arguments ),
|
|
|
|
props = args.pop() || {},
|
|
|
|
base = args.pop() || exports.ClassBase,
|
2011-12-15 22:58:23 -05:00
|
|
|
prototype = this._getBase( base ),
|
2011-03-27 23:16:19 -04:00
|
|
|
cname = '',
|
|
|
|
|
2011-08-09 17:27:26 -04:00
|
|
|
prop_init = this._memberBuilder.initMembers(),
|
|
|
|
members = this._memberBuilder.initMembers( prototype ),
|
2011-04-10 22:32:46 -04:00
|
|
|
static_members = {
|
2011-08-09 17:27:26 -04:00
|
|
|
methods: this._memberBuilder.initMembers(),
|
|
|
|
props: this._memberBuilder.initMembers(),
|
2011-12-10 11:06:34 -05:00
|
|
|
},
|
2011-03-27 23:16:19 -04:00
|
|
|
|
|
|
|
abstract_methods =
|
|
|
|
util.clone( exports.getMeta( base ).abstractMethods )
|
|
|
|
|| { __length: 0 }
|
2014-02-02 23:28:09 -05:00
|
|
|
|
|
|
|
virtual_members =
|
|
|
|
util.clone( exports.getMeta( base ).virtualMembers )
|
|
|
|
|| {}
|
2011-03-27 23:16:19 -04:00
|
|
|
;
|
|
|
|
|
2011-05-22 11:11:18 -04:00
|
|
|
// prevent extending final classes
|
|
|
|
if ( base.___$$final$$ === true )
|
|
|
|
{
|
|
|
|
throw Error(
|
|
|
|
"Cannot extend final class " +
|
|
|
|
( base.___$$meta$$.name || '(anonymous)' )
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
2011-03-27 23:16:19 -04:00
|
|
|
// grab the name, if one was provided
|
|
|
|
if ( cname = props.__name )
|
2011-03-27 23:04:40 -04:00
|
|
|
{
|
2011-03-27 23:16:19 -04:00
|
|
|
// we no longer need it
|
|
|
|
delete props.__name;
|
|
|
|
}
|
2011-03-27 23:04:40 -04:00
|
|
|
|
2011-03-27 23:16:19 -04:00
|
|
|
// IE has problems with toString()
|
|
|
|
if ( enum_bug )
|
|
|
|
{
|
|
|
|
if ( props.toString !== Object.prototype.toString )
|
2011-03-27 23:04:40 -04:00
|
|
|
{
|
2011-03-27 23:16:19 -04:00
|
|
|
props.__toString = props.toString;
|
2011-03-27 23:04:40 -04:00
|
|
|
}
|
2011-03-27 23:16:19 -04:00
|
|
|
}
|
2011-03-27 23:04:40 -04:00
|
|
|
|
2011-03-27 23:16:19 -04:00
|
|
|
// increment class identifier
|
2011-08-09 17:27:26 -04:00
|
|
|
this._classId++;
|
2011-03-27 23:16:19 -04:00
|
|
|
|
2014-01-23 23:43:34 -05:00
|
|
|
// build the various class components (XXX: this is temporary; needs
|
2011-03-27 23:16:19 -04:00
|
|
|
// refactoring)
|
2011-07-06 19:34:35 -04:00
|
|
|
try
|
|
|
|
{
|
2011-08-09 17:27:26 -04:00
|
|
|
this.buildMembers( props,
|
|
|
|
this._classId,
|
2011-07-06 19:34:35 -04:00
|
|
|
base,
|
|
|
|
prop_init,
|
2014-02-02 22:59:20 -05:00
|
|
|
{
|
|
|
|
all: members,
|
|
|
|
'abstract': abstract_methods,
|
|
|
|
'static': static_members,
|
2014-02-02 23:28:09 -05:00
|
|
|
'virtual': virtual_members,
|
2014-02-02 22:59:20 -05:00
|
|
|
},
|
2011-07-06 19:34:35 -04:00
|
|
|
function( inst )
|
|
|
|
{
|
|
|
|
return new_class.___$$svis$$;
|
|
|
|
}
|
|
|
|
);
|
|
|
|
}
|
|
|
|
catch ( e )
|
|
|
|
{
|
|
|
|
// intercept warnings /only/
|
|
|
|
if ( e instanceof Warning )
|
|
|
|
{
|
|
|
|
warn.handle( e );
|
|
|
|
}
|
|
|
|
else
|
2011-05-30 23:03:08 -04:00
|
|
|
{
|
2011-07-06 19:34:35 -04:00
|
|
|
throw e;
|
2011-05-30 23:03:08 -04:00
|
|
|
}
|
2011-07-06 19:34:35 -04:00
|
|
|
}
|
2011-03-27 23:16:19 -04:00
|
|
|
|
|
|
|
// reference to the parent prototype (for more experienced users)
|
|
|
|
prototype.___$$parent$$ = base.prototype;
|
|
|
|
|
|
|
|
// set up the new class
|
2011-08-09 17:27:26 -04:00
|
|
|
var new_class = this.createCtor( cname, abstract_methods, members );
|
2011-03-27 23:16:19 -04:00
|
|
|
|
2011-04-05 23:11:25 -04:00
|
|
|
// closure to hold static initialization to be used later by subtypes
|
2011-12-13 21:19:14 -05:00
|
|
|
initStaticVisibilityObj( new_class );
|
2011-04-10 22:32:46 -04:00
|
|
|
var staticInit = function( ctor, inheriting )
|
2011-04-05 23:11:25 -04:00
|
|
|
{
|
2011-04-10 22:32:46 -04:00
|
|
|
attachStatic( ctor, static_members, base, inheriting );
|
2011-04-05 23:11:25 -04:00
|
|
|
}
|
2011-04-10 22:32:46 -04:00
|
|
|
staticInit( new_class, false );
|
2011-04-05 23:11:25 -04:00
|
|
|
|
2011-08-13 23:58:08 -04:00
|
|
|
this._attachPropInit(
|
|
|
|
prototype, prop_init, members, new_class, this._classId
|
|
|
|
);
|
2011-03-27 23:16:19 -04:00
|
|
|
|
2012-01-19 23:21:04 -05:00
|
|
|
new_class.prototype = prototype;
|
|
|
|
new_class.prototype.constructor = new_class;
|
|
|
|
new_class.___$$props$$ = prop_init;
|
|
|
|
new_class.___$$methods$$ = members;
|
|
|
|
new_class.___$$sinit$$ = staticInit;
|
2011-03-27 23:16:19 -04:00
|
|
|
|
2011-05-22 11:11:18 -04:00
|
|
|
attachFlags( new_class, props );
|
|
|
|
|
2011-05-22 13:57:56 -04:00
|
|
|
validateAbstract( new_class, cname, abstract_methods );
|
|
|
|
|
2011-04-05 22:07:13 -04:00
|
|
|
// We reduce the overall cost of this definition by defining it on the
|
|
|
|
// prototype rather than during instantiation. While this does increase the
|
|
|
|
// amount of time it takes to access the property through the prototype
|
|
|
|
// chain, it takes much more time to define the property in this manner.
|
|
|
|
// Therefore, we can save a substantial amount of time by defining it on the
|
|
|
|
// prototype rather than on each new instance via __initProps().
|
Fixed __self assignment for FF
This little experience was rather frustrating. Indeed, it would imply that
the static implementation (at least, accessing protected and private static
members) was always broken in FF. I should be a bit more diligent in my testing.
Or perhaps it broke in a more recent version of FF, which is more likely. The
problem seems to be that we used defineSecureProp() for an assignment to the
actual class, then later properly assigned it to class.___$$svis$$.
Of course, defineSecureProp() makes it read-only, so this failed, causing
an improper assignment for __self, breaking the implementation. As such,
this probably broke in newer versions of FF and worked properly in older versions.
More concerningly is that the implementations clearly differ between Chromium
and Firefox. It may be that Firefox checks the prototype chain, whereas Chromium
(v8, specifically) will simply write to that object, ignoring that the property
further down the prototype chain is read-only.
2011-12-04 00:32:16 -05:00
|
|
|
util.defineSecureProp( prototype, '__self', new_class.___$$svis$$ );
|
2011-04-05 22:07:13 -04:00
|
|
|
|
2011-03-27 23:16:19 -04:00
|
|
|
// create internal metadata for the new class
|
|
|
|
var meta = createMeta( new_class, base );
|
|
|
|
meta.abstractMethods = abstract_methods;
|
2014-02-02 23:28:09 -05:00
|
|
|
meta.virtualMembers = virtual_members;
|
2011-03-27 23:16:19 -04:00
|
|
|
meta.name = cname;
|
|
|
|
|
2011-03-29 00:08:49 -04:00
|
|
|
attachAbstract( new_class, abstract_methods );
|
2011-08-09 17:27:26 -04:00
|
|
|
attachId( new_class, this._classId );
|
2011-03-29 00:08:49 -04:00
|
|
|
|
2011-03-27 23:16:19 -04:00
|
|
|
// we're done with the extension process
|
2011-08-09 17:27:26 -04:00
|
|
|
this._extending = false;
|
2011-03-27 23:16:19 -04:00
|
|
|
|
2011-03-29 00:15:16 -04:00
|
|
|
return new_class;
|
2011-03-27 23:16:19 -04:00
|
|
|
};
|
2011-03-27 23:04:40 -04:00
|
|
|
|
|
|
|
|
2011-12-15 22:58:23 -05:00
|
|
|
exports.prototype._getBase = function( base )
|
|
|
|
{
|
|
|
|
var type = ( typeof base );
|
|
|
|
|
|
|
|
switch ( type )
|
|
|
|
{
|
|
|
|
// constructor (we could also check to ensure that the return value of
|
|
|
|
// the constructor is an object, but that is not our concern)
|
|
|
|
case 'function':
|
|
|
|
return new base();
|
|
|
|
|
|
|
|
// we can use objects as the prototype directly
|
|
|
|
case 'object':
|
|
|
|
return base;
|
|
|
|
}
|
|
|
|
|
|
|
|
// scalars
|
|
|
|
throw TypeError( 'Must extend from Class, constructor or object' );
|
|
|
|
};
|
|
|
|
|
|
|
|
|
2011-08-09 17:27:26 -04:00
|
|
|
exports.prototype.buildMembers = function buildMembers(
|
2014-02-02 22:59:20 -05:00
|
|
|
props, class_id, base, prop_init, memberdest, staticInstLookup
|
2011-08-09 17:27:26 -04:00
|
|
|
)
|
|
|
|
{
|
|
|
|
var hasOwn = Array.prototype.hasOwnProperty,
|
|
|
|
defs = {},
|
|
|
|
|
2014-02-02 23:28:09 -05:00
|
|
|
// TODO: there does not seem to be tests for these guys; perhaps
|
|
|
|
// this can be rectified with the reflection implementation
|
2014-02-02 22:59:20 -05:00
|
|
|
members = memberdest.all,
|
|
|
|
abstract_methods = memberdest['abstract'],
|
|
|
|
static_members = memberdest['static'],
|
2014-02-02 23:28:09 -05:00
|
|
|
virtual_members = memberdest['virtual'],
|
2014-02-02 22:59:20 -05:00
|
|
|
|
2011-08-09 17:27:26 -04:00
|
|
|
smethods = static_members.methods,
|
|
|
|
sprops = static_members.props,
|
|
|
|
|
2014-01-30 23:13:52 -05:00
|
|
|
// holds member builder state
|
|
|
|
state = {},
|
|
|
|
|
2011-08-09 17:27:26 -04:00
|
|
|
_self = this
|
|
|
|
;
|
|
|
|
|
|
|
|
util.propParse( props, {
|
|
|
|
each: function( name, value, keywords )
|
|
|
|
{
|
|
|
|
// disallow use of our internal __initProps() method
|
|
|
|
if ( reserved_members[ name ] === true )
|
|
|
|
{
|
2011-12-10 11:06:34 -05:00
|
|
|
throw Error( name + " is reserved" );
|
2011-08-09 17:27:26 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
// if a member was defined multiple times in the same class
|
2014-01-23 23:43:34 -05:00
|
|
|
// declaration, throw an error (unless the `weak' keyword is
|
|
|
|
// provided, which exists to accomodate this situation)
|
|
|
|
if ( hasOwn.call( defs, name )
|
|
|
|
&& !( keywords['weak'] || defs[ name ].weak )
|
|
|
|
)
|
2011-08-09 17:27:26 -04:00
|
|
|
{
|
|
|
|
throw Error(
|
|
|
|
"Cannot redefine method '" + name + "' in same declaration"
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
// keep track of the definitions (only during class declaration)
|
|
|
|
// to catch duplicates
|
2014-01-23 23:43:34 -05:00
|
|
|
defs[ name ] = keywords;
|
2011-08-09 17:27:26 -04:00
|
|
|
},
|
|
|
|
|
|
|
|
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
|
|
|
|
);
|
|
|
|
},
|
|
|
|
|
2011-10-29 08:08:02 -04:00
|
|
|
getset: function( name, get, set, keywords )
|
2011-08-09 17:27:26 -04:00
|
|
|
{
|
'this' now properly binds to the private member object of the instance for getters/setters
Getters/setters did not get much attention during the initial development of
ease.js, simply because there was such a strong focus on pre-ES5
compatibility---ease.js was created for a project that strongly required it.
Given that, getters/setters were not used, since those are ES5 features. As
such, I find that two things have happened:
1. There was little incentive to provide a proper implementation; even though
I noticed the issues during the initial development, they were left
unresolved and were then forgotten about as the project lay dormant for a
while.
2. The project was dormant because it was working as intended (sure, there
are still things on the TODO-list feature-wise). Since getters/setters were
unused in the project for which ease.js was created, the bug was never
found and so never addressed.
That said, I now am using getters/setters in a project with ease.js and noticed
a very odd bug that could not be explained by that project's implementation.
Sure enough, it was an ease.js issue and this commit resolves it.
Now, there is more to be said about this commit. Mainly, it should be noted that
MemberBuilder.buildGetterSetter, when compared with its method counterpart
(buildMethod) is incomplete---it does not properly address overrides, the
abstract keyword, proxies or the possibility of method hiding. This is certainly
something that I will get to, but I want to get this fix out as soon as I can.
Since overriding ES5 getters/setters (rather than explicit methods) is more
likely to be a rarity, and since a partial fix is better than no fix, this will
likely be tagged immediately and a further fix will follow in the (hopefully
near) future.
(This is an interesting example of how glaring bugs manage to slip through the
cracks, even when the developer is initially aware of them.)
2013-01-19 20:54:30 -05:00
|
|
|
var dest = ( keywordStatic( keywords ) ) ? smethods : members,
|
|
|
|
is_static = keywordStatic( keywords ),
|
|
|
|
instLookup = ( ( is_static )
|
|
|
|
? staticInstLookup
|
|
|
|
: exports.getMethodInstance
|
|
|
|
);
|
2011-08-09 17:27:26 -04:00
|
|
|
|
2011-10-29 08:23:59 -04:00
|
|
|
_self._memberBuilder.buildGetterSetter(
|
'this' now properly binds to the private member object of the instance for getters/setters
Getters/setters did not get much attention during the initial development of
ease.js, simply because there was such a strong focus on pre-ES5
compatibility---ease.js was created for a project that strongly required it.
Given that, getters/setters were not used, since those are ES5 features. As
such, I find that two things have happened:
1. There was little incentive to provide a proper implementation; even though
I noticed the issues during the initial development, they were left
unresolved and were then forgotten about as the project lay dormant for a
while.
2. The project was dormant because it was working as intended (sure, there
are still things on the TODO-list feature-wise). Since getters/setters were
unused in the project for which ease.js was created, the bug was never
found and so never addressed.
That said, I now am using getters/setters in a project with ease.js and noticed
a very odd bug that could not be explained by that project's implementation.
Sure enough, it was an ease.js issue and this commit resolves it.
Now, there is more to be said about this commit. Mainly, it should be noted that
MemberBuilder.buildGetterSetter, when compared with its method counterpart
(buildMethod) is incomplete---it does not properly address overrides, the
abstract keyword, proxies or the possibility of method hiding. This is certainly
something that I will get to, but I want to get this fix out as soon as I can.
Since overriding ES5 getters/setters (rather than explicit methods) is more
likely to be a rarity, and since a partial fix is better than no fix, this will
likely be tagged immediately and a further fix will follow in the (hopefully
near) future.
(This is an interesting example of how glaring bugs manage to slip through the
cracks, even when the developer is initially aware of them.)
2013-01-19 20:54:30 -05:00
|
|
|
dest, null, name, get, set, keywords, instLookup, class_id, base
|
2011-08-09 17:27:26 -04:00
|
|
|
);
|
|
|
|
},
|
|
|
|
|
|
|
|
method: function( name, func, is_abstract, keywords )
|
|
|
|
{
|
|
|
|
var is_static = keywordStatic( keywords ),
|
|
|
|
dest = ( is_static ) ? smethods : members,
|
|
|
|
instLookup = ( is_static )
|
|
|
|
? staticInstLookup
|
2011-08-31 00:24:19 -04:00
|
|
|
: exports.getMethodInstance
|
2011-08-09 17:27:26 -04:00
|
|
|
;
|
|
|
|
|
|
|
|
// constructor check
|
|
|
|
if ( public_methods[ name ] === true )
|
|
|
|
{
|
|
|
|
if ( keywords[ 'protected' ] || keywords[ 'private' ] )
|
|
|
|
{
|
|
|
|
throw TypeError(
|
|
|
|
name + " must be public"
|
|
|
|
);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2014-01-26 00:08:42 -05:00
|
|
|
var used = _self._memberBuilder.buildMethod(
|
2011-08-09 17:27:26 -04:00
|
|
|
dest, null, name, func, keywords, instLookup,
|
2014-01-30 23:13:52 -05:00
|
|
|
class_id, base, state
|
2011-08-09 17:27:26 -04:00
|
|
|
);
|
|
|
|
|
2014-01-26 00:08:42 -05:00
|
|
|
// do nothing more if we didn't end up using this definition
|
|
|
|
// (this may be the case, for example, with weak members)
|
|
|
|
if ( !used )
|
|
|
|
{
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
// note the concrete method check; this ensures that weak
|
|
|
|
// abstract methods will not count if a concrete method of the
|
|
|
|
// smae name has already been seen
|
2011-08-09 17:27:26 -04:00
|
|
|
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--;
|
|
|
|
}
|
2014-02-02 23:28:09 -05:00
|
|
|
|
|
|
|
if ( keywords['virtual'] )
|
|
|
|
{
|
|
|
|
virtual_members[ name ] = true;
|
|
|
|
}
|
2011-08-09 17:27:26 -04:00
|
|
|
},
|
|
|
|
} );
|
2014-01-30 23:13:52 -05:00
|
|
|
|
|
|
|
// process accumulated member state
|
|
|
|
this._memberBuilder.end( state );
|
2011-08-09 17:27:26 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
|
2011-05-22 13:57:56 -04:00
|
|
|
/**
|
|
|
|
* Validates abstract class requirements
|
|
|
|
*
|
|
|
|
* @param {function()} ctor class
|
|
|
|
* @param {string} cname class name
|
2011-12-13 21:19:14 -05:00
|
|
|
* @param {{__length}} abstract_methods object containing abstract methods
|
2011-05-22 13:57:56 -04:00
|
|
|
*
|
|
|
|
* @return {undefined}
|
|
|
|
*/
|
|
|
|
function validateAbstract( ctor, cname, abstract_methods )
|
|
|
|
{
|
|
|
|
if ( ctor.___$$abstract$$ )
|
|
|
|
{
|
|
|
|
if ( abstract_methods.__length === 0 )
|
|
|
|
{
|
|
|
|
throw TypeError(
|
|
|
|
"Class " + ( cname || "(anonymous)" ) + " was declared as " +
|
|
|
|
"abstract, but contains no abstract members"
|
|
|
|
);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
if ( abstract_methods.__length > 0 )
|
|
|
|
{
|
|
|
|
throw TypeError(
|
|
|
|
"Class " + ( cname || "(anonymous)" ) + " contains abstract " +
|
|
|
|
"members and must therefore be declared abstract"
|
|
|
|
);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2011-03-27 23:16:19 -04:00
|
|
|
/**
|
|
|
|
* 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
|
|
|
|
*/
|
2011-08-09 17:27:26 -04:00
|
|
|
exports.prototype.createCtor = function( cname, abstract_methods, members )
|
2011-03-27 23:16:19 -04:00
|
|
|
{
|
|
|
|
// concrete class
|
|
|
|
if ( abstract_methods.__length === 0 )
|
|
|
|
{
|
2011-08-09 17:27:26 -04:00
|
|
|
return this.createConcreteCtor( cname, members );
|
2011-03-27 23:16:19 -04:00
|
|
|
}
|
|
|
|
// abstract class
|
|
|
|
else
|
2011-03-27 23:04:40 -04:00
|
|
|
{
|
2011-08-09 17:27:26 -04:00
|
|
|
return this.createAbstractCtor( cname );
|
2011-03-27 23:16:19 -04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
* 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
|
|
|
|
*/
|
2011-08-09 17:27:26 -04:00
|
|
|
exports.prototype.createConcreteCtor = function( cname, members )
|
2011-03-27 23:16:19 -04:00
|
|
|
{
|
2011-08-09 17:27:26 -04:00
|
|
|
var args = null,
|
|
|
|
_self = this;
|
2011-03-27 23:16:19 -04:00
|
|
|
|
2011-12-13 21:19:14 -05:00
|
|
|
/**
|
|
|
|
* 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 constructor instances rather than
|
|
|
|
* invoking the toString() method
|
|
|
|
*
|
|
|
|
* @constructor
|
|
|
|
*
|
|
|
|
* Suppressing due to complaints for using __initProps
|
|
|
|
* @suppress {checkTypes}
|
|
|
|
*/
|
2011-12-06 18:20:41 -05:00
|
|
|
function ClassInstance()
|
2011-03-27 23:16:19 -04:00
|
|
|
{
|
2011-03-29 22:04:54 -04:00
|
|
|
if ( !( this instanceof ClassInstance ) )
|
2011-03-27 23:04:40 -04:00
|
|
|
{
|
2011-03-27 23:16:19 -04:00
|
|
|
// store arguments to be passed to constructor and
|
|
|
|
// instantiate new object
|
|
|
|
args = arguments;
|
2011-03-29 22:04:54 -04:00
|
|
|
return new ClassInstance();
|
2011-03-27 23:16:19 -04:00
|
|
|
}
|
2011-03-27 23:04:40 -04:00
|
|
|
|
2011-05-10 23:30:32 -04:00
|
|
|
initInstance( this );
|
2011-03-27 23:16:19 -04:00
|
|
|
this.__initProps();
|
|
|
|
|
2011-05-10 23:21:12 -04:00
|
|
|
// 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.
|
2011-08-09 17:27:26 -04:00
|
|
|
if ( _self._extending )
|
2011-05-10 23:21:12 -04:00
|
|
|
{
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2011-05-10 23:34:50 -04:00
|
|
|
// generate and store unique instance id
|
2011-08-09 17:27:26 -04:00
|
|
|
attachInstanceId( this, ++_self._instanceId );
|
2011-05-10 23:34:50 -04:00
|
|
|
|
2014-01-23 00:34:15 -05:00
|
|
|
// handle internal trait initialization logic, if provided
|
|
|
|
if ( typeof this.___$$tctor$$ === 'function' )
|
|
|
|
{
|
|
|
|
this.___$$tctor$$.call( this );
|
|
|
|
}
|
|
|
|
|
2011-03-27 23:16:19 -04:00
|
|
|
// call the constructor, if one was provided
|
2011-11-03 21:56:15 -04:00
|
|
|
if ( typeof this.__construct === 'function' )
|
2011-03-27 23:16:19 -04:00
|
|
|
{
|
|
|
|
// 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 ) );
|
2011-03-27 23:04:40 -04:00
|
|
|
}
|
2011-03-27 23:16:19 -04:00
|
|
|
|
2014-01-23 00:34:15 -05:00
|
|
|
args = null;
|
|
|
|
|
2011-03-27 23:16:19 -04:00
|
|
|
// 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'
|
|
|
|
) ) )
|
2011-03-27 23:04:40 -04:00
|
|
|
{
|
2011-03-27 23:16:19 -04:00
|
|
|
// use __toString if available (see enum_bug), otherwise use
|
|
|
|
// our own defaults
|
|
|
|
this.toString = members[ 'public' ].__toString
|
|
|
|
|| ( ( cname )
|
|
|
|
? function()
|
|
|
|
{
|
|
|
|
return '#<' + cname + '>';
|
|
|
|
}
|
|
|
|
: function()
|
|
|
|
{
|
|
|
|
return '#<anonymous>';
|
|
|
|
}
|
|
|
|
)
|
2011-03-27 23:04:40 -04:00
|
|
|
;
|
2011-03-27 23:16:19 -04:00
|
|
|
}
|
2012-01-19 23:21:04 -05:00
|
|
|
|
2011-03-27 23:16:19 -04:00
|
|
|
};
|
|
|
|
|
|
|
|
// provide a more intuitive string representation
|
2011-03-29 22:04:54 -04:00
|
|
|
ClassInstance.toString = ( cname )
|
2011-03-27 23:16:19 -04:00
|
|
|
? function() { return cname; }
|
|
|
|
: function() { return '(Class)'; }
|
|
|
|
;
|
|
|
|
|
2011-03-29 22:04:54 -04:00
|
|
|
return ClassInstance;
|
2011-03-27 23:16:19 -04:00
|
|
|
}
|
2011-03-27 23:04:40 -04:00
|
|
|
|
2011-03-27 23:16:19 -04:00
|
|
|
|
|
|
|
/**
|
|
|
|
* 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
|
|
|
|
*/
|
2011-08-09 17:27:26 -04:00
|
|
|
exports.prototype.createAbstractCtor = function( cname )
|
2011-03-27 23:16:19 -04:00
|
|
|
{
|
2011-08-09 17:27:26 -04:00
|
|
|
var _self = this;
|
|
|
|
|
2011-03-27 23:16:19 -04:00
|
|
|
var __abstract_self = function()
|
|
|
|
{
|
2011-08-09 17:27:26 -04:00
|
|
|
if ( !_self._extending )
|
2011-03-27 23:16:19 -04:00
|
|
|
{
|
|
|
|
throw Error(
|
|
|
|
"Abstract class " + ( cname || '(anonymous)' ) +
|
|
|
|
" cannot be instantiated"
|
|
|
|
);
|
2011-03-27 23:04:40 -04:00
|
|
|
}
|
2011-03-27 23:16:19 -04:00
|
|
|
};
|
|
|
|
|
|
|
|
__abstract_self.toString = ( cname )
|
|
|
|
? function()
|
|
|
|
{
|
|
|
|
return cname;
|
|
|
|
}
|
|
|
|
: function()
|
|
|
|
{
|
|
|
|
return '(AbstractClass)';
|
|
|
|
}
|
|
|
|
;
|
|
|
|
|
|
|
|
return __abstract_self;
|
|
|
|
}
|
2011-03-27 23:04:40 -04:00
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Attaches __initProps() method to the class prototype
|
|
|
|
*
|
|
|
|
* The __initProps() method will initialize class properties for that instance,
|
|
|
|
* ensuring that their data is not shared with other instances (this is not a
|
|
|
|
* problem with primitive data types).
|
|
|
|
*
|
|
|
|
* The method will also initialize any parent properties (recursive) to ensure
|
|
|
|
* that subtypes do not have a referencing issue, and subtype properties take
|
|
|
|
* precedence over those of the parent.
|
|
|
|
*
|
|
|
|
* @param {Object} prototype prototype to attach method to
|
|
|
|
* @param {Object} properties properties to initialize
|
|
|
|
*
|
|
|
|
* @param {{public: Object, protected: Object, private: Object}} members
|
|
|
|
*
|
2011-12-10 11:06:34 -05:00
|
|
|
* @param {function()} ctor class
|
2011-05-10 19:54:23 -04:00
|
|
|
* @param {number} cid class id
|
2011-04-05 22:07:13 -04:00
|
|
|
*
|
2011-03-27 23:04:40 -04:00
|
|
|
* @return {undefined}
|
|
|
|
*/
|
2011-08-13 23:58:08 -04:00
|
|
|
exports.prototype._attachPropInit = function(
|
|
|
|
prototype, properties, members, ctor, cid
|
|
|
|
)
|
2011-03-27 23:04:40 -04:00
|
|
|
{
|
2011-08-13 23:58:08 -04:00
|
|
|
var _self = this;
|
|
|
|
|
2011-03-27 23:04:40 -04:00
|
|
|
util.defineSecureProp( prototype, '__initProps', function( inherit )
|
|
|
|
{
|
2011-04-02 10:58:26 -04:00
|
|
|
// defaults to false
|
2011-03-27 23:04:40 -04:00
|
|
|
inherit = !!inherit;
|
|
|
|
|
2011-12-15 22:58:23 -05:00
|
|
|
var iid = this.__iid,
|
|
|
|
parent = prototype.___$$parent$$;
|
2011-03-27 23:04:40 -04:00
|
|
|
|
|
|
|
// first initialize the parent's properties, so that ours will overwrite
|
|
|
|
// them
|
2011-12-15 22:58:23 -05:00
|
|
|
var parent_init = parent && parent.__initProps;
|
2011-11-03 21:56:15 -04:00
|
|
|
if ( typeof parent_init === 'function' )
|
2011-03-27 23:04:40 -04:00
|
|
|
{
|
|
|
|
// call the parent prop_init, letting it know that it's been
|
|
|
|
// inherited so that it does not initialize private members or
|
|
|
|
// perform other unnecessary tasks
|
|
|
|
parent_init.call( this, true );
|
|
|
|
}
|
|
|
|
|
|
|
|
// this will return our property proxy, if supported by our environment,
|
|
|
|
// otherwise just a normal object with everything merged in
|
2011-08-13 23:58:08 -04:00
|
|
|
var inst_props = _self._visFactory.createPropProxy(
|
2011-03-27 23:04:40 -04:00
|
|
|
this, this.___$$vis$$, properties[ 'public' ]
|
|
|
|
);
|
|
|
|
|
2011-04-02 10:58:26 -04:00
|
|
|
// Copies all public and protected members into inst_props and stores
|
|
|
|
// private in a separate object, which adds inst_props to its prototype
|
|
|
|
// chain and is returned. This is stored in a property referenced by the
|
|
|
|
// class id, so that the private members can be swapped on each method
|
|
|
|
// request, depending on calling context.
|
2011-08-13 23:58:08 -04:00
|
|
|
var vis = this.___$$vis$$[ cid ] = _self._visFactory.setup(
|
2011-03-27 23:04:40 -04:00
|
|
|
inst_props, properties, members
|
|
|
|
);
|
2011-03-30 00:55:27 -04:00
|
|
|
|
|
|
|
// provide a means to access the actual instance (rather than the
|
|
|
|
// property/visibility object) internally (this will translate to
|
2011-04-03 11:57:15 -04:00
|
|
|
// this.__inst from within a method), but only if we're on our final
|
2011-03-30 23:31:46 -04:00
|
|
|
// object (not a parent)
|
2011-04-05 22:09:02 -04:00
|
|
|
if ( !inherit )
|
2011-03-30 23:31:46 -04:00
|
|
|
{
|
2011-04-03 11:57:15 -04:00
|
|
|
util.defineSecureProp( vis, '__inst', this );
|
2011-03-30 23:31:46 -04:00
|
|
|
}
|
2011-03-27 23:04:40 -04:00
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2011-08-13 23:58:08 -04:00
|
|
|
/**
|
|
|
|
* Determines if the given keywords should result in a static member
|
|
|
|
*
|
|
|
|
* A member will be considered static if the static or const keywords are given.
|
|
|
|
*
|
|
|
|
* @param {Object} keywords keywords to scan
|
|
|
|
*
|
2011-12-13 21:19:14 -05:00
|
|
|
* @return {boolean} true if to be static, otherwise false
|
2011-08-13 23:58:08 -04:00
|
|
|
*/
|
|
|
|
function keywordStatic( keywords )
|
|
|
|
{
|
|
|
|
return ( keywords[ 'static' ] || keywords[ 'const' ] )
|
|
|
|
? true
|
|
|
|
: false
|
|
|
|
;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2011-05-09 23:09:32 -04:00
|
|
|
/**
|
|
|
|
* Creates and populates the static visibility object
|
|
|
|
*
|
2011-12-13 21:19:14 -05:00
|
|
|
* @param {Function} ctor class
|
2011-05-09 23:09:32 -04:00
|
|
|
*
|
|
|
|
* @return {undefined}
|
|
|
|
*/
|
2011-05-30 20:10:52 -04:00
|
|
|
function initStaticVisibilityObj( ctor )
|
2011-05-09 23:09:32 -04:00
|
|
|
{
|
2011-08-09 17:27:26 -04:00
|
|
|
var _self = this;
|
|
|
|
|
2011-12-13 21:19:14 -05:00
|
|
|
/**
|
|
|
|
* the object will simply be another layer in the prototype chain to
|
|
|
|
* prevent protected/private members from being mixed in with the public
|
|
|
|
*
|
|
|
|
* @constructor
|
|
|
|
*/
|
2011-05-09 23:09:32 -04:00
|
|
|
var sobj = function() {};
|
2011-05-10 19:54:23 -04:00
|
|
|
sobj.prototype = ctor;
|
2011-05-09 23:09:32 -04:00
|
|
|
|
|
|
|
var sobji = new sobj();
|
|
|
|
|
|
|
|
// override __self on the instance's visibility object, giving internal
|
|
|
|
// methods access to the restricted static methods
|
2011-05-10 19:54:23 -04:00
|
|
|
ctor.___$$svis$$ = sobji;
|
2011-05-12 00:05:50 -04:00
|
|
|
|
|
|
|
// Override the class-level accessor method to allow the system to know we
|
|
|
|
// are within a method. An internal flag is necessary, rather than using an
|
|
|
|
// argument or binding, because those two options are exploitable. An
|
|
|
|
// internal flag cannot be modified by conventional means.
|
|
|
|
sobji.$ = function()
|
|
|
|
{
|
2011-08-09 17:27:26 -04:00
|
|
|
_self._spropInternal = true;
|
2011-05-12 00:05:50 -04:00
|
|
|
var val = ctor.$.apply( ctor, arguments );
|
2011-08-09 17:27:26 -04:00
|
|
|
_self._spropInternal = false;
|
2011-05-12 00:05:50 -04:00
|
|
|
|
|
|
|
return val;
|
|
|
|
};
|
2011-05-09 23:09:32 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
|
2011-04-04 23:07:01 -04:00
|
|
|
/**
|
|
|
|
* Attaches static members to a constructor (class)
|
|
|
|
*
|
2011-04-10 22:32:46 -04:00
|
|
|
* Static methods will be assigned to the constructor itself. Properties, on the
|
|
|
|
* other hand, will be assigned to ctor.$. The reason for this is because JS
|
|
|
|
* engines pre-ES5 support no means of sharing references to primitives. Static
|
|
|
|
* properties of subtypes should share references to the static properties of
|
|
|
|
* their parents.
|
|
|
|
*
|
|
|
|
* @param {function()} ctor class
|
|
|
|
* @param {Object} members static members
|
|
|
|
* @param {function()} base base class inheriting from
|
|
|
|
* @param {boolean} inheriting true if inheriting static members,
|
|
|
|
* otherwise false (setting own static
|
|
|
|
* members)
|
2011-04-04 23:07:01 -04:00
|
|
|
*
|
|
|
|
* @return {undefined}
|
|
|
|
*/
|
2011-04-10 22:32:46 -04:00
|
|
|
function attachStatic( ctor, members, base, inheriting )
|
2011-04-04 23:07:01 -04:00
|
|
|
{
|
2011-04-10 22:32:46 -04:00
|
|
|
var methods = members.methods,
|
2011-08-09 17:27:26 -04:00
|
|
|
props = members.props,
|
|
|
|
_self = this
|
|
|
|
;
|
2011-04-10 22:32:46 -04:00
|
|
|
|
2011-05-11 18:36:49 -04:00
|
|
|
// "Inherit" the parent's static methods by running the parent's static
|
|
|
|
// initialization method. It is important that we do this before anything,
|
|
|
|
// because this will recursively inherit all members in order, permitting
|
|
|
|
// overrides.
|
2011-04-05 23:11:25 -04:00
|
|
|
var baseinit = base.___$$sinit$$;
|
|
|
|
if ( baseinit )
|
|
|
|
{
|
2011-04-10 22:32:46 -04:00
|
|
|
baseinit( ctor, true );
|
|
|
|
}
|
|
|
|
|
|
|
|
// initialize static property if not yet defined
|
|
|
|
if ( !inheriting )
|
|
|
|
{
|
2011-04-13 22:38:05 -04:00
|
|
|
ctor.___$$sprops$$ = props;
|
2011-04-10 22:32:46 -04:00
|
|
|
|
2011-04-13 23:06:24 -04:00
|
|
|
// provide a method to access static properties
|
2011-05-11 20:53:43 -04:00
|
|
|
util.defineSecureProp( ctor, '$', function( prop, val )
|
2011-04-13 22:38:05 -04:00
|
|
|
{
|
2011-04-13 23:06:24 -04:00
|
|
|
// we use hasOwnProperty to ensure that undefined values will not
|
|
|
|
// cause us to continue checking the parent, thereby potentially
|
|
|
|
// failing to set perfectly legal values
|
2011-05-12 00:05:50 -04:00
|
|
|
var has = Object.prototype.hasOwnProperty,
|
|
|
|
found = false,
|
2011-04-13 23:35:54 -04:00
|
|
|
|
|
|
|
// Determine if we were invoked in the context of a class. If
|
|
|
|
// so, use that. Otherwise, use ourself.
|
2011-05-13 00:55:09 -04:00
|
|
|
context = ( this.___$$sprops$$ ) ? this : ctor,
|
|
|
|
|
|
|
|
// We are in a subtype if the context does not match the
|
|
|
|
// constructor. This works because, when invoked for the first
|
|
|
|
// time, this method is not bound to the constructor. In such a
|
|
|
|
// case, we default the context to the constructor and pass that
|
|
|
|
// down the line to each recursive call. Therefore, recursive
|
|
|
|
// calls to subtypes will have a context mismatch.
|
|
|
|
in_subtype = ( context !== ctor )
|
2011-04-13 23:35:54 -04:00
|
|
|
;
|
2011-04-13 23:06:24 -04:00
|
|
|
|
2011-05-12 00:05:50 -04:00
|
|
|
// Attempt to locate the property. First, we check public. If not
|
|
|
|
// available and we are internal (within a method), we can move on
|
|
|
|
// 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';
|
2011-08-09 17:27:26 -04:00
|
|
|
if ( !found && _self._spropInternal )
|
2011-05-12 00:05:50 -04:00
|
|
|
{
|
2011-05-13 00:55:09 -04:00
|
|
|
// Check for protected/private. We only check for private
|
|
|
|
// properties if we are not currently checking the properties of
|
|
|
|
// a subtype. This works because the context is passed to each
|
|
|
|
// recursive call.
|
|
|
|
found = has.call( props[ 'protected' ], prop ) && 'protected'
|
|
|
|
|| !in_subtype
|
|
|
|
&& has.call( props[ 'private' ], prop ) && 'private'
|
|
|
|
;
|
2011-05-12 00:05:50 -04:00
|
|
|
}
|
|
|
|
|
2011-04-13 23:06:24 -04:00
|
|
|
// if we don't own the property, let the parent(s) handle it
|
2011-05-12 00:05:50 -04:00
|
|
|
if ( found === false )
|
2011-04-13 23:06:24 -04:00
|
|
|
{
|
2011-11-19 00:09:49 -05:00
|
|
|
// TODO: This check is simple, but quick. It may be worth
|
|
|
|
// setting a flag on the class during definition to specify if
|
|
|
|
// it's extending from a non-class base.
|
|
|
|
return ( base.__cid && base.$ || exports.ClassBase.$ ).apply(
|
|
|
|
context, arguments
|
|
|
|
);
|
2011-04-13 23:06:24 -04:00
|
|
|
}
|
|
|
|
|
2011-05-19 19:48:47 -04:00
|
|
|
var prop_item = props[ found ][ prop ];
|
|
|
|
|
2011-04-13 23:06:24 -04:00
|
|
|
// if a value was provided, this method should be treated as a
|
2011-04-13 23:52:25 -04:00
|
|
|
// setter rather than a getter (we *must* test using
|
|
|
|
// arguments.length to ensure that setting to undefined works)
|
|
|
|
if ( arguments.length > 1 )
|
2011-04-13 23:06:24 -04:00
|
|
|
{
|
2011-05-19 19:48:47 -04:00
|
|
|
// if const, disallow modification
|
|
|
|
if ( prop_item[ 1 ][ 'const' ] )
|
|
|
|
{
|
|
|
|
throw TypeError(
|
|
|
|
"Cannot modify constant property '" + prop + "'"
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
prop_item[ 0 ] = val;
|
2011-04-13 23:35:54 -04:00
|
|
|
return context;
|
2011-04-13 23:06:24 -04:00
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
// return the value
|
2011-05-19 19:48:47 -04:00
|
|
|
return prop_item[ 0 ];
|
2011-04-13 23:06:24 -04:00
|
|
|
}
|
2011-05-11 20:53:43 -04:00
|
|
|
} );
|
2011-04-05 23:11:25 -04:00
|
|
|
}
|
|
|
|
|
2011-04-14 20:28:42 -04:00
|
|
|
// copy over public static methods
|
2011-04-10 22:32:46 -04:00
|
|
|
util.copyTo( ctor, methods[ 'public' ], true );
|
2011-05-11 17:56:48 -04:00
|
|
|
util.copyTo( ctor.___$$svis$$, methods[ 'protected' ], true );
|
2011-05-11 20:10:10 -04:00
|
|
|
|
|
|
|
// private methods should not be inherited by subtypes
|
|
|
|
if ( !inheriting )
|
|
|
|
{
|
|
|
|
util.copyTo( ctor.___$$svis$$, methods[ 'private' ], true );
|
|
|
|
}
|
2011-04-04 23:07:01 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
|
2011-03-27 23:04:40 -04:00
|
|
|
/**
|
|
|
|
* Initializes class metadata for the given class
|
|
|
|
*
|
2011-12-13 21:19:14 -05:00
|
|
|
* @param {Function} func class to initialize metadata for
|
|
|
|
* @param {Function} cparent class parent
|
2011-03-27 23:04:40 -04:00
|
|
|
*
|
|
|
|
* @return {undefined}
|
2011-12-13 21:19:14 -05:00
|
|
|
*
|
|
|
|
* Suppressed due to warnings for use of __cid
|
|
|
|
* @suppress {checkTypes}
|
2011-03-27 23:04:40 -04:00
|
|
|
*/
|
|
|
|
function createMeta( func, cparent )
|
|
|
|
{
|
|
|
|
var id = func.__cid,
|
|
|
|
parent_meta = ( ( cparent.__cid )
|
|
|
|
? exports.getMeta( cparent )
|
|
|
|
: undefined
|
|
|
|
);
|
|
|
|
|
|
|
|
// copy the parent prototype's metadata if it exists (inherit metadata)
|
|
|
|
if ( parent_meta )
|
|
|
|
{
|
|
|
|
func.___$$meta$$ = util.clone( parent_meta, true );
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
// create empty
|
|
|
|
func.___$$meta$$ = {
|
|
|
|
implemented: [],
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
|
|
|
// store the metadata in the prototype as well (inconsiderable overhead;
|
|
|
|
// it's just a reference)
|
|
|
|
func.prototype.___$$meta$$ = func.___$$meta$$;
|
|
|
|
|
|
|
|
return func.___$$meta$$;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Attaches an instance identifier to a class instance
|
|
|
|
*
|
|
|
|
* @param {Object} instance class instance
|
|
|
|
* @param {number} iid instance id
|
|
|
|
*
|
|
|
|
* @return {undefined}
|
|
|
|
*/
|
|
|
|
function attachInstanceId( instance, iid )
|
|
|
|
{
|
|
|
|
util.defineSecureProp( instance, '__iid', iid );
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Initializes class instance
|
|
|
|
*
|
2011-05-10 23:30:32 -04:00
|
|
|
* This process will create the instance visibility object that will contain
|
|
|
|
* private and protected members. The class instance is part of the prototype
|
|
|
|
* chain. This will be passed to all methods when invoked, permitting them to
|
|
|
|
* access the private and protected members while keeping them encapsulated.
|
2011-03-27 23:04:40 -04:00
|
|
|
*
|
|
|
|
* For each instance, there is always a base. The base will contain a proxy to
|
|
|
|
* the public members on the instance itself. The base will also contain all
|
|
|
|
* protected members.
|
|
|
|
*
|
|
|
|
* Atop the base object is a private member object, with the base as its
|
|
|
|
* prototype. There exists a private member object for the instance itself and
|
|
|
|
* one for each supertype. This is stored by the class id (cid) as the key. This
|
|
|
|
* permits the private member object associated with the class of the method
|
|
|
|
* call to be bound to that method. For example, if a parent method is called,
|
|
|
|
* that call must be invoked in the context of the parent, so the private
|
|
|
|
* members of the parent must be made available.
|
|
|
|
*
|
|
|
|
* The resulting structure looks something like this:
|
|
|
|
* class_instance = { iid: { cid: {} } }
|
|
|
|
*
|
|
|
|
* @param {Object} instance instance to initialize
|
|
|
|
*
|
|
|
|
* @return {undefined}
|
|
|
|
*/
|
2011-05-10 23:30:32 -04:00
|
|
|
function initInstance( instance )
|
2011-03-27 23:04:40 -04:00
|
|
|
{
|
2011-12-13 21:19:14 -05:00
|
|
|
/** @constructor */
|
2011-03-27 23:04:40 -04:00
|
|
|
var prot = function() {};
|
|
|
|
prot.prototype = instance;
|
|
|
|
|
|
|
|
// add the visibility objects to the data object for this class instance
|
|
|
|
instance.___$$vis$$ = new prot();
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Attaches partially applied isInstanceOf() method to class instance
|
|
|
|
*
|
|
|
|
* @param {Object} instance class instance to attach method to
|
|
|
|
*
|
|
|
|
* @return {undefined}
|
|
|
|
*/
|
|
|
|
function attachInstanceOf( instance )
|
|
|
|
{
|
|
|
|
var method = function( type )
|
|
|
|
{
|
|
|
|
return module.exports.isInstanceOf( type, instance );
|
|
|
|
};
|
|
|
|
|
2011-10-30 11:56:54 -04:00
|
|
|
// TODO: To improve performance (defineSecureProp can be costly), simply
|
|
|
|
// define a normal prop and freeze the class afterward. The class shouldn't
|
|
|
|
// have any mutable methods.
|
2011-03-27 23:04:40 -04:00
|
|
|
util.defineSecureProp( instance, 'isInstanceOf', method );
|
|
|
|
util.defineSecureProp( instance, 'isA', method );
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Returns the instance object associated with the given method
|
|
|
|
*
|
|
|
|
* The instance object contains the protected members. This object can be passed
|
|
|
|
* as the context when calling a method in order to give that method access to
|
|
|
|
* those members.
|
|
|
|
*
|
|
|
|
* One level above the instance object on the prototype chain is the object
|
|
|
|
* containing the private members. This is swappable, depending on the class id
|
|
|
|
* associated with the provided method call. This allows methods that were not
|
|
|
|
* overridden by the subtype to continue to use the private members of the
|
|
|
|
* supertype.
|
|
|
|
*
|
|
|
|
* @param {function()} inst instance that the method is being called from
|
|
|
|
* @param {number} cid class id
|
|
|
|
*
|
2011-12-10 11:06:34 -05:00
|
|
|
* @return {Object|null} instance object if found, otherwise null
|
2011-12-13 21:19:14 -05:00
|
|
|
*
|
|
|
|
* @suppress {checkTypes}
|
2011-03-27 23:04:40 -04:00
|
|
|
*/
|
2011-08-31 00:24:19 -04:00
|
|
|
exports.getMethodInstance = function( inst, cid )
|
2011-03-27 23:04:40 -04:00
|
|
|
{
|
|
|
|
var iid = inst.__iid,
|
|
|
|
data = inst.___$$vis$$;
|
|
|
|
|
|
|
|
return ( iid && data )
|
|
|
|
? data[ cid ]
|
2011-05-30 23:03:08 -04:00
|
|
|
: null
|
2011-03-27 23:04:40 -04:00
|
|
|
;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2011-03-29 00:08:49 -04:00
|
|
|
/**
|
|
|
|
* Attaches isAbstract() method to the class
|
|
|
|
*
|
|
|
|
* @param {Function} func function (class) to attach method to
|
|
|
|
* @param {Array} methods abstract method names
|
|
|
|
*
|
|
|
|
* @return {undefined}
|
|
|
|
*/
|
|
|
|
function attachAbstract( func, methods )
|
|
|
|
{
|
|
|
|
var is_abstract = ( methods.__length > 0 ) ? true: false;
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Returns whether the class contains abstract methods (and is therefore
|
|
|
|
* abstract)
|
|
|
|
*
|
2011-12-13 21:19:14 -05:00
|
|
|
* @return {boolean} true if class is abstract, otherwise false
|
2011-03-29 00:08:49 -04:00
|
|
|
*/
|
|
|
|
util.defineSecureProp( func, 'isAbstract', function()
|
|
|
|
{
|
|
|
|
return is_abstract;
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2011-03-29 00:15:16 -04:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Attaches the unique id to the class and its prototype
|
|
|
|
*
|
|
|
|
* The unique identifier is used internally to match a class and its instances
|
|
|
|
* with the class metadata. Exposing the id breaks encapsulation to a degree,
|
|
|
|
* but is a lesser evil when compared to exposing all metadata.
|
|
|
|
*
|
|
|
|
* @param {function()} ctor constructor (class) to attach method to
|
|
|
|
* @param {number} id id to assign
|
|
|
|
*
|
|
|
|
* @return {undefined}
|
|
|
|
*/
|
|
|
|
function attachId( ctor, id )
|
|
|
|
{
|
|
|
|
util.defineSecureProp( ctor, '__cid', id );
|
|
|
|
util.defineSecureProp( ctor.prototype, '__cid', id );
|
|
|
|
}
|
|
|
|
|
2011-05-22 11:11:18 -04:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Sets class flags
|
|
|
|
*
|
2011-12-13 21:19:14 -05:00
|
|
|
* @param {Function} ctor class to flag
|
|
|
|
* @param {Object} props class properties
|
2011-05-22 11:11:18 -04:00
|
|
|
*
|
|
|
|
* @return {undefined}
|
|
|
|
*/
|
|
|
|
function attachFlags( ctor, props )
|
|
|
|
{
|
|
|
|
ctor.___$$final$$ = !!( props.___$$final$$ );
|
2011-05-22 13:57:56 -04:00
|
|
|
ctor.___$$abstract$$ = !!( props.___$$abstract$$ );
|
2011-05-22 11:11:18 -04:00
|
|
|
|
|
|
|
// The properties are no longer needed. Set to undefined rather than delete
|
|
|
|
// (v8 performance)
|
2011-05-22 13:57:56 -04:00
|
|
|
props.___$$final$$ = props.___$$abstract$$ = undefined;
|
2011-05-22 11:11:18 -04:00
|
|
|
}
|
|
|
|
|