2011-03-27 01:57:17 -04:00
|
|
|
/**
|
|
|
|
* Handles building of classes
|
|
|
|
*
|
2017-01-02 23:28:28 -05:00
|
|
|
* Copyright (C) 2011, 2012, 2013, 2014, 2015, 2016, 2017
|
|
|
|
* Free Software Foundation, Inc.
|
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
|
|
|
*/
|
|
|
|
|
2014-04-20 21:11:30 -04:00
|
|
|
var util = require( './util' ),
|
2014-06-11 21:49:00 -04:00
|
|
|
Warning = require( './warn' ).Warning,
|
2014-07-06 23:10:03 -04:00
|
|
|
Symbol = require( './util/Symbol' ),
|
2011-07-06 19:34:35 -04:00
|
|
|
|
Alias `constructor` member to `__construct`
This allows ease.js classes to mimic the structure of ES6 classes, which use
`constructor` to denote the constructor. This patch simply aliases it to
`__construct`, which ease.js handles as it would normally.
To that note, since the ES6 `class` keyword is purely syntatic sugar around
the prototype model, there is not much benefit to using it over ease.js if
benefits of ease.js are still desired, since the member definition syntax is
a feature of object literals:
```
// ease.js using ES6
let Person = Class(
{
_name: '',
// note that __construct still works as well
constructor( name ) {
this._name = ''+name;
},
sayHi() {
return "Hi, I'm " + this.getName();
},
// keywords still work as expected
'protected getName'() {
return this._name;
}
} );
// ES6 using `class` keyword
class Person
{
// note that ES6 will _not_ make this private
_name: '',
constructor( name ) {
this._name = ''+name;
},
sayHi() {
return "Hi, I'm " + this.getName();
}
// keywords unsupported (you'd have to use Symbols)
getName() {
return this._name;
}
}
// ES3/5 ease.js
var Person = Class(
{
_name: '',
__construct: function( name ) {
this._name = ''+name;
},
sayHi: function() {
return "Hi, I'm " + this._name;
},
'protected getName': function() {
return this._name;
}
} );
```
As you can see, the only change between writing ES6-style method definitions
is the syntax; all keywords and other features continue to work as expected.
2015-09-15 00:10:07 -04:00
|
|
|
parseKeywords = require( './prop_parser' ).parseKeywords,
|
|
|
|
|
2014-03-10 01:34:31 -04:00
|
|
|
hasOwn = Object.prototype.hasOwnProperty,
|
|
|
|
|
|
|
|
|
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
|
|
|
|
*
|
Alias `constructor` member to `__construct`
This allows ease.js classes to mimic the structure of ES6 classes, which use
`constructor` to denote the constructor. This patch simply aliases it to
`__construct`, which ease.js handles as it would normally.
To that note, since the ES6 `class` keyword is purely syntatic sugar around
the prototype model, there is not much benefit to using it over ease.js if
benefits of ease.js are still desired, since the member definition syntax is
a feature of object literals:
```
// ease.js using ES6
let Person = Class(
{
_name: '',
// note that __construct still works as well
constructor( name ) {
this._name = ''+name;
},
sayHi() {
return "Hi, I'm " + this.getName();
},
// keywords still work as expected
'protected getName'() {
return this._name;
}
} );
// ES6 using `class` keyword
class Person
{
// note that ES6 will _not_ make this private
_name: '',
constructor( name ) {
this._name = ''+name;
},
sayHi() {
return "Hi, I'm " + this.getName();
}
// keywords unsupported (you'd have to use Symbols)
getName() {
return this._name;
}
}
// ES3/5 ease.js
var Person = Class(
{
_name: '',
__construct: function( name ) {
this._name = ''+name;
},
sayHi: function() {
return "Hi, I'm " + this._name;
},
'protected getName': function() {
return this._name;
}
} );
```
As you can see, the only change between writing ES6-style method definitions
is the syntax; all keywords and other features continue to work as expected.
2015-09-15 00:10:07 -04:00
|
|
|
* These methods cannot be defined in the class; they are for internal
|
|
|
|
* use 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,
|
Alias `constructor` member to `__construct`
This allows ease.js classes to mimic the structure of ES6 classes, which use
`constructor` to denote the constructor. This patch simply aliases it to
`__construct`, which ease.js handles as it would normally.
To that note, since the ES6 `class` keyword is purely syntatic sugar around
the prototype model, there is not much benefit to using it over ease.js if
benefits of ease.js are still desired, since the member definition syntax is
a feature of object literals:
```
// ease.js using ES6
let Person = Class(
{
_name: '',
// note that __construct still works as well
constructor( name ) {
this._name = ''+name;
},
sayHi() {
return "Hi, I'm " + this.getName();
},
// keywords still work as expected
'protected getName'() {
return this._name;
}
} );
// ES6 using `class` keyword
class Person
{
// note that ES6 will _not_ make this private
_name: '',
constructor( name ) {
this._name = ''+name;
},
sayHi() {
return "Hi, I'm " + this.getName();
}
// keywords unsupported (you'd have to use Symbols)
getName() {
return this._name;
}
}
// ES3/5 ease.js
var Person = Class(
{
_name: '',
__construct: function( name ) {
this._name = ''+name;
},
sayHi: function() {
return "Hi, I'm " + this._name;
},
'protected getName': function() {
return this._name;
}
} );
```
As you can see, the only change between writing ES6-style method definitions
is the syntax; all keywords and other features continue to work as expected.
2015-09-15 00:10:07 -04:00
|
|
|
},
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Hash of aliased members
|
|
|
|
*
|
|
|
|
* These are members that alias to another. Ideally, this should be a
|
|
|
|
* very small list. It is useful for introducing features without
|
|
|
|
* deprecating old.
|
|
|
|
*
|
|
|
|
* @type {Object.<string,string>}
|
|
|
|
*/
|
|
|
|
aliased_members = {
|
|
|
|
'constructor': '__construct',
|
2012-01-17 23:36:01 -05:00
|
|
|
},
|
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,
|
Initial implementation of parameterized traits
This is an important feature to permit trait reuse without excessive
subtyping---composition over inheritance. For example, consider that you
have a `HttpPlainAuth` trait that adds authentication support to some
transport layer. Without parameterized traits, you have two options:
1. Expose setters for credentials
2. Trait closure
3. Extend the trait (not yet supported)
The first option seems like a simple solution:
```javascript
Transport.use( HttpPlainAuth )()
.setUser( 'username', 'password' )
.send( ... );
```
But we are now in the unfortunate situation that our initialization
procedure has changed. This, for one, means that authentication logic must
be added to anything that instantiates classes that mix in `HttpPlainAuth`.
We'll explore that in more detail momentarily.
More concerning with this first method is that, not only have we prohibited
immutability, but we have also made our code reliant on *invocation order*;
`setUser` must be called before `send`. What if we have other traits mixed
in that have similar conventions? Normally, this is the type of problem that
would be solved with a builder, but would we want every configurable trait
to return a new `Transport` instance? All that on top of littering the
API---what a mess!
The second option is to store configuration data outside of the Trait acting
as a closure:
```javascript
var _user, _pass;
function setCredentials( user, pass ) { _user = user; _pass = pass; }
Trait( 'HttpPlainAuth', { /* use _user and _pass */ } )
```
There are a number of problems with this; the most apparent is that, in this
case, the variables `_user` and `_pass` act in place of static fields---all
instances will share that data, and if the data is modified, it will affect
all instances; you are therefore relying on external state, and mutability
is forced upon you. You are also left with an awkward `setCredentials` call
that is disjoint from `HttpPlainAuth`.
The other notable issue arises if you did want to support instance-specific
credentials. You would have to use ease.js' internal identifiers (which is
undocumented and subject to change in future versions), and would likely
accumulate garbage data as mixin instances are deallocated, since ECMAScript
does not have destructor support.
To recover from memory leaks, you could instead create a trait generator:
```javascript
function createHttpPlainAuth( user, pass )
{
return Trait( { /* ... */ } );
}
```
This uses the same closure concept, but generates new traits at runtime.
This has various implications depending on your engine, and may thwart
future ease.js optimization attempts.
The third (which will be supported in the near future) is prohibitive: we'll
add many unnecessary traits that are a nightmare to develop and maintain.
Parameterized traits are similar in spirit to option three, but without
creating new traits each call: traits now support being passed configuration
data at the time of mixin that will be passed to every new instance:
```javascript
Transport.use( HttpPlainAuth( user, pass ) )()
.send( ... );
```
Notice now how the authentication configuration is isolated to the actual
mixin, *prior to* instantiation; the caller performing instantiation need
not be aware of this mixin, and so the construction logic can remain wholly
generic for all `Transport` types.
It further allows for a convenient means of providing useful, reusable
exports:
```javascript
module.exports = {
ServerFooAuth: HttpPlainAuth( userfoo, passfoo ),
ServerBarAuth: HttpPlainAuth( userbar, passbar ),
ServerFooTransport: Transport.use( module.exports.ServerFooAuth ),
// ...
};
var module = require( 'foo' );
// dynamic auth
Transport.use( foo.ServerFooAuth )().send( ... );
// or predefined classes
module.ServerFooTransport().send( ... );
```
Note that, in all of the above cases, the initialization logic is
unchanged---the caller does not need to be aware of any authentication
mechanism, nor should the caller care of its existence.
So how do you create parameterized traits? You need only define a `__mixin`
method:
Trait( 'HttpPlainAuth', { __mixin: function( user, pass ) { ... } } );
The method `__mixin` will be invoked upon instantiation of the class into
which a particular configuration of `HttpPlainAuth` is mixed into; it was
named differently from `__construct` to make clear that (a) traits cannot be
instantiated and (b) the constructor cannot be overridden by traits.
A configured parameterized trait is said to be an *argument trait*; each
argument trait's configuration is discrete, as was demonstrated by
`ServerFooAuth` and `ServerBarAuth` above. Once a parameterized trait is
configured, its arguments are stored within the argument trait and those
arguments are passed, by reference, to `__mixin`. Since any mixed in trait
can have its own `__mixin` method, this permits traits to have their own
initialization logic without the need for awkward overrides or explicit
method calls.
2014-05-28 23:37:36 -04:00
|
|
|
'__mixin': true,
|
2011-03-28 19:52:16 -04:00
|
|
|
'toString': true,
|
|
|
|
'__toString': true,
|
2014-07-06 23:10:03 -04:00
|
|
|
},
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Symbol used to encapsulate internal data
|
|
|
|
*
|
|
|
|
* Note that this is intentionally generated *outside* the ClassBuilder
|
|
|
|
* instance; this ensures that it is properly encapsulated and will not
|
|
|
|
* be exposed on the Classbuilder instance (which would defeat the
|
|
|
|
* purpose).
|
|
|
|
*/
|
|
|
|
_priv = Symbol()
|
|
|
|
;
|
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 =
|
2016-07-15 00:15:11 -04:00
|
|
|
function ClassBuilder( warn_handler, member_builder, visibility_factory, ector )
|
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
|
2014-06-11 21:49:00 -04:00
|
|
|
return new module.exports(
|
2016-07-15 00:15:11 -04:00
|
|
|
warn_handler, member_builder, visibility_factory, ector
|
2014-06-11 21:49:00 -04:00
|
|
|
);
|
2011-08-09 17:27:26 -04:00
|
|
|
}
|
|
|
|
|
2014-06-11 21:49:00 -04:00
|
|
|
/**
|
|
|
|
* Determines how warnings should be handled
|
|
|
|
* @type {WarningHandler}
|
|
|
|
*/
|
|
|
|
this._warnHandler = warn_handler;
|
|
|
|
|
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;
|
|
|
|
|
2016-07-15 00:15:11 -04:00
|
|
|
/**
|
|
|
|
* Error constructor generator
|
|
|
|
* @type {ErrorCtor}
|
|
|
|
*/
|
|
|
|
this._ector = ector;
|
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;
|
|
|
|
|
|
|
|
/**
|
|
|
|
* 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() {};
|
|
|
|
|
2014-03-06 01:50:00 -05:00
|
|
|
// the base class has the class identifier 0
|
|
|
|
util.defineSecureProp( exports.ClassBase, '__cid', 0 );
|
|
|
|
|
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
|
|
|
*
|
2014-07-07 00:11:58 -04:00
|
|
|
* @return {__class_meta} or null if unavailable
|
2011-08-09 17:27:26 -04:00
|
|
|
*/
|
|
|
|
exports.getMeta = function( cls )
|
|
|
|
{
|
2014-07-07 00:11:58 -04:00
|
|
|
return ( cls[ _priv ] || {} ).meta || null;
|
2011-08-09 17:27:26 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
|
2015-05-12 23:30:14 -04:00
|
|
|
/**
|
|
|
|
* Allow OBJ to assume an identity as a class
|
|
|
|
*
|
|
|
|
* This is useful to use objects in situations where classes are expected,
|
|
|
|
* as it eliminates the need for handling of special cases.
|
|
|
|
*
|
|
|
|
* This is intended for internal use---there are no guarantees as to what
|
|
|
|
* methods ease.js may expect that a class-like object incorporate. That
|
|
|
|
* guarantee may exist in the future, but until then, stay away.
|
|
|
|
*
|
|
|
|
* @param {Object} obj object to masquerade as an ease.js class
|
|
|
|
*
|
|
|
|
* @return {Object} OBJ
|
|
|
|
*/
|
|
|
|
exports.masquerade = function( obj )
|
|
|
|
{
|
|
|
|
// XXX: this is duplicated; abstract
|
|
|
|
util.defineSecureProp( obj, _priv, {} );
|
|
|
|
|
|
|
|
createMeta( obj, exports.ClassBase );
|
|
|
|
return obj;
|
|
|
|
};
|
|
|
|
|
|
|
|
|
2011-08-09 17:27:26 -04:00
|
|
|
/**
|
|
|
|
* 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;
|
|
|
|
}
|
|
|
|
|
2014-04-22 00:24:21 -04:00
|
|
|
// defer check to type, falling back to a more primitive check; this
|
|
|
|
// also allows extending ease.js' type system
|
|
|
|
return !!( type.__isInstanceOf || _instChk )( type, instance );
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2017-11-04 01:07:06 -04:00
|
|
|
/**
|
|
|
|
* Determines whether the provided object is a class created through ease.js
|
|
|
|
*
|
|
|
|
* @param {Object} obj object to test
|
|
|
|
*
|
|
|
|
* @return {boolean} true if class (created through ease.js), otherwise
|
|
|
|
* false
|
|
|
|
*/
|
|
|
|
module.exports.isClass = function( obj )
|
|
|
|
{
|
|
|
|
obj = obj || _dummyclass;
|
|
|
|
|
|
|
|
var meta = module.exports.getMeta( obj );
|
|
|
|
|
|
|
|
// TODO: we're checking a random field on the meta object; do something
|
|
|
|
// proper
|
|
|
|
return ( ( ( meta !== null ) && meta.implemented )
|
|
|
|
|| ( obj.prototype instanceof module.exports.ClassBase ) )
|
|
|
|
? true
|
|
|
|
: false
|
|
|
|
;
|
|
|
|
};
|
|
|
|
|
|
|
|
|
2014-04-22 00:24:21 -04:00
|
|
|
/**
|
|
|
|
* Wrapper around ECMAScript instanceof check
|
|
|
|
*
|
|
|
|
* This will not throw an error if TYPE is not a function.
|
|
|
|
*
|
|
|
|
* Note that a try/catch is used instead of checking first to see if TYPE is
|
|
|
|
* a function; this is due to the implementation of, notably, IE, which
|
|
|
|
* allows instanceof to be used on some DOM objects with typeof `object'.
|
|
|
|
* These same objects have typeof `function' in other browsers.
|
|
|
|
*
|
|
|
|
* @param {*} type constructor to check against
|
|
|
|
* @param {Object} instance instance to examine
|
|
|
|
*
|
|
|
|
* @return {boolean} whether INSTANCE is an instance of TYPE
|
|
|
|
*/
|
|
|
|
function _instChk( type, instance )
|
|
|
|
{
|
2014-04-21 22:22:42 -04:00
|
|
|
try
|
2011-08-09 17:27:26 -04:00
|
|
|
{
|
2014-04-21 22:22:42 -04:00
|
|
|
// check prototype chain (will throw an error if type is not a
|
2014-04-22 00:24:21 -04:00
|
|
|
// constructor)
|
2014-04-21 22:22:42 -04:00
|
|
|
if ( instance instanceof type )
|
|
|
|
{
|
|
|
|
return true;
|
|
|
|
}
|
2011-08-09 17:27:26 -04:00
|
|
|
}
|
2014-04-21 22:22:42 -04:00
|
|
|
catch ( e ) {}
|
2011-08-09 17:27:26 -04:00
|
|
|
|
|
|
|
return false;
|
2014-04-22 00:24:21 -04:00
|
|
|
}
|
2011-08-09 17:27:26 -04:00
|
|
|
|
|
|
|
|
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
|
|
|
{
|
2014-04-26 10:00:01 -04:00
|
|
|
var build = this;
|
|
|
|
|
2014-03-29 00:40:36 -04:00
|
|
|
var a = arguments,
|
|
|
|
an = a.length,
|
|
|
|
props = ( ( an > 0 ) ? a[ an - 1 ] : 0 ) || {},
|
|
|
|
base = ( ( an > 1 ) ? a[ an - 2 ] : 0 ) || exports.ClassBase,
|
2011-12-15 22:58:23 -05:00
|
|
|
prototype = this._getBase( base ),
|
2011-03-27 23:16:19 -04:00
|
|
|
cname = '',
|
2014-02-16 23:23:11 -05:00
|
|
|
autoa = false,
|
2011-03-27 23:16:19 -04:00
|
|
|
|
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
|
|
|
|
2015-05-24 00:27:46 -04:00
|
|
|
// constructor may be different than base
|
|
|
|
pmeta = exports.getMeta( prototype.constructor ) || {},
|
2014-07-06 03:07:17 -04:00
|
|
|
|
2011-03-27 23:16:19 -04:00
|
|
|
abstract_methods =
|
2015-05-24 00:27:46 -04:00
|
|
|
util.clone( pmeta.abstractMethods )
|
2014-05-04 22:17:23 -04:00
|
|
|
|| { __length: 0 },
|
2014-02-02 23:28:09 -05:00
|
|
|
|
|
|
|
virtual_members =
|
2015-05-24 00:27:46 -04:00
|
|
|
util.clone( pmeta.virtualMembers )
|
2014-02-02 23:28:09 -05:00
|
|
|
|| {}
|
2011-03-27 23:16:19 -04:00
|
|
|
;
|
|
|
|
|
2017-11-04 01:07:06 -04:00
|
|
|
// respond intelligently if the definition object is mistakenly a class
|
|
|
|
if ( module.exports.isClass( props ) )
|
|
|
|
{
|
|
|
|
throw TypeError( ( an > 1 )
|
|
|
|
? "Expected class definition, but found class " + props.toString()
|
|
|
|
: "Missing second argument to extend class " + props.toString()
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
2015-05-27 23:03:11 -04:00
|
|
|
// prevent extending final classes (TODO: abstract this check)
|
2011-05-22 11:11:18 -04:00
|
|
|
if ( base.___$$final$$ === true )
|
|
|
|
{
|
|
|
|
throw Error(
|
|
|
|
"Cannot extend final class " +
|
2014-07-06 23:10:03 -04:00
|
|
|
( base[ _priv ].meta.name || '(anonymous)' )
|
2011-05-22 11:11:18 -04:00
|
|
|
);
|
|
|
|
}
|
|
|
|
|
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
|
|
|
|
2014-02-16 23:23:11 -05:00
|
|
|
// gobble up auto-abstract flag if present
|
|
|
|
if ( ( autoa = props.___$$auto$abstract$$ ) !== undefined )
|
|
|
|
{
|
|
|
|
delete props.___$$auto$abstract$$;
|
|
|
|
}
|
|
|
|
|
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-04-23 02:01:02 -04:00
|
|
|
// if we are inheriting from a prototype, we must make sure that all
|
|
|
|
// properties initialized by the ctor are implicitly public; otherwise,
|
|
|
|
// proxying will fail to take place
|
2014-04-29 10:47:12 -04:00
|
|
|
// TODO: see Class.isA TODO
|
2014-07-07 00:11:58 -04:00
|
|
|
if ( ( prototype[ _priv ] || {} ).vis === undefined )
|
2014-04-23 02:01:02 -04:00
|
|
|
{
|
|
|
|
this._discoverProtoProps( prototype, prop_init );
|
|
|
|
}
|
|
|
|
|
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 )
|
|
|
|
{
|
2014-06-11 21:49:00 -04:00
|
|
|
this._warnHandler.handle( e );
|
2011-07-06 19:34:35 -04:00
|
|
|
}
|
|
|
|
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
|
|
|
|
2017-11-04 14:51:29 -04:00
|
|
|
// we transparently handle extending errors in a sane manner, which is
|
|
|
|
// traditionally a huge mess (you're welcome)
|
|
|
|
if ( this._ector && this._ector.isError( base ) )
|
|
|
|
{
|
|
|
|
// declare public properties (otherwise, they'll be confined to the
|
|
|
|
// private visibility object in ES5+ environments)
|
|
|
|
props.message = '';
|
|
|
|
props.stack = '';
|
|
|
|
|
|
|
|
// user-provided constructor
|
|
|
|
var ector_own = members[ 'public' ].__construct;
|
|
|
|
|
|
|
|
// everything else is handled by the constructor
|
|
|
|
members[ 'public' ].__construct = this._ector.createCtor(
|
|
|
|
base, cname, ector_own
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
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
|
2014-05-04 22:17:23 -04:00
|
|
|
this.initStaticVisibilityObj( new_class );
|
|
|
|
|
|
|
|
var _self = this;
|
2011-04-10 22:32:46 -04:00
|
|
|
var staticInit = function( ctor, inheriting )
|
2011-04-05 23:11:25 -04:00
|
|
|
{
|
2014-05-04 22:17:23 -04:00
|
|
|
_self.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 );
|
2014-02-16 23:23:11 -05:00
|
|
|
validateAbstract( new_class, cname, abstract_methods, autoa );
|
2011-05-22 13:57:56 -04:00
|
|
|
|
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
|
2015-05-24 00:27:46 -04:00
|
|
|
var meta = createMeta( new_class, base, pmeta );
|
2011-03-27 23:16:19 -04:00
|
|
|
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
|
|
|
|
2014-04-26 10:00:01 -04:00
|
|
|
// returns a new instance of the class without invoking the constructor
|
|
|
|
// (intended for use in prototype chains)
|
|
|
|
new_class.asPrototype = function()
|
|
|
|
{
|
2015-05-13 00:18:08 -04:00
|
|
|
new_class[ _priv ].extending = true;
|
|
|
|
var inst = new new_class();
|
|
|
|
new_class[ _priv ].extending = false;
|
|
|
|
|
2014-04-26 10:00:01 -04:00
|
|
|
return inst;
|
|
|
|
};
|
|
|
|
|
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':
|
2015-05-13 00:18:08 -04:00
|
|
|
return ( base[ _priv ] )
|
|
|
|
? base.asPrototype()
|
|
|
|
: new base();
|
2011-12-15 22:58:23 -05:00
|
|
|
|
|
|
|
// we can use objects as the prototype directly
|
|
|
|
case 'object':
|
|
|
|
return base;
|
|
|
|
}
|
|
|
|
|
|
|
|
// scalars
|
|
|
|
throw TypeError( 'Must extend from Class, constructor or object' );
|
|
|
|
};
|
|
|
|
|
|
|
|
|
2014-04-23 02:01:02 -04:00
|
|
|
/**
|
|
|
|
* Discovers public properties on the given object and create an associated
|
|
|
|
* property
|
|
|
|
*
|
|
|
|
* This allows inheriting from a prototype that uses properties by ensuring
|
|
|
|
* that we properly proxy to that property. Otherwise, assigning the value
|
|
|
|
* on the private visibilit object would mask the underlying value rather
|
|
|
|
* than modifying it, leading to an inconsistent and incorrect state.
|
|
|
|
*
|
|
|
|
* This assumes that the object has already been initialized with all the
|
|
|
|
* properties. This may not be the case if the prototype constructor does
|
|
|
|
* not do so, in which case there is nothing we can do.
|
|
|
|
*
|
|
|
|
* This does not recurse on the prototype chian.
|
|
|
|
*
|
|
|
|
* For a more detailed description of this issue, see the interoperability
|
|
|
|
* test case for classes.
|
|
|
|
*
|
|
|
|
* @param {Object} obj object from which to gather properties
|
|
|
|
* @param {Object} prop_init destination property object
|
|
|
|
*
|
|
|
|
* @return {undefined}
|
|
|
|
*/
|
|
|
|
exports.prototype._discoverProtoProps = function( obj, prop_init )
|
|
|
|
{
|
|
|
|
var hasOwn = Object.hasOwnProperty,
|
|
|
|
pub = prop_init[ 'public' ];
|
|
|
|
|
|
|
|
for ( var field in obj )
|
|
|
|
{
|
|
|
|
var value = obj[ field ];
|
|
|
|
|
|
|
|
// we are not interested in the objtype chain, nor are we
|
|
|
|
// interested in functions (which are methods and need not be
|
|
|
|
// proxied)
|
|
|
|
if ( !( hasOwn.call( obj, field ) )
|
|
|
|
|| typeof value === 'function'
|
|
|
|
)
|
|
|
|
{
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
this._memberBuilder.buildProp(
|
|
|
|
prop_init, null, field, value, {}
|
|
|
|
);
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
|
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
|
|
|
)
|
|
|
|
{
|
2014-03-10 01:34:31 -04:00
|
|
|
var context = {
|
|
|
|
_cb: this,
|
2011-08-09 17:27:26 -04:00
|
|
|
|
2014-03-10 01:34:31 -04:00
|
|
|
// arguments
|
|
|
|
prop_init: prop_init,
|
|
|
|
class_id: class_id,
|
|
|
|
base: base,
|
|
|
|
staticInstLookup: staticInstLookup,
|
2014-02-02 22:59:20 -05:00
|
|
|
|
2014-03-10 01:34:31 -04:00
|
|
|
defs: {},
|
2011-08-09 17:27:26 -04:00
|
|
|
|
2014-01-30 23:13:52 -05:00
|
|
|
// holds member builder state
|
2014-03-10 01:34:31 -04:00
|
|
|
state: {},
|
2014-01-30 23:13:52 -05:00
|
|
|
|
2014-03-10 01:34:31 -04:00
|
|
|
// TODO: there does not seem to be tests for these guys; perhaps
|
|
|
|
// this can be rectified with the reflection implementation
|
|
|
|
members: memberdest.all,
|
|
|
|
abstract_methods: memberdest['abstract'],
|
|
|
|
static_members: memberdest['static'],
|
|
|
|
virtual_members: memberdest['virtual'],
|
|
|
|
};
|
2011-08-09 17:27:26 -04:00
|
|
|
|
2014-03-11 06:36:45 -04:00
|
|
|
// default member handlers for parser
|
|
|
|
var handlers = {
|
2014-03-10 01:34:31 -04:00
|
|
|
each: _parseEach,
|
|
|
|
property: _parseProp,
|
|
|
|
getset: _parseGetSet,
|
|
|
|
method: _parseMethod,
|
2014-03-11 06:36:45 -04:00
|
|
|
};
|
|
|
|
|
|
|
|
// a custom parser may be provided to hook the below property parser;
|
|
|
|
// this can be done to save time on post-processing, or alter the
|
|
|
|
// default behavior of the parser
|
|
|
|
if ( props.___$$parser$$ )
|
|
|
|
{
|
|
|
|
// this isn't something that we actually want to parse
|
|
|
|
var parser = props.___$$parser$$;
|
|
|
|
delete props.___$$parser$$;
|
|
|
|
|
2014-05-04 22:17:23 -04:00
|
|
|
// TODO: this is recreated every call!
|
|
|
|
var hjoin = function( name, orig )
|
2014-03-11 06:36:45 -04:00
|
|
|
{
|
|
|
|
handlers[ name ] = function()
|
|
|
|
{
|
2014-03-29 00:40:36 -04:00
|
|
|
var args = [],
|
|
|
|
i = arguments.length;
|
|
|
|
|
|
|
|
while ( i-- ) args[ i ] = arguments[ i ];
|
2014-03-11 06:36:45 -04:00
|
|
|
|
|
|
|
// invoke the custom handler with the original handler as
|
|
|
|
// its last argument (which the custom handler may choose
|
|
|
|
// not to invoke at all)
|
|
|
|
args.push( orig );
|
|
|
|
parser[ name ].apply( context, args );
|
|
|
|
};
|
2014-05-04 22:17:23 -04:00
|
|
|
};
|
2014-03-11 06:36:45 -04:00
|
|
|
|
|
|
|
// this avoids a performance penalty unless the above property is
|
|
|
|
// set
|
|
|
|
parser.each && hjoin( 'each', handlers.each );
|
|
|
|
parser.property && hjoin( 'property', handlers.property );
|
|
|
|
parser.getset && hjoin( 'getset', handlers.getset );
|
|
|
|
parser.method && hjoin( 'method', handlers.method );
|
|
|
|
}
|
2011-08-09 17:27:26 -04:00
|
|
|
|
Alias `constructor` member to `__construct`
This allows ease.js classes to mimic the structure of ES6 classes, which use
`constructor` to denote the constructor. This patch simply aliases it to
`__construct`, which ease.js handles as it would normally.
To that note, since the ES6 `class` keyword is purely syntatic sugar around
the prototype model, there is not much benefit to using it over ease.js if
benefits of ease.js are still desired, since the member definition syntax is
a feature of object literals:
```
// ease.js using ES6
let Person = Class(
{
_name: '',
// note that __construct still works as well
constructor( name ) {
this._name = ''+name;
},
sayHi() {
return "Hi, I'm " + this.getName();
},
// keywords still work as expected
'protected getName'() {
return this._name;
}
} );
// ES6 using `class` keyword
class Person
{
// note that ES6 will _not_ make this private
_name: '',
constructor( name ) {
this._name = ''+name;
},
sayHi() {
return "Hi, I'm " + this.getName();
}
// keywords unsupported (you'd have to use Symbols)
getName() {
return this._name;
}
}
// ES3/5 ease.js
var Person = Class(
{
_name: '',
__construct: function( name ) {
this._name = ''+name;
},
sayHi: function() {
return "Hi, I'm " + this._name;
},
'protected getName': function() {
return this._name;
}
} );
```
As you can see, the only change between writing ES6-style method definitions
is the syntax; all keywords and other features continue to work as expected.
2015-09-15 00:10:07 -04:00
|
|
|
handlers.keywordParser = _keywordParser;
|
|
|
|
|
2014-03-11 06:36:45 -04:00
|
|
|
// parse members and process accumulated member state
|
|
|
|
util.propParse( props, handlers, context );
|
2014-03-10 01:34:31 -04:00
|
|
|
this._memberBuilder.end( context.state );
|
|
|
|
}
|
2011-08-09 17:27:26 -04:00
|
|
|
|
|
|
|
|
Alias `constructor` member to `__construct`
This allows ease.js classes to mimic the structure of ES6 classes, which use
`constructor` to denote the constructor. This patch simply aliases it to
`__construct`, which ease.js handles as it would normally.
To that note, since the ES6 `class` keyword is purely syntatic sugar around
the prototype model, there is not much benefit to using it over ease.js if
benefits of ease.js are still desired, since the member definition syntax is
a feature of object literals:
```
// ease.js using ES6
let Person = Class(
{
_name: '',
// note that __construct still works as well
constructor( name ) {
this._name = ''+name;
},
sayHi() {
return "Hi, I'm " + this.getName();
},
// keywords still work as expected
'protected getName'() {
return this._name;
}
} );
// ES6 using `class` keyword
class Person
{
// note that ES6 will _not_ make this private
_name: '',
constructor( name ) {
this._name = ''+name;
},
sayHi() {
return "Hi, I'm " + this.getName();
}
// keywords unsupported (you'd have to use Symbols)
getName() {
return this._name;
}
}
// ES3/5 ease.js
var Person = Class(
{
_name: '',
__construct: function( name ) {
this._name = ''+name;
},
sayHi: function() {
return "Hi, I'm " + this._name;
},
'protected getName': function() {
return this._name;
}
} );
```
As you can see, the only change between writing ES6-style method definitions
is the syntax; all keywords and other features continue to work as expected.
2015-09-15 00:10:07 -04:00
|
|
|
/**
|
|
|
|
* Member keyword parser
|
|
|
|
*
|
2017-01-02 23:28:28 -05:00
|
|
|
* This parser handles aliases and constructor virtualization; all keyword
|
|
|
|
* parsing is kept to the original implementation.
|
Alias `constructor` member to `__construct`
This allows ease.js classes to mimic the structure of ES6 classes, which use
`constructor` to denote the constructor. This patch simply aliases it to
`__construct`, which ease.js handles as it would normally.
To that note, since the ES6 `class` keyword is purely syntatic sugar around
the prototype model, there is not much benefit to using it over ease.js if
benefits of ease.js are still desired, since the member definition syntax is
a feature of object literals:
```
// ease.js using ES6
let Person = Class(
{
_name: '',
// note that __construct still works as well
constructor( name ) {
this._name = ''+name;
},
sayHi() {
return "Hi, I'm " + this.getName();
},
// keywords still work as expected
'protected getName'() {
return this._name;
}
} );
// ES6 using `class` keyword
class Person
{
// note that ES6 will _not_ make this private
_name: '',
constructor( name ) {
this._name = ''+name;
},
sayHi() {
return "Hi, I'm " + this.getName();
}
// keywords unsupported (you'd have to use Symbols)
getName() {
return this._name;
}
}
// ES3/5 ease.js
var Person = Class(
{
_name: '',
__construct: function( name ) {
this._name = ''+name;
},
sayHi: function() {
return "Hi, I'm " + this._name;
},
'protected getName': function() {
return this._name;
}
} );
```
As you can see, the only change between writing ES6-style method definitions
is the syntax; all keywords and other features continue to work as expected.
2015-09-15 00:10:07 -04:00
|
|
|
*
|
|
|
|
* @param {string} prop property to parse
|
|
|
|
*
|
|
|
|
* @return {{name: string, bitwords: number, keywords: Object.<string, boolean>}}
|
|
|
|
*/
|
|
|
|
function _keywordParser( prop )
|
|
|
|
{
|
|
|
|
var result = parseKeywords( prop ),
|
|
|
|
alias = _getMemberAlias( result.name );
|
|
|
|
|
|
|
|
if ( alias !== undefined )
|
|
|
|
{
|
|
|
|
result.name = alias;
|
|
|
|
}
|
|
|
|
|
2017-01-02 23:28:28 -05:00
|
|
|
// constructors are always virtual by default (exception to the rule)
|
|
|
|
if ( result.name === '__construct' )
|
|
|
|
{
|
|
|
|
result.keywords[ 'virtual' ] = true;
|
|
|
|
}
|
|
|
|
|
Alias `constructor` member to `__construct`
This allows ease.js classes to mimic the structure of ES6 classes, which use
`constructor` to denote the constructor. This patch simply aliases it to
`__construct`, which ease.js handles as it would normally.
To that note, since the ES6 `class` keyword is purely syntatic sugar around
the prototype model, there is not much benefit to using it over ease.js if
benefits of ease.js are still desired, since the member definition syntax is
a feature of object literals:
```
// ease.js using ES6
let Person = Class(
{
_name: '',
// note that __construct still works as well
constructor( name ) {
this._name = ''+name;
},
sayHi() {
return "Hi, I'm " + this.getName();
},
// keywords still work as expected
'protected getName'() {
return this._name;
}
} );
// ES6 using `class` keyword
class Person
{
// note that ES6 will _not_ make this private
_name: '',
constructor( name ) {
this._name = ''+name;
},
sayHi() {
return "Hi, I'm " + this.getName();
}
// keywords unsupported (you'd have to use Symbols)
getName() {
return this._name;
}
}
// ES3/5 ease.js
var Person = Class(
{
_name: '',
__construct: function( name ) {
this._name = ''+name;
},
sayHi: function() {
return "Hi, I'm " + this._name;
},
'protected getName': function() {
return this._name;
}
} );
```
As you can see, the only change between writing ES6-style method definitions
is the syntax; all keywords and other features continue to work as expected.
2015-09-15 00:10:07 -04:00
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Return a member alias for NAME
|
|
|
|
*
|
|
|
|
* If NAME has no alias, then the result is `undefined`.
|
|
|
|
*
|
|
|
|
* @param {string} name member name
|
|
|
|
*
|
|
|
|
* @return {string|undefined}
|
|
|
|
*/
|
|
|
|
function _getMemberAlias( name )
|
|
|
|
{
|
|
|
|
return ( hasOwn.call( aliased_members, name ) )
|
|
|
|
? aliased_members[ name ]
|
|
|
|
: undefined;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2014-03-10 01:34:31 -04:00
|
|
|
function _parseEach( name, value, keywords )
|
|
|
|
{
|
|
|
|
var defs = this.defs;
|
2011-08-09 17:27:26 -04:00
|
|
|
|
2014-03-10 01:34:31 -04:00
|
|
|
// disallow use of our internal __initProps() method
|
|
|
|
if ( reserved_members[ name ] === true )
|
|
|
|
{
|
|
|
|
throw Error( name + " is reserved" );
|
|
|
|
}
|
2011-08-09 17:27:26 -04:00
|
|
|
|
2014-03-10 01:34:31 -04:00
|
|
|
// if a member was defined multiple times in the same class
|
|
|
|
// 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 )
|
|
|
|
)
|
|
|
|
{
|
|
|
|
throw Error(
|
|
|
|
"Cannot redefine method '" + name + "' in same declaration"
|
|
|
|
);
|
|
|
|
}
|
2011-08-09 17:27:26 -04:00
|
|
|
|
2014-03-10 01:34:31 -04:00
|
|
|
// keep track of the definitions (only during class declaration)
|
|
|
|
// to catch duplicates
|
|
|
|
defs[ name ] = keywords;
|
|
|
|
}
|
2011-08-09 17:27:26 -04:00
|
|
|
|
|
|
|
|
2014-03-10 01:34:31 -04:00
|
|
|
function _parseProp( name, value, keywords )
|
|
|
|
{
|
|
|
|
var dest = ( keywordStatic( keywords ) )
|
|
|
|
? this.static_members.props
|
|
|
|
: this.prop_init;
|
|
|
|
|
|
|
|
// build a new property, passing in the other members to compare
|
|
|
|
// against for preventing nonsensical overrides
|
|
|
|
this._cb._memberBuilder.buildProp(
|
|
|
|
dest, null, name, value, keywords, this.base
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function _parseGetSet( name, get, set, keywords )
|
|
|
|
{
|
|
|
|
var dest = ( keywordStatic( keywords ) )
|
|
|
|
? this.static_members.methods
|
|
|
|
: this.members,
|
|
|
|
|
|
|
|
is_static = keywordStatic( keywords ),
|
|
|
|
instLookup = ( ( is_static )
|
|
|
|
? this.staticInstLookup
|
|
|
|
: exports.getMethodInstance
|
|
|
|
);
|
|
|
|
|
|
|
|
this._cb._memberBuilder.buildGetterSetter(
|
|
|
|
dest, null, name, get, set, keywords, instLookup,
|
|
|
|
this.class_id, this.base
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function _parseMethod( name, func, is_abstract, keywords )
|
|
|
|
{
|
|
|
|
var is_static = keywordStatic( keywords ),
|
|
|
|
dest = ( is_static )
|
|
|
|
? this.static_members.methods
|
|
|
|
: this.members,
|
|
|
|
instLookup = ( is_static )
|
|
|
|
? this.staticInstLookup
|
|
|
|
: exports.getMethodInstance
|
|
|
|
;
|
|
|
|
|
|
|
|
// constructor check
|
|
|
|
if ( public_methods[ name ] === true )
|
|
|
|
{
|
|
|
|
if ( keywords[ 'protected' ] || keywords[ 'private' ] )
|
|
|
|
{
|
|
|
|
throw TypeError(
|
|
|
|
name + " must be public"
|
2011-08-09 17:27:26 -04:00
|
|
|
);
|
2014-03-10 01:34:31 -04:00
|
|
|
}
|
|
|
|
}
|
2011-08-09 17:27:26 -04:00
|
|
|
|
2014-03-10 01:34:31 -04:00
|
|
|
var used = this._cb._memberBuilder.buildMethod(
|
|
|
|
dest, null, name, func, keywords, instLookup,
|
|
|
|
this.class_id, this.base, this.state
|
|
|
|
);
|
2014-01-26 00:08:42 -05:00
|
|
|
|
2014-03-10 01:34:31 -04: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;
|
|
|
|
}
|
2014-02-02 23:28:09 -05:00
|
|
|
|
2014-03-10 01:34:31 -04:00
|
|
|
// 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
|
|
|
|
if ( is_abstract )
|
|
|
|
{
|
|
|
|
this.abstract_methods[ name ] = true;
|
|
|
|
this.abstract_methods.__length++;
|
|
|
|
}
|
|
|
|
else if ( ( hasOwn.call( this.abstract_methods, name ) )
|
|
|
|
&& ( is_abstract === false )
|
|
|
|
)
|
|
|
|
{
|
|
|
|
// if this was a concrete method, then it should no longer
|
|
|
|
// be marked as abstract
|
|
|
|
delete this.abstract_methods[ name ];
|
|
|
|
this.abstract_methods.__length--;
|
|
|
|
}
|
2014-01-30 23:13:52 -05:00
|
|
|
|
2014-03-10 01:34:31 -04:00
|
|
|
if ( keywords['virtual'] )
|
|
|
|
{
|
|
|
|
this.virtual_members[ name ] = true;
|
|
|
|
}
|
Support for trait class supertype overrides
Traits can now override methods of their class supertypes. Previously, in
order to override a method of some class `C` by mixing in some trait `T`,
both had to implement a common interface. This had two notable downsides:
1. A trait that was only compatible with details of `C` could only work
with `C#M` if it implemented an interface `I` that declared `I#M`.
This required the author of `C` to create interfaces where they would
otherwise not be necessary.
2. As a corollary of #1---since methods of interfaces must be public, it
was not possible for `T` to override any protected method of `C`; this
meant that `C` would have to declare such methods public, which may
break encapsulation or expose unnecessary concerns to the outside
world.
Until documentation is available---hopefully in the near future---the test
cases provide detailed documentation of the behavior. Stackable traits work
as you would expect:
```javascript
var C = Class(
{
'virtual foo': function()
{
return 'C';
},
} );
var T1 = Trait.extend( C,
{
'virtual abstract override foo': function()
{
return 'T1' + this.__super();
},
} );
var T2 = Trait.extend( C,
{
'virtual abstract override foo': function()
{
return 'T2' + this.__super();
},
} );
C.use( T1 )
.use( T1 )
.use( T2 )
.use( T2 )
.foo();
// result: "T2T2T1T1C"
```
If the `override` keyword is used without `abstract`, then the super method
is statically bound to the supertype, rather than being resolved at runtime:
```javascript
var C = Class(
{
'virtual foo': function()
{
return 'C';
},
} );
var T1 = Trait.extend( C,
{
'virtual abstract override foo': function()
{
return 'T1' + this.__super();
},
} );
var T2 = Trait.extend( C,
{
// static override
'virtual override foo': function()
{
return 'T2' + this.__super();
},
} );
C.use( T1 )
.use( T1 )
.use( T2 )
.use( T2 )
.foo();
// result: "T2C"
```
This latter form should be discouraged in most circumstances (as it prevents
stackable traits), but the behavior is consistent with the rest of the
system.
Happy hacking.
2015-10-22 23:44:28 -04:00
|
|
|
else
|
|
|
|
{
|
|
|
|
// final (non-virtual) definitions must clear the virtual flag from
|
|
|
|
// their super method
|
|
|
|
delete this.virtual_members[ name ];
|
|
|
|
}
|
2011-08-09 17:27:26 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
|
2011-05-22 13:57:56 -04:00
|
|
|
/**
|
|
|
|
* Validates abstract class requirements
|
|
|
|
*
|
2014-02-16 23:23:11 -05:00
|
|
|
* We permit an `auto' flag for internal use only that will cause the
|
|
|
|
* abstract flag to be automatically set if the class should be marked as
|
|
|
|
* abstract, instead of throwing an error; this should be used sparingly and
|
|
|
|
* never exposed via a public API (for explicit use), as it goes against the
|
|
|
|
* self-documentation philosophy.
|
|
|
|
*
|
2011-05-22 13:57:56 -04:00
|
|
|
* @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
|
2014-02-16 23:23:11 -05:00
|
|
|
* @param {boolean} auto automatically flag as abstract
|
2011-05-22 13:57:56 -04:00
|
|
|
*
|
|
|
|
* @return {undefined}
|
|
|
|
*/
|
2014-02-16 23:23:11 -05:00
|
|
|
function validateAbstract( ctor, cname, abstract_methods, auto )
|
2011-05-22 13:57:56 -04:00
|
|
|
{
|
|
|
|
if ( ctor.___$$abstract$$ )
|
|
|
|
{
|
2014-03-15 01:47:31 -04:00
|
|
|
if ( !auto && ( abstract_methods.__length === 0 ) )
|
2011-05-22 13:57:56 -04:00
|
|
|
{
|
|
|
|
throw TypeError(
|
|
|
|
"Class " + ( cname || "(anonymous)" ) + " was declared as " +
|
|
|
|
"abstract, but contains no abstract members"
|
|
|
|
);
|
|
|
|
}
|
|
|
|
}
|
2014-02-16 23:23:11 -05:00
|
|
|
else if ( abstract_methods.__length > 0 )
|
2011-05-22 13:57:56 -04:00
|
|
|
{
|
2014-02-16 23:23:11 -05:00
|
|
|
if ( auto )
|
2011-05-22 13:57:56 -04:00
|
|
|
{
|
2014-02-16 23:23:11 -05:00
|
|
|
ctor.___$$abstract$$ = true;
|
|
|
|
return;
|
2011-05-22 13:57:56 -04:00
|
|
|
}
|
2014-02-16 23:23:11 -05:00
|
|
|
|
|
|
|
throw TypeError(
|
|
|
|
"Class " + ( cname || "(anonymous)" ) + " contains abstract " +
|
|
|
|
"members and must therefore be declared abstract"
|
|
|
|
);
|
2011-05-22 13:57:56 -04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
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
|
|
|
{
|
2014-07-06 23:10:03 -04:00
|
|
|
var new_class;
|
|
|
|
|
2011-03-27 23:16:19 -04:00
|
|
|
if ( abstract_methods.__length === 0 )
|
|
|
|
{
|
2014-07-06 23:10:03 -04:00
|
|
|
new_class = this.createConcreteCtor( cname, members );
|
2011-03-27 23:16:19 -04:00
|
|
|
}
|
|
|
|
else
|
2011-03-27 23:04:40 -04:00
|
|
|
{
|
2014-07-06 23:10:03 -04:00
|
|
|
new_class = this.createAbstractCtor( cname );
|
2011-03-27 23:16:19 -04:00
|
|
|
}
|
2014-07-06 23:10:03 -04:00
|
|
|
|
2014-07-07 22:04:50 -04:00
|
|
|
util.defineSecureProp( new_class, _priv, {} );
|
2014-07-06 23:10:03 -04:00
|
|
|
return new_class;
|
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
|
|
|
{
|
2015-05-13 00:18:08 -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.
|
2015-05-13 00:18:08 -04:00
|
|
|
if ( ClassInstance[ _priv ].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-07-27 01:40:35 -04:00
|
|
|
// FIXME: this is a bit of a kluge for determining whether the ctor
|
|
|
|
// should be invoked before a child prector...
|
|
|
|
var haspre = ( typeof this.___$$ctor$pre$$ === 'function' );
|
|
|
|
if ( haspre
|
|
|
|
&& ClassInstance.prototype.hasOwnProperty( '___$$ctor$pre$$' )
|
|
|
|
)
|
2014-01-23 00:34:15 -05:00
|
|
|
{
|
2014-07-07 23:09:58 -04:00
|
|
|
// FIXME: we're exposing _priv to something that can be
|
2014-06-05 23:35:03 -04:00
|
|
|
// malicously set by the user
|
|
|
|
this.___$$ctor$pre$$( _priv );
|
2014-07-27 01:40:35 -04:00
|
|
|
haspre = false;
|
2014-01-23 00:34:15 -05:00
|
|
|
}
|
|
|
|
|
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-07-27 01:40:35 -04:00
|
|
|
// FIXME: see above
|
|
|
|
if ( haspre )
|
|
|
|
{
|
|
|
|
this.___$$ctor$pre$$( _priv );
|
|
|
|
}
|
|
|
|
|
2014-06-05 23:35:03 -04:00
|
|
|
if ( typeof this.___$$ctor$post$$ === 'function' )
|
|
|
|
{
|
|
|
|
this.___$$ctor$post$$( _priv );
|
|
|
|
}
|
|
|
|
|
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.
|
2014-03-10 01:34:31 -04:00
|
|
|
if ( !( hasOwn.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()
|
|
|
|
{
|
2015-05-13 00:18:08 -04:00
|
|
|
if ( !__abstract_self[ _priv ].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,
|
2014-07-07 00:11:58 -04:00
|
|
|
parent = prototype.___$$parent$$,
|
|
|
|
vis = this[ _priv ].vis;
|
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(
|
2014-07-07 00:11:58 -04:00
|
|
|
this, vis, properties[ 'public' ]
|
2011-03-27 23:04:40 -04:00
|
|
|
);
|
|
|
|
|
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.
|
2014-07-07 00:11:58 -04:00
|
|
|
var vis = 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}
|
|
|
|
*/
|
2014-05-04 22:17:23 -04:00
|
|
|
exports.prototype.initStaticVisibilityObj = function( 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}
|
|
|
|
*/
|
2014-05-04 22:17:23 -04:00
|
|
|
exports.prototype.attachStatic = function( 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
|
2014-03-10 01:34:31 -04:00
|
|
|
var 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.
|
2014-03-10 01:34:31 -04:00
|
|
|
found = hasOwn.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.
|
2014-03-10 01:34:31 -04:00
|
|
|
found = hasOwn.call( props[ 'protected' ], prop ) && 'protected'
|
2011-05-13 00:55:09 -04:00
|
|
|
|| !in_subtype
|
2014-03-10 01:34:31 -04:00
|
|
|
&& hasOwn.call( props[ 'private' ], prop ) && 'private'
|
2011-05-13 00:55:09 -04:00
|
|
|
;
|
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
|
|
|
|
*
|
2015-05-24 00:27:46 -04:00
|
|
|
* DYNMETA is used only when CPARENT's metadata are flagged as "lazy",
|
|
|
|
* meaning that the data are not available at the time of its definition,
|
|
|
|
* but are available now as DYNMETA.
|
|
|
|
*
|
2011-12-13 21:19:14 -05:00
|
|
|
* @param {Function} func class to initialize metadata for
|
|
|
|
* @param {Function} cparent class parent
|
2015-05-24 00:27:46 -04:00
|
|
|
* @param {?Object} dynmeta dynamic metadata
|
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
|
|
|
*/
|
2015-05-24 00:27:46 -04:00
|
|
|
function createMeta( func, cparent, dynmeta )
|
2011-03-27 23:04:40 -04:00
|
|
|
{
|
|
|
|
var id = func.__cid,
|
2015-05-24 00:27:46 -04:00
|
|
|
parent_meta = ( cparent[ _priv ]
|
2011-03-27 23:04:40 -04:00
|
|
|
? exports.getMeta( cparent )
|
|
|
|
: undefined
|
|
|
|
);
|
|
|
|
|
|
|
|
// copy the parent prototype's metadata if it exists (inherit metadata)
|
|
|
|
if ( parent_meta )
|
|
|
|
{
|
2015-05-24 00:27:46 -04:00
|
|
|
return func[ _priv ].meta = util.clone(
|
|
|
|
// "lazy" metadata are unavailable at the time of definition
|
|
|
|
parent_meta._lazy
|
|
|
|
? dynmeta
|
|
|
|
: parent_meta,
|
|
|
|
true
|
|
|
|
);
|
2011-03-27 23:04:40 -04:00
|
|
|
}
|
|
|
|
|
2014-07-07 22:01:23 -04:00
|
|
|
// create empty
|
|
|
|
return func[ _priv ].meta = {
|
|
|
|
implemented: [],
|
|
|
|
};
|
2011-03-27 23:04:40 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
* 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;
|
|
|
|
|
2014-07-07 00:11:58 -04:00
|
|
|
// initialize our *own* private metadata store; do not use the
|
|
|
|
// prototype's
|
2014-07-07 22:04:50 -04:00
|
|
|
util.defineSecureProp( instance, _priv, {} );
|
2014-07-07 00:11:58 -04:00
|
|
|
|
2011-03-27 23:04:40 -04:00
|
|
|
// add the visibility objects to the data object for this class instance
|
2017-11-02 00:15:31 -04:00
|
|
|
instance[ _priv ].vis = new prot();
|
|
|
|
instance[ _priv ].inst = instance;
|
2011-03-27 23:04:40 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
* 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
|
|
|
{
|
2014-05-04 22:17:23 -04:00
|
|
|
if ( inst === undefined )
|
|
|
|
{
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
|
2011-03-27 23:04:40 -04:00
|
|
|
var iid = inst.__iid,
|
2014-07-07 00:11:58 -04:00
|
|
|
priv = inst[ _priv ],
|
|
|
|
data;
|
2011-03-27 23:04:40 -04:00
|
|
|
|
2014-07-07 00:11:58 -04:00
|
|
|
return ( iid && priv && ( data = priv.vis ) )
|
2011-03-27 23:04:40 -04:00
|
|
|
? 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
|
|
|
|
*
|
2015-10-26 22:46:08 -04:00
|
|
|
* The method returns whether the class contains abstract methods (and is
|
|
|
|
* therefore abstract).
|
|
|
|
*
|
2011-03-29 00:08:49 -04:00
|
|
|
* @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;
|
|
|
|
|
|
|
|
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
|
|
|
}
|