/** * Combined redistributable ease.js file * * For the original, uncombined and unminifed source, please visit * http://easejs.org. * * Copyright (C) 2010,2011 Mike Gerwitz * * This file is part of ease.js. * * ease.js is free software: you can redistribute it and/or modify it under the * terms of the GNU Lesser General Public License as published by the Free * Software Foundation, either version 3 of the License, or (at your option) * any later version. * * This program is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License * for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program. If not, see . */ /** * ease.js namespace * * All modules will be available via this namespace. In CommonJS format, they * were accessed via the require() function. For example: * * var util = require( 'easejs' ).Class; * * In this file, the above would be written as: * * var util = easejs.Class; * * @type {Object} */ var easejs = {}; ( function( ns_exports ) { /** * CommonJS module exports * * Since this file contains all of the modules, this will be populated with * every module right off the bat. * * @type {Object.} */ var module = {}; /** * Returns the requested module * * The require() function is likely unavailable client-side (within a web * browser). Therefore, we mock one. If it is available, this overwrites it. * Our modules are all preloaded in the exports object. * * @param {string} module_id id of the module to load * * return tag intentionally omitted; too many potential return types and * setting return type of {*} will throw warnings for those attempting to * treat the return value as a function */ var require = function( module_id ) { // remove the './' directory prefix (every module is currently included // via a relative path), stupidly remove ../'s and remove .js extensions var id_clean = module_id.replace( /^\.?\/|[^/]*?\/\.\.\/|\.js$/, '' ); // attempt to retrieve the module var mod = module[ id_clean ]; if ( mod === undefined ) { throw "[ease.js] Undefined module: " + module_id; } return mod.exports; }; /** prop_parser **/ ( function( module, __dirname ) { var exports = module.exports = {}; /** * Property keyword parser module * * Copyright (C) 2010,2011 Mike Gerwitz * * This file is part of ease.js. * * ease.js is free software: you can redistribute it and/or modify it under the * terms of the GNU Lesser General Public License as published by the Free * Software Foundation, either version 3 of the License, or (at your option) * any later version. * * This program is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License * for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program. If not, see . * * @author Mike Gerwitz */ /** * Known (permitted) keywords * @type {Object.} */ var _keywords = { 'public': true, 'protected': true, 'private': true, 'static': true, 'abstract': true, 'const': true, 'virtual': true, 'override': true, 'proxy': true }; /** * Parses property keywords * * @param {string} prop property string, which may contain keywords * * @return {{name: string, keywords: Object.}} */ exports.parseKeywords = function ( prop ) { var name = prop, keywords = [], keyword_obj = {}; prop = ''+( prop ); // only perform parsing if the string contains a space if ( / /.test( prop ) ) { // the keywords are all words, except for the last, which is the // property name keywords = prop.split( /\s+/ ); name = keywords.pop(); var i = keywords.length, keyword = ''; while ( i-- ) { keyword = keywords[ i ]; // ensure the keyword is recognized if ( !_keywords[ keyword ] ) { throw Error( "Unexpected keyword for '" + name + "': " + keyword ); } keyword_obj[ keyword ] = true; } } return { name: name, keywords: keyword_obj }; } } )( module['prop_parser'] = {}, '.' ); /** util **/ ( function( module, __dirname ) { var exports = module.exports = {}; /** * Contains utilities functions shared by modules * * Copyright (C) 2010,2011 Mike Gerwitz * * This file is part of ease.js. * * ease.js is free software: you can redistribute it and/or modify it under the * terms of the GNU Lesser General Public License as published by the Free * Software Foundation, either version 3 of the License, or (at your option) * any later version. * * This program is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License * for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program. If not, see . * * @author Mike Gerwitz */ var propParseKeywords = require( __dirname + '/prop_parser' ).parseKeywords; /** * Whether we can actually define properties, or we need to fall back * * This check actually attempts to set a property and fails if there's an error. * This is needed because IE8 has a broken implementation, yet still defines * Object.defineProperty for use with DOM elements. Just another day in the life * of a web developer. * * This test is only performed once, when the module is first loaded. Don't * expect a performance hit from it. * * @type {boolean} */ var can_define_prop = ( function() { if ( typeof Object.defineProperty === 'function' ) { try { // perform test, primarily for IE8 Object.defineProperty( {}, 'x', {} ); return true; } catch ( e ) {} } return false; } )(); /** * Freezes an object if freezing is supported * * @param {Object} obj object to freeze * * @return {Object} object passed to function */ exports.freeze = ( typeof Object.freeze === 'function' ) ? Object.freeze : function( obj ) { return; } ; /** * Gets/sets whether the system needs to fall back to defining properties in a * normal manner when use of Object.defineProperty() is requested * * This will be set by default if the JS engine does not support the * Object.defineProperty method from ECMAScript 5. * * @param {boolean=} val value, if used as setter * * @return {boolean|Object} current value if getter, self if setter */ exports.definePropertyFallback = function( val ) { if ( val === undefined ) { return !can_define_prop; } can_define_prop = !val; exports.defineSecureProp = getDefineSecureProp(); return exports; }; /** * Attempts to define a non-enumerable, non-writable and non-configurable * property on the given object * * If the operation is unsupported, a normal property will be set. * * @param {Object} obj object to set property on * @param {string} prop name of property to set * @param {*} value value to set * * @return {undefined} */ exports.defineSecureProp = getDefineSecureProp(); /** * Clones an object * * @param {*} data object to clone * @param {boolean=} deep perform deep clone (defaults to shallow) * * @return {*} cloned object * * Closure Compiler ignores typeof checks and is thusly confused: * @suppress {checkTypes} */ exports.clone = function clone( data, deep ) { deep = !!deep; if ( data instanceof Array ) { if ( !deep ) { // return a copy of the array return data.slice( 0 ); } // if we're performing a deep clone, we have to loop through each of the // elements of the array and clone them var ret = []; for ( var i = 0, len = data.length; i < len; i++ ) { // clone this element ret.push( clone( data[ i ], deep ) ); } return ret; } else if ( typeof data === 'function' ) { // It is pointless to clone a function. Even if we did clone those that // support toSource(), they'd still do the same damn thing. return data; } // explicitly testing with instanceof will ensure we're actually testing an // object, not something that may be misinterpreted as one (e.g. null) else if ( data instanceof Object ) { var newobj = {}, hasOwn = Object.prototype.hasOwnProperty; // copy data to the new object for ( var prop in data ) { if ( hasOwn.call( data, prop ) ) { newobj[ prop ] = ( deep ) ? clone( data[ prop ] ) : data[ prop ] ; } } return newobj; } // primitive type; cloning unnecessary return data; }; /** * Copies properties from one object to another * * This method is designed to support very basic object extensions. The * destination argument is first to allow extending an object without using the * full-blown class system. * * If a deep copy is not performed, all values will be copied by reference. * * @param {Object} dest destination object * @param {Object} src source object * @param {boolean} deep perform deep copy (slower) * * @return {Object} dest */ exports.copyTo = function( dest, src, deep ) { deep = !!deep; var get, set, data; // sanity check if ( !( dest instanceof Object ) || !( src instanceof Object ) ) { throw TypeError( "Must provide both source and destination objects" ); } // slower; supports getters/setters if ( can_define_prop ) { for ( var prop in src ) { data = Object.getOwnPropertyDescriptor( src, prop ); if ( data.get || data.set ) { // Define the property the slower way (only needed for // getters/setters). We don't have to worry about cloning in // this case, since getters/setters are methods. Object.defineProperty( dest, prop, data ); } else { // normal copy; cloned if deep, otherwise by reference dest[ prop ] = ( deep ) ? exports.clone( src[ prop ], true ) : src[ prop ] ; } } } // quick (keep if statement out of the loop) else { for ( var prop in src ) { // normal copy; cloned if deep, otherwise by reference dest[ prop ] = ( deep ) ? exports.clone( src[ prop ], true ) : src[ prop ] ; } } // return dest for convenience (and to feel useful about ourselves) return dest; }; /** * Parses object properties to determine how they should be interpreted in an * Object Oriented manner * * @param {!Object} data properties with names as the key * * @param {!{each,property,method,getset,keywordParser}} options * parser options and callbacks * * @return undefined */ exports.propParse = function( data, options ) { // todo: profile; function calls are more expensive than if statements, so // it's probably a better idea not to use fvoid var fvoid = function() {}, callbackEach = options.each || undefined, callbackProp = options.property || fvoid, callbackMethod = options.method || fvoid, callbackGetSet = options.getset || fvoid, keywordParser = options.keywordParser || propParseKeywords, hasOwn = Object.prototype.hasOwnProperty, parse_data = {}, name = '', keywords = {}, value = null, getter = false, setter = false; // for each of the given properties, determine what type of property we're // dealing with (in the classic OO sense) for ( var prop in data ) { // ignore properties of instance prototypes if ( !( hasOwn.call( data, prop ) ) ) { continue; } // retrieve getters/setters, if supported if ( can_define_prop ) { var prop_desc = Object.getOwnPropertyDescriptor( data, prop ); getter = prop_desc.get; setter = prop_desc.set; } // do not attempt to retrieve the value if a getter is defined (as that // would then call the getter) value = ( typeof getter === 'function' ) ? undefined : data[ prop ]; parse_data = keywordParser( prop ) || {}; name = parse_data.name || prop; keywords = parse_data.keywords || {}; if ( options.assumeAbstract || keywords[ 'abstract' ] ) { // may not be set if assumeAbstract is given keywords[ 'abstract' ] = true; if ( !( value instanceof Array ) ) { throw TypeError( "Missing parameter list for abstract method: " + name ); } verifyAbstractNames( name, value ); value = exports.createAbstractMethod.apply( this, value ); } // if an 'each' callback was provided, pass the data before parsing it if ( callbackEach ) { callbackEach.call( callbackEach, name, value, keywords ); } // getter/setter if ( getter || setter ) { callbackGetSet.call( callbackGetSet, name, getter, setter, keywords ); } // method else if ( ( typeof value === 'function' ) || ( keywords[ 'proxy' ] ) ) { callbackMethod.call( callbackMethod, name, value, exports.isAbstractMethod( value ), keywords ); } // simple property else { callbackProp.call( callbackProp, name, value, keywords ); } } }; /** * Only permit valid names for parameter list * * In the future, we may add additional functionality, so it's important to * restrict this as much as possible for the time being. * * @param {string} name name of abstract member (for error) * @param {Object} params parameter list to check * * @return {undefined} */ function verifyAbstractNames( name, params ) { var i = params.length; while ( i-- ) { if ( params[ i ].match( /^[a-z_][a-z0-9_]*$/i ) === null ) { throw SyntaxError( "Member " + name + " contains invalid parameter '" + params[ i ] + "'" ); } } } /** * Creates an abstract method * * Abstract methods must be implemented by a subclass and cannot be called * directly. If a class contains a single abstract method, the class itself is * considered to be abstract and cannot be instantiated. It may only be * extended. * * @param {...string} def function definition that concrete * implementations must follow * * @return {function()} */ exports.createAbstractMethod = function( def ) { var definition = Array.prototype.slice.call( arguments ); var method = function() { throw new Error( "Cannot call abstract method" ); }; exports.defineSecureProp( method, 'abstractFlag', true ); exports.defineSecureProp( method, 'definition', definition ); exports.defineSecureProp( method, '__length', arguments.length ); return method; }; /** * Determines if the given function is an abstract method * * @param {function()} func function to inspect * * @return {boolean} true if function is an abstract method, otherwise false * * @suppress {checkTypes} */ exports.isAbstractMethod = function( func ) { return ( ( typeof func === 'function') && ( func.abstractFlag === true ) ) ? true : false ; }; /** * Shrinks an array, removing undefined elements * * Pushes all items onto a new array, removing undefined elements. This ensures * that the length of the array represents correctly the number of elements in * the array. * * @param {Array} items array to shrink * * @return {Array} shrunken array */ exports.arrayShrink = function( items ) { // copy the methods into a new array by pushing them onto it, to ensure // the length property of the array will work properly var arr_new = []; for ( var i = 0, len = items.length; i < len; i++ ) { var item = items[ i ]; if ( item === undefined ) { continue; } arr_new.push( item ); } return arr_new; }; /** * Uses Object.getOwnPropertyDescriptor if available, otherwise provides our own * implementation to fall back on */ exports.getOwnPropertyDescriptor = ( can_define_prop && Object.getOwnPropertyDescriptor ) || /** * If the environment does not support retrieving property descriptors * (ES5), then the following will be true: * - get/set will always be undefined * - writable, enumerable and configurable will always be true * - value will be the value of the requested property on the given object * * @param {!Object} obj object to check property on * @param {string} prop property to retrieve descriptor for * * @return {Object|undefined} descriptor for requested property, if found */ function( obj, prop ) { if ( !Object.prototype.hasOwnProperty.call( obj, prop ) ) { return undefined; } // fallback response return { get: undefined, set: undefined, writable: true, enumerable: true, configurable: true, value: obj[ prop ] }; }; /** * Returns prototype of object, or undefined if unsupported */ exports.getPrototypeOf = Object.getPrototypeOf || function() { return undefined; }; /** * Travels down the prototype chain of the given object in search of the * requested property and returns its descriptor * * This operates as Object.getOwnPropertyDescriptor(), except that it traverses * the prototype chain. For environments that do not support __proto__, it will * not traverse the prototype chain and essentially serve as an alias for * getOwnPropertyDescriptor(). * * This method has the option to ignore the base prototype. This is useful to, * for example, not catch properties like Object.prototype.toString() when * searching for 'toString' on an object. * * @param {Object} obj object to check property on * @param {string} prop property to retrieve descriptor for * @param {boolean} nobase whether to ignore the base prototype * * @return {Object} descriptor for requested property or undefined if not found */ exports.getPropertyDescriptor = function( obj, prop, nobase ) { // false by default nobase = !!nobase; // note that this uses util's function, not Object's var desc = exports.getOwnPropertyDescriptor( obj, prop ), next = exports.getPrototypeOf( obj ); // if we didn't find a descriptor and a prototype is available, recurse down // the prototype chain, ensuring that the next prototype has a prototype if // the base is to be excluded if ( !desc && next && ( !nobase || exports.getPrototypeOf( next ) ) ) { return exports.getPropertyDescriptor( next, prop, nobase ); } // return the descriptor or undefined if no prototype is available return desc; }; /** * Indicates whether or not the getPropertyDescriptor method is capable of * traversing the prototype chain * * @type {boolean} */ exports.defineSecureProp( exports.getPropertyDescriptor, 'canTraverse', ( Object.getPrototypeOf ) ? true : false ); /** * Appropriately returns defineSecureProp implementation to avoid check on each * invocation * * @return {function( Object, string, * )} */ function getDefineSecureProp() { // falls back to simply defining a normal property var fallback = function( obj, prop, value ) { obj[ prop ] = value; }; if ( !can_define_prop ) { return fallback; } else { // uses ECMAScript 5's Object.defineProperty() method return function( obj, prop, value ) { try { Object.defineProperty( obj, prop, { value: value, enumerable: false, writable: false, configurable: false }); } catch ( e ) { // let's not have this happen again, as repeatedly throwing // exceptions will do nothing but slow down the system exports.definePropertyFallback( true ); // if there's an error (ehem, IE8), fall back fallback( obj, prop, value ); } }; } } } )( module['util'] = {}, '.' ); /** warn **/ ( function( module, __dirname ) { var exports = module.exports = {}; /** * ease.js warning system * * Copyright (C) 2010,2011 Mike Gerwitz * * This file is part of ease.js. * * ease.js is free software: you can redistribute it and/or modify it under the * terms of the GNU Lesser General Public License as published by the Free * Software Foundation, either version 3 of the License, or (at your option) * any later version. * * This program is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License * for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program. If not, see . * * @author Mike Gerwitz */ /** * Active warning handler * @type {?function( Warning )} */ var _handler = null; /** * Console to use for logging * * This reference allows an alternative console to be used. Must contain warn() * or log() methods. * * @type {Object} */ var _console = ( typeof console !== 'undefined' ) ? console : undefined; /** * Permits wrapping an exception as a warning * * Warnings are handled differently by the system, depending on the warning * level that has been set. * * @param {Error} e exception (error) to wrap * * @return {Warning} new warning instance * * @constructor */ var Warning = exports.Warning = function( e ) { // allow instantiation without use of 'new' keyword if ( !( this instanceof Warning ) ) { return new Warning( e ); } // ensure we're wrapping an exception if ( !( e instanceof Error ) ) { throw TypeError( "Must provide exception to wrap" ); } Error.prototype.constructor.call( this, e.message ); // copy over the message for convenience this.message = e.message; this.name = 'Warning'; this._error = e; this.stack = e.stack && e.stack.replace( /^.*?\n+/, this.name + ': ' + this.message + "\n" ); }; // ensures the closest compatibility...just be careful not to modify Warning's // prototype Warning.prototype = Error(); Warning.prototype.constructor = Warning; Warning.prototype.name = 'Warning'; /** * Return the error wrapped by the warning * * @return {Error} wrapped error */ Warning.prototype.getError = function() { return this._error; }; /** * Core warning handlers * @type {Object} */ exports.handlers = { /** * Logs message to console * * Will attempt to log using console.warn(), falling back to console.log() * if necessary and aborting entirely if neither is available. * * This is useful as a default option to bring problems to the developer's * attention without affecting the control flow of the software. * * @param {Warning} warning to log * * @return {undefined} */ log: function( warning ) { var dest; _console && ( dest = _console.warn || _console.log ) && dest.call( _console, ( 'Warning: ' + warning.message ) ); }, /** * Throws the error associated with the warning * * This handler is useful for development and will ensure that problems are * brought to the attention of the developer. * * @param {Warning} warning to log * * @return {undefined} */ throwError: function( warning ) { throw warning.getError(); }, /** * Ignores warnings * * This is useful in a production environment where (a) warnings will affect * the reputation of the software or (b) warnings may provide too much * insight into the software. If using this option, you should always * develop in a separate environment so that the system may bring warnings * to your attention. * * @param {Warning} warning to log * * @return {undefined} */ dismiss: function( warning ) { // do nothing } }; /** * Sets the active warning handler * * You may use any of the predefined warning handlers or pass your own function. * * @param {function( Warning )} handler warning handler * * @return {undefined} */ exports.setHandler = function( handler ) { _handler = handler; }; /** * Handles a warning using the active warning handler * * @param {Warning} warning warning to handle * * @return {undefined} */ exports.handle = function( warning ) { _handler( warning ); } /** * Sets active console * * @param {Object} console containing warn() or log() method * * @return {undefined} */ exports.setConsole = function( console ) { _console = console; }; // set the default handler _handler = exports.handlers.log; } )( module['warn'] = {}, '.' ); /** ClassBuilder **/ ( function( module, __dirname ) { var exports = module.exports = {}; /** * Handles building of classes * * Copyright (C) 2010,2011 Mike Gerwitz * * This file is part of ease.js. * * ease.js is free software: you can redistribute it and/or modify it under the * terms of the GNU Lesser General Public License as published by the Free * Software Foundation, either version 3 of the License, or (at your option) * any later version. * * This program is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License * for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program. If not, see . * * @author Mike Gerwitz * * 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. */ var util = require( __dirname + '/util' ), warn = require( __dirname + '/warn' ), Warning = warn.Warning, /** * 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. * * This test will determine if this poor implementation exists. */ enum_bug = ( Object.prototype.propertyIsEnumerable.call( { toString: function() {} }, 'toString' ) === false ) ? true : false, /** * Hash of reserved members * * 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. * * @type {Object.} */ reserved_members = { '__initProps': true, 'constructor': true }, /** * 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. * * @type {Object.} */ public_methods = { '__construct': true, 'toString': true, '__toString': true }; /** * Initializes class builder with given member builder * * The 'new' keyword is not required when instantiating this constructor. * * @param {Object} member_builder member builder * * @param {VisibilityObjectFactory} visibility_factory visibility object * generator * * @constructor */ module.exports = exports = function ClassBuilder( member_builder, visibility_factory ) { // allow ommitting the 'new' keyword if ( !( this instanceof exports ) ) { // module.exports for Closure Compiler return new module.exports( member_builder, visibility_factory ); } /** * Used for building class members * @type {Object} */ this._memberBuilder = member_builder; /** * Generates visibility object * @type {VisibilityObjectFactory} */ this._visFactory = visibility_factory; /** * 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; }; /** * Default class implementation * * @return undefined */ exports.ClassBase = function Class() {}; /** * Default static property method * * This simply returns undefined, signifying that the property was not found. * * @param {string} prop requested property * * @return {undefined} */ exports.ClassBase.$ = function( prop, val ) { if ( val !== undefined ) { throw ReferenceError( "Cannot set value of undeclared static property '" + prop + "'" ); } return undefined; }; /** * 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. * * @return {Object.} reserved members */ exports.getReservedMembers = function() { // return a copy of the reserved members return util.clone( reserved_members, true ); }; /** * 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.} forced-public methods */ exports.getForcedPublicMethods = function() { return util.clone( public_methods, true ); }; /** * Returns reference to metadata for the requested class * * Since a reference is returned (rather than a copy), the returned object can * be modified to alter the metadata. * * @param {Function|Object} cls class from which to retrieve metadata * * @return {__class_meta} */ 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; if ( !( type && instance ) ) { return false; } try { // check prototype chain (will throw an error if type is not a // constructor (function) if ( instance instanceof type ) { return true; } } catch ( e ) {} // if no metadata is available, then our remaining checks cannot be // performed if ( !instance.__cid || !( meta = exports.getMeta( instance ) ) ) { return false; } implemented = meta.implemented; i = implemented.length; // check implemented interfaces while ( i-- ) { if ( implemented[ i ] === type ) { return true; } } return false; }; /** * Mimics class inheritance * * 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. * * @param {Function|Object} _ parent or definition object * @param {Object=} __ definition object if parent was provided * * @return {Function} extended class */ exports.prototype.build = function extend( _, __ ) { // ensure we'll be permitted to instantiate abstract classes for the base this._extending = true; var args = Array.prototype.slice.call( arguments ), props = args.pop() || {}, base = args.pop() || exports.ClassBase, prototype = this._getBase( base ), cname = '', prop_init = this._memberBuilder.initMembers(), members = this._memberBuilder.initMembers( prototype ), static_members = { methods: this._memberBuilder.initMembers(), props: this._memberBuilder.initMembers() }, abstract_methods = util.clone( exports.getMeta( base ).abstractMethods ) || { __length: 0 } ; // prevent extending final classes if ( base.___$$final$$ === true ) { throw Error( "Cannot extend final class " + ( base.___$$meta$$.name || '(anonymous)' ) ); } // grab the name, if one was provided if ( cname = props.__name ) { // we no longer need it delete props.__name; } // IE has problems with toString() if ( enum_bug ) { if ( props.toString !== Object.prototype.toString ) { props.__toString = props.toString; } } // increment class identifier this._classId++; // build the various class components (xxx: this is temporary; needs // refactoring) try { this.buildMembers( props, this._classId, base, prop_init, abstract_methods, members, static_members, function( inst ) { return new_class.___$$svis$$; } ); } catch ( e ) { // intercept warnings /only/ if ( e instanceof Warning ) { warn.handle( e ); } else { throw e; } } // reference to the parent prototype (for more experienced users) prototype.___$$parent$$ = base.prototype; // set up the new class var new_class = this.createCtor( cname, abstract_methods, members ); // closure to hold static initialization to be used later by subtypes initStaticVisibilityObj( new_class ); var staticInit = function( ctor, inheriting ) { attachStatic( ctor, static_members, base, inheriting ); } staticInit( new_class, false ); this._attachPropInit( prototype, prop_init, members, new_class, this._classId ); new_class.prototype = prototype; new_class.prototype.constructor = new_class; new_class.___$$props$$ = prop_init; new_class.___$$methods$$ = members; new_class.___$$sinit$$ = staticInit; attachFlags( new_class, props ); validateAbstract( new_class, cname, abstract_methods ); // 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(). util.defineSecureProp( prototype, '__self', new_class.___$$svis$$ ); // create internal metadata for the new class var meta = createMeta( new_class, base ); meta.abstractMethods = abstract_methods; meta.name = cname; attachAbstract( new_class, abstract_methods ); attachId( new_class, this._classId ); // we're done with the extension process this._extending = false; return new_class; }; 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' ); }; exports.prototype.buildMembers = function buildMembers( props, class_id, base, prop_init, abstract_methods, members, static_members, staticInstLookup ) { var hasOwn = Array.prototype.hasOwnProperty, defs = {}, smethods = static_members.methods, sprops = static_members.props, _self = this ; util.propParse( props, { each: function( name, value, keywords ) { // disallow use of our internal __initProps() method if ( reserved_members[ name ] === true ) { throw Error( name + " is reserved" ); } // if a member was defined multiple times in the same class // declaration, throw an error if ( hasOwn.call( defs, name ) ) { throw Error( "Cannot redefine method '" + name + "' in same declaration" ); } // keep track of the definitions (only during class declaration) // to catch duplicates defs[ name ] = 1; }, property: function( name, value, keywords ) { var dest = ( keywordStatic( keywords ) ) ? sprops : prop_init; // build a new property, passing in the other members to compare // against for preventing nonsensical overrides _self._memberBuilder.buildProp( dest, null, name, value, keywords, base ); }, getset: function( name, get, set, keywords ) { var dest = ( keywordStatic( keywords ) ) ? smethods : members, is_static = keywordStatic( keywords ), instLookup = ( ( is_static ) ? staticInstLookup : exports.getMethodInstance ); _self._memberBuilder.buildGetterSetter( dest, null, name, get, set, keywords, instLookup, class_id, base ); }, method: function( name, func, is_abstract, keywords ) { var is_static = keywordStatic( keywords ), dest = ( is_static ) ? smethods : members, instLookup = ( is_static ) ? staticInstLookup : exports.getMethodInstance ; // constructor check if ( public_methods[ name ] === true ) { if ( keywords[ 'protected' ] || keywords[ 'private' ] ) { throw TypeError( name + " must be public" ); } } _self._memberBuilder.buildMethod( dest, null, name, func, keywords, instLookup, class_id, base ); if ( is_abstract ) { abstract_methods[ name ] = true; abstract_methods.__length++; } else if ( ( hasOwn.call( abstract_methods, name ) ) && ( is_abstract === false ) ) { // if this was a concrete method, then it should no longer // be marked as abstract delete abstract_methods[ name ]; abstract_methods.__length--; } } } ); } /** * Validates abstract class requirements * * @param {function()} ctor class * @param {string} cname class name * @param {{__length}} abstract_methods object containing abstract methods * * @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" ); } } } /** * 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.} abstract_methods list of abstract methods * @param {Object} members class members * * @return {Function} constructor */ exports.prototype.createCtor = function( cname, abstract_methods, members ) { // concrete class if ( abstract_methods.__length === 0 ) { return this.createConcreteCtor( cname, members ); } // abstract class else { return this.createAbstractCtor( cname ); } } /** * 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 */ exports.prototype.createConcreteCtor = function( cname, members ) { var args = null, _self = this; /** * Constructor function to be returned * * The name is set to ClassInstance because some debuggers (e.g. v8) will * show the name of this function for constructor instances rather than * invoking the toString() method * * @constructor * * Suppressing due to complaints for using __initProps * @suppress {checkTypes} */ function ClassInstance() { if ( !( this instanceof ClassInstance ) ) { // store arguments to be passed to constructor and // instantiate new object args = arguments; return new ClassInstance(); } initInstance( this ); this.__initProps(); // If we're extending, we don't actually want to invoke any class // construction logic. The above is sufficient to use this class in a // prototype, so stop here. if ( _self._extending ) { return; } // generate and store unique instance id attachInstanceId( this, ++_self._instanceId ); // call the constructor, if one was provided if ( typeof this.__construct === 'function' ) { // 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 ) ); args = null; } // 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' ) ) ) { // use __toString if available (see enum_bug), otherwise use // our own defaults this.toString = members[ 'public' ].__toString || ( ( cname ) ? function() { return '#<' + cname + '>'; } : function() { return '#'; } ) ; } }; // provide a more intuitive string representation ClassInstance.toString = ( cname ) ? function() { return cname; } : function() { return '(Class)'; } ; return ClassInstance; } /** * 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 */ exports.prototype.createAbstractCtor = function( cname ) { var _self = this; var __abstract_self = function() { if ( !_self._extending ) { throw Error( "Abstract class " + ( cname || '(anonymous)' ) + " cannot be instantiated" ); } }; __abstract_self.toString = ( cname ) ? function() { return cname; } : function() { return '(AbstractClass)'; } ; return __abstract_self; } /** * 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 * * @param {function()} ctor class * @param {number} cid class id * * @return {undefined} */ exports.prototype._attachPropInit = function( prototype, properties, members, ctor, cid ) { var _self = this; util.defineSecureProp( prototype, '__initProps', function( inherit ) { // defaults to false inherit = !!inherit; var iid = this.__iid, parent = prototype.___$$parent$$; // first initialize the parent's properties, so that ours will overwrite // them var parent_init = parent && parent.__initProps; if ( typeof parent_init === 'function' ) { // 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 var inst_props = _self._visFactory.createPropProxy( this, this.___$$vis$$, properties[ 'public' ] ); // 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. var vis = this.___$$vis$$[ cid ] = _self._visFactory.setup( inst_props, properties, members ); // provide a means to access the actual instance (rather than the // property/visibility object) internally (this will translate to // this.__inst from within a method), but only if we're on our final // object (not a parent) if ( !inherit ) { util.defineSecureProp( vis, '__inst', this ); } }); } /** * 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 * * @return {boolean} true if to be static, otherwise false */ function keywordStatic( keywords ) { return ( keywords[ 'static' ] || keywords[ 'const' ] ) ? true : false ; } /** * Creates and populates the static visibility object * * @param {Function} ctor class * * @return {undefined} */ function initStaticVisibilityObj( ctor ) { var _self = this; /** * the object will simply be another layer in the prototype chain to * prevent protected/private members from being mixed in with the public * * @constructor */ var sobj = function() {}; sobj.prototype = ctor; var sobji = new sobj(); // override __self on the instance's visibility object, giving internal // methods access to the restricted static methods ctor.___$$svis$$ = sobji; // 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() { _self._spropInternal = true; var val = ctor.$.apply( ctor, arguments ); _self._spropInternal = false; return val; }; } /** * Attaches static members to a constructor (class) * * 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) * * @return {undefined} */ function attachStatic( ctor, members, base, inheriting ) { var methods = members.methods, props = members.props, _self = this ; // "Inherit" the parent's static methods by running the parent's static // initialization method. It is important that we do this before anything, // because this will recursively inherit all members in order, permitting // overrides. var baseinit = base.___$$sinit$$; if ( baseinit ) { baseinit( ctor, true ); } // initialize static property if not yet defined if ( !inheriting ) { ctor.___$$sprops$$ = props; // provide a method to access static properties util.defineSecureProp( ctor, '$', function( prop, val ) { // 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 var has = Object.prototype.hasOwnProperty, found = false, // Determine if we were invoked in the context of a class. If // so, use that. Otherwise, use ourself. 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 ) ; // 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'; if ( !found && _self._spropInternal ) { // 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' ; } // if we don't own the property, let the parent(s) handle it if ( found === false ) { // 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 ); } var prop_item = props[ found ][ prop ]; // if a value was provided, this method should be treated as a // setter rather than a getter (we *must* test using // arguments.length to ensure that setting to undefined works) if ( arguments.length > 1 ) { // if const, disallow modification if ( prop_item[ 1 ][ 'const' ] ) { throw TypeError( "Cannot modify constant property '" + prop + "'" ); } prop_item[ 0 ] = val; return context; } else { // return the value return prop_item[ 0 ]; } } ); } // copy over public static methods util.copyTo( ctor, methods[ 'public' ], true ); util.copyTo( ctor.___$$svis$$, methods[ 'protected' ], true ); // private methods should not be inherited by subtypes if ( !inheriting ) { util.copyTo( ctor.___$$svis$$, methods[ 'private' ], true ); } } /** * Initializes class metadata for the given class * * @param {Function} func class to initialize metadata for * @param {Function} cparent class parent * * @return {undefined} * * Suppressed due to warnings for use of __cid * @suppress {checkTypes} */ 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 * * 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. * * 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} */ function initInstance( instance ) { /** @constructor */ 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 ); }; // 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. 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 * * @return {Object|null} instance object if found, otherwise null * * @suppress {checkTypes} */ exports.getMethodInstance = function( inst, cid ) { var iid = inst.__iid, data = inst.___$$vis$$; return ( iid && data ) ? data[ cid ] : null ; } /** * 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) * * @return {boolean} true if class is abstract, otherwise false */ util.defineSecureProp( func, 'isAbstract', function() { return is_abstract; }); } /** * 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 ); } /** * Sets class flags * * @param {Function} ctor class to flag * @param {Object} props class properties * * @return {undefined} */ function attachFlags( ctor, props ) { ctor.___$$final$$ = !!( props.___$$final$$ ); ctor.___$$abstract$$ = !!( props.___$$abstract$$ ); // The properties are no longer needed. Set to undefined rather than delete // (v8 performance) props.___$$final$$ = props.___$$abstract$$ = undefined; } } )( module['ClassBuilder'] = {}, '.' ); /** MethodWrapperFactory **/ ( function( module, __dirname ) { var exports = module.exports = {}; /** * Builds method wrappers * * Copyright (C) 2010,2011 Mike Gerwitz * * This file is part of ease.js. * * ease.js is free software: you can redistribute it and/or modify it under the * terms of the GNU Lesser General Public License as published by the Free * Software Foundation, either version 3 of the License, or (at your option) * any later version. * * This program is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License * for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program. If not, see . * * @author Mike Gerwitz */ /** * Initializes factory to wrap methods * * @param {function(Function,Function,number)} factory function that will * perform the actual * wrapping * * @constructor */ module.exports = exports = function MethodWrapperFactory( factory ) { // permit omission of the 'new' keyword for instantiation if ( !( this instanceof exports ) ) { // module.exports for Closure Compiler return new module.exports( factory ); } this._factory = factory; }; /** * Wraps the provided method * * The returned function is determined by the factory function provided when the * MethodWrapperFactory was instantiated. * * @param {function()} method method to wrap * @param {function()} super_method super method, if overriding * @param {number} cid class id that method is associated with * @param {function()} getInst function to determine instance and return * associated visibility object * @param {string=} name name of method * @param {Object=} keywords method keywords */ exports.prototype.wrapMethod = function( method, super_method, cid, getInst, name, keywords ) { return this._factory( method, super_method, cid, getInst, name, keywords ); }; } )( module['MethodWrapperFactory'] = {}, '.' ); /** MethodWrappers **/ ( function( module, __dirname ) { var exports = module.exports = {}; /** * Default method wrapper functions * * Copyright (C) 2010,2011 Mike Gerwitz * * This file is part of ease.js. * * ease.js is free software: you can redistribute it and/or modify it under the * terms of the GNU Lesser General Public License as published by the Free * Software Foundation, either version 3 of the License, or (at your option) * any later version. * * This program is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License * for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program. If not, see . * * @author Mike Gerwitz */ /** * Method wrappers for standard (non-fallback) * @type {Object} */ exports.standard = { wrapOverride: function( method, super_method, cid, getInst ) { return function() { var context = getInst( this, cid ) || this, retval = undefined ; // the _super property will contain the parent method (we don't // store the previous value for performance reasons and because, // during conventional use, it's completely unnecessary) context.__super = super_method; retval = method.apply( context, arguments ); // prevent sneaky bastards from breaking encapsulation by stealing // method references (we set to undefined rather than deleting it // because deletion causes performance degradation within V8) context.__super = undefined; // if the value returned from the method was the context that we // passed in, return the actual instance (to ensure we do not break // encapsulation) if ( retval === context ) { return this; } return retval; }; }, wrapNew: function( method, super_method, cid, getInst ) { return function() { var context = getInst( this, cid ) || this, retval = undefined ; // invoke the method retval = method.apply( context, arguments ); // if the value returned from the method was the context that we // passed in, return the actual instance (to ensure we do not break // encapsulation) if ( retval === context ) { return this; } return retval; }; }, wrapProxy: function( proxy_to, _, cid, getInst, name, keywords ) { // it is important that we store only a boolean value as to whether or // not this method is static *outside* of the returned closure, so as // not to keep an unnecessary reference to the keywords object var is_static = keywords && keywords[ 'static' ]; return function() { var context = getInst( this, cid ) || this, retval = undefined, dest = ( ( is_static ) ? context.$( proxy_to ) : context[ proxy_to ] ) ; // rather than allowing a cryptic error to be thrown, attempt to // detect when the proxy call will fail and provide a useful error // message if ( !( ( dest !== null ) && ( typeof dest === 'object' ) && ( typeof dest[ name ] === 'function' ) ) ) { throw TypeError( "Unable to proxy " + name + "() call to '" + proxy_to + "'; '" + proxy_to + "' is undefined or '" + name + "' is not a function." ); } retval = dest[ name ].apply( dest, arguments ); // if the object we are proxying to returns itself, then instead // return a reference to *ourself* (so as not to break encapsulation // and to provide a more consistent and sensible API) return ( retval === dest ) ? this : retval; }; } }; } )( module['MethodWrappers'] = {}, '.' ); /** MemberBuilder **/ ( function( module, __dirname ) { var exports = module.exports = {}; /** * Handles building members (properties, methods) * * This prototype could have easily been refactored into a number of others * (e.g. one for each type of member), but that refactoring has been deferred * until necessary to ensure ease.js maintains a relatively small footprint. * Ultimately, however, such a decision is a micro-optimization and shouldn't * harm the design and maintainability of the software. * * TODO: Implementation is inconsistent between various members. For example, * methods use ___$$keywords$$, whereas properties use [ val, keywords ]. Decide * on a common format. * * Copyright (C) 2010,2011 Mike Gerwitz * * This file is part of ease.js. * * ease.js is free software: you can redistribute it and/or modify it under the * terms of the GNU Lesser General Public License as published by the Free * Software Foundation, either version 3 of the License, or (at your option) * any later version. * * This program is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License * for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program. If not, see . * * @author Mike Gerwitz */ var util = require( __dirname + '/util' ), Warning = require( __dirname + '/warn' ).Warning, visibility = [ 'public', 'protected', 'private' ] ; /** * Responsible for building class members * * @param {Function} wrap_method method wrapper * @param {Function} wrap_override method override wrapper * @param {Function} wrap_proxy method proxy wrapper * @param {MemberBuilderValidator} validate member validator * * @constructor */ module.exports = function MemberBuilder( wrap_method, wrap_override, wrap_proxy, validate ) { // permit omitting 'new' keyword if ( !( this instanceof module.exports ) ) { return new module.exports( wrap_method, wrap_override, wrap_proxy, validate ); } this._wrapMethod = wrap_method; this._wrapOverride = wrap_override; this._wrapProxy = wrap_proxy; this._validate = validate; }; // we're throwing everything into the prototype exports = module.exports.prototype; /** * Initializes member object * * The member object contains members for each level of visibility (public, * protected and private). * * @param {Object} mpublic default public members * @param {Object} mprotected default protected members * @param {Object} mprivate default private members * * @return {__visobj} */ exports.initMembers = function( mpublic, mprotected, mprivate ) { return { 'public': mpublic || {}, 'protected': mprotected || {}, 'private': mprivate || {} }; }; /** * Copies a method to the appropriate member prototype, depending on * visibility, and assigns necessary metadata from keywords * * @param {__visobj} members * @param {!Object} meta metadata container * @param {string} name property name * @param {*} value property value * * @param {!Object.} keywords parsed keywords * * @param {Function} instCallback function to call in order to retrieve * object to bind 'this' keyword to * * @param {number} cid class id * @param {Object=} base optional base object to scan * * @return {undefined} */ exports.buildMethod = function( members, meta, name, value, keywords, instCallback, cid, base ) { // TODO: We can improve performance by not scanning each one individually // every time this method is called var prev_data = scanMembers( members, name, base ), prev = ( prev_data ) ? prev_data.member : null, prev_keywords = ( prev && prev.___$$keywords$$ ), dest = getMemberVisibility( members, keywords, name ); ; // ensure that the declaration is valid (keywords make sense, argument // length, etc) this._validate.validateMethod( name, value, keywords, prev_data, prev_keywords ); // we might be overriding an existing method if ( keywords[ 'proxy' ] ) { // TODO: Note that this is not compatible with method hiding, due to its // positioning (see hideMethod() below); address once method hiding is // implemented (the validators currently handle everything else) dest[ name ] = this._createProxy( value, instCallback, cid, name, keywords ); } else if ( prev ) { if ( keywords[ 'override' ] || prev_keywords[ 'abstract' ] ) { // override the method dest[ name ] = this._overrideMethod( prev, value, instCallback, cid ); } else { // by default, perform method hiding, even if the keyword was not // provided (the keyword simply suppresses the warning) dest[ name ] = hideMethod( prev, value, instCallback, cid ); } } else if ( keywords[ 'abstract' ] ) { // we do not want to wrap abstract methods, since they are not callable dest[ name ] = value; } else { // we are not overriding the method, so simply copy it over, wrapping it // to ensure privileged calls will work properly dest[ name ] = this._overrideMethod( null, value, instCallback, cid ); } // store keywords for later reference (needed for pre-ES5 fallback) dest[ name ].___$$keywords$$ = keywords; }; /** * Copies a property to the appropriate member prototype, depending on * visibility, and assigns necessary metadata from keywords * * @param {__visobj} members * @param {!Object} meta metadata container * @param {string} name property name * @param {*} value property value * * @param {!Object.} keywords parsed keywords * * @param {Object=} base optional base object to scan * * @return {undefined} */ exports.buildProp = function( members, meta, name, value, keywords, base ) { // TODO: We can improve performance by not scanning each one individually // every time this method is called var prev_data = scanMembers( members, name, base ), prev = ( prev_data ) ? prev_data.member : null, prev_keywords = ( prev ) ? prev[ 1 ] : null; this._validate.validateProperty( name, value, keywords, prev_data, prev_keywords ); getMemberVisibility( members, keywords, name )[ name ] = [ value, keywords ]; }; /** * Copies a getter/setter to the appropriate member prototype, depending on * visibility, and assigns necessary metadata from keywords * * TODO: This should essentially mirror buildMethod with regards to overrides, * proxies, etc. * * @param {!__visobj} members * @param {!Object} meta metadata container * @param {string} name getter name * @param {*} get getter value * @param {*} set setter value * * @param {!Object.} keywords parsed keywords * * @param {Function} instCallback function to call in order to retrieve * object to bind 'this' keyword to * * @param {number} cid class id * @param {Object=} base optional base object to scan * * @return {undefined} * * Closure Compiler is improperly throwing warnings on Object.defineProperty(): * @suppress {checkTypes} */ exports.buildGetterSetter = function( members, meta, name, get, set, keywords, instCallback, cid, base ) { var prev_data = scanMembers( members, name, base ), prev_keywords = ( ( prev_data && prev_data.get ) ? prev_data.get.___$$keywords$$ : null ) ; this._validate.validateGetterSetter( name, {}, keywords, prev_data, prev_keywords ); if ( get ) { get = this._overrideMethod( null, get, instCallback, cid ); // ensure we store the keywords *after* the override, otherwise they // will be assigned to the wrapped function (the getter) get.___$$keywords$$ = keywords; } Object.defineProperty( getMemberVisibility( members, keywords, name ), name, { get: get, set: ( set ) ? this._overrideMethod( null, set, instCallback, cid ) : set, enumerable: true, configurable: false } ); }; /** * Returns member prototype to use for the requested visibility * * Will throw an exception if multiple access modifiers were used. * * @param {__visobj} members * * @param {!Object.} keywords parsed keywords * @param {string} name member name * * @return {Object} reference to visibility of members argument to use */ function getMemberVisibility( members, keywords, name ) { var viserr = function() { throw TypeError( "Only one access modifier may be used for definition of '" + name + "'" ); } // there's cleaner ways of doing this, but consider it loop unrolling for // performance if ( keywords[ 'private' ] ) { ( keywords[ 'public' ] || keywords[ 'protected' ] ) && viserr(); return members[ 'private' ]; } else if ( keywords[ 'protected' ] ) { ( keywords[ 'public' ] || keywords[ 'private' ] ) && viserr(); return members[ 'protected' ]; } else { // public keyword is the default, so explicitly specifying it is only // for clarity ( keywords[ 'private' ] || keywords[ 'protected' ] ) && viserr(); return members[ 'public' ]; } } /** * Scan each level of visibility for the requested member * * @param {__visobj} members * * @param {string} name member to locate * @param {Object=} base optional base object to scan * * @return {{get,set,member}|null} */ function scanMembers( members, name, base ) { var i = visibility.length, member = null; // locate requested member by scanning each level of visibility while ( i-- ) { var visobj = members[ visibility[ i ] ]; // In order to support getters/setters, we must go off of the // descriptor. We must also ignore base properties (last argument), such // as Object.prototype.toString(). However, we must still traverse the // prototype chain. if ( member = util.getPropertyDescriptor( visobj, name, true ) ) { return { get: member.get, set: member.set, member: member.value }; } } // if a second comparison object was given, try again using it instead of // the original members object if ( base !== undefined ) { var base_methods = base.___$$methods$$, base_props = base.___$$props$$; // scan the base's methods and properties, if they are available return ( base_methods && scanMembers( base_methods, name ) ) || ( base_props && scanMembers( base_props, name ) ) || null ; } // nothing was found return null; } /** * Hide a method with a "new" method */ function hideMethod( super_method, new_method, instCallback, cid ) { // TODO: This function is currently unimplemented. It exists at present to // provide a placeholder and ensure that the override keyword is required to // override a parent method. // // We should never get to this point if the default validation rule set is // used to prevent omission of the 'override' keyword. throw Error( 'Method hiding not yet implemented (we should never get here; bug).' ); } /** * Create a method that proxies to the method of another object * * @param {string} proxy_to name of property (of instance) to proxy to * * @param {Function} instCallback function to call in order to retrieve * object to bind 'this' keyword to * * @param {number} cid class id * @param {string} mname name of method to invoke on destination object * @param {Object} keywords method keywords * * @return {Function} proxy method */ exports._createProxy = function( proxy_to, instCallback, cid, mname, keywords ) { return this._wrapProxy.wrapMethod( proxy_to, null, cid, instCallback, mname, keywords ); }; /** * Generates a method override function * * The override function simply wraps the method so that its invocation will * pass a __super property. This property may be used to invoke the overridden * method. * * @param {function()} super_method method to override * @param {function()} new_method method to override with * * @param {Function} instCallback function to call in order to retrieve * object to bind 'this' keyword to * * @param {number} cid class id * * @return {function()} override method */ exports._overrideMethod = function( super_method, new_method, instCallback, cid ) { instCallback = instCallback || function() {}; // return a function that permits referencing the super method via the // __super property var override = null; // are we overriding? override = ( ( super_method ) ? this._wrapOverride : this._wrapMethod ).wrapMethod( new_method, super_method, cid, instCallback ); // This is a trick to work around the fact that we cannot set the length // property of a function. Instead, we define our own property - __length. // This will store the expected number of arguments from the super method. // This way, when a method is being overridden, we can check to ensure its // compatibility with its super method. util.defineSecureProp( override, '__length', ( new_method.__length || new_method.length ) ); return override; } /** * Return the visibility level as a numeric value, where 0 is public and 2 is * private * * @param {Object} keywords keywords to scan for visibility level * * @return {number} visibility level as a numeric value */ exports._getVisibilityValue = function( keywords ) { if ( keywords[ 'protected' ] ) { return 1; } else if ( keywords[ 'private' ] ) { return 2; } else { // default is public return 0; } } } )( module['MemberBuilder'] = {}, '.' ); /** MemberBuilderValidator **/ ( function( module, __dirname ) { var exports = module.exports = {}; /** * Validation rules for members * * Copyright (C) 2010,2011 Mike Gerwitz * * This file is part of ease.js. * * ease.js is free software: you can redistribute it and/or modify it under the * terms of the GNU Lesser General Public License as published by the Free * Software Foundation, either version 3 of the License, or (at your option) * any later version. * * This program is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License * for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program. If not, see . * * @author Mike Gerwitz */ module.exports = exports = function MemberBuilderValidator( warn_handler ) { // permit omitting 'new' keyword if ( !( this instanceof module.exports ) ) { return new module.exports( warn_handler ); } this._warningHandler = warn_handler || function() {}; }; /** * Validates a method declaration, ensuring that keywords are valid, overrides * make sense, etc. * * Throws exception on validation failure * * @param {string} name method name * @param {*} value method value * * @param {Object.} keywords parsed keywords * * @param {Object} prev_data data of member being overridden * @param {Object} prev_keywords keywords of member being overridden * * @return {undefined} */ exports.prototype.validateMethod = function( name, value, keywords, prev_data, prev_keywords ) { var prev = ( prev_data ) ? prev_data.member : null; if ( keywords[ 'abstract' ] ) { // do not permit private abstract methods (doesn't make sense, since // they cannot be inherited/overridden) if ( keywords[ 'private' ] ) { throw TypeError( "Method '" + name + "' cannot be both private and abstract" ); } } // const doesn't make sense for methods; they're always immutable if ( keywords[ 'const' ] ) { throw TypeError( "Cannot declare method '" + name + "' as constant; keyword is " + "redundant" ); } // virtual static does not make sense, as static methods cannot be // overridden if ( keywords[ 'virtual' ] && ( keywords[ 'static' ] ) ) { throw TypeError( "Cannot declare static method '" + name + "' as virtual" ); } // do not allow overriding getters/setters if ( prev_data && ( prev_data.get || prev_data.set ) ) { throw TypeError( "Cannot override getter/setter '" + name + "' with method" ); } if ( keywords[ 'proxy' ] ) { // proxies are expected to provide the name of the destination object if ( typeof value !== 'string' ) { throw TypeError( "Cannot declare proxy method '" + name + "'; string value " + "expected" ); } else if ( keywords[ 'abstract' ] ) { // proxies are always concrete throw TypeError( "Proxy method '" + name + "' cannot be abstract" ); } } // search for any previous instances of this member if ( prev ) { // perform this check first, as it will make more sense than those that // follow, should this condition be satisfied if ( prev_keywords[ 'private' ] ) { throw TypeError( "Private member name '" + name + "' conflicts with supertype" ); } // disallow overriding properties with methods if ( !( typeof prev === 'function' ) ) { throw TypeError( "Cannot override property '" + name + "' with method" ); } // disallow overriding non-virtual methods if ( keywords[ 'override' ] && !( prev_keywords[ 'virtual' ] ) ) { throw TypeError( "Cannot override non-virtual method '" + name + "'" ); } // do not allow overriding concrete methods with abstract if ( keywords[ 'abstract' ] && !( prev_keywords[ 'abstract' ] ) ) { throw TypeError( "Cannot override concrete method '" + name + "' with " + "abstract method" ); } // ensure parameter list is at least the length of its supertype if ( ( value.__length || value.length ) < ( prev.__length || prev.length ) ) { throw TypeError( "Declaration of method '" + name + "' must be compatible " + "with that of its supertype" ); } // do not permit visibility deescalation if ( this._getVisibilityValue( prev_keywords ) < this._getVisibilityValue( keywords ) ) { throw TypeError( "Cannot de-escalate visibility of method '" + name + "'" ); } // Disallow overriding method without override keyword (unless parent // method is abstract). In the future, this will provide a warning to // default to method hiding. if ( !( keywords[ 'override' ] || prev_keywords[ 'abstract' ] ) ) { throw TypeError( "Attempting to override method '" + name + "' without 'override' keyword" ); } } else if ( keywords[ 'override' ] ) { // using the override keyword without a super method may indicate a bug, // but it shouldn't stop the class definition (it doesn't adversely // affect the functionality of the class, unless of course the method // attempts to reference a supertype) this._warningHandler( Error( "Method '" + name + "' using 'override' keyword without super method" ) ); } }; /** * Validates a property declaration, ensuring that keywords are valid, overrides * make sense, etc. * * Throws exception on validation failure * * @param {string} name method name * @param {*} value method value * * @param {Object.} keywords parsed keywords * * @param {Object} prev_data data of member being overridden * @param {Object} prev_keywords keywords of member being overridden * * @return {undefined} */ exports.prototype.validateProperty = function( name, value, keywords, prev_data, prev_keywords ) { var prev = ( prev_data ) ? prev_data.member : null; // do not permit visibility de-escalation if ( prev ) { // perform this check first, as it will make more sense than those that // follow, should this condition be satisfied if ( prev_keywords[ 'private' ] ) { throw TypeError( "Private member name '" + name + "' conflicts with supertype" ); } // disallow overriding methods with properties if ( typeof prev === 'function' ) { throw new TypeError( "Cannot override method '" + name + "' with property" ); } if ( this._getVisibilityValue( prev_keywords ) < this._getVisibilityValue( keywords ) ) { throw TypeError( "Cannot de-escalate visibility of property '" + name + "'" ); } } // do not allow overriding getters/setters if ( prev_data && ( prev_data.get || prev_data.set ) ) { throw TypeError( "Cannot override getter/setter '" + name + "' with property" ); } // abstract properties do not make sense if ( keywords[ 'abstract' ] ) { throw TypeError( "Property '" + name + "' cannot be declared as abstract" ); } // constants are static if ( keywords[ 'static' ] && keywords[ 'const' ] ) { throw TypeError( "Static keyword cannot be used with const for property '" + name + "'" ); } // properties are inherently virtual if ( keywords['virtual'] ) { throw TypeError( "Cannot declare property '" + name + "' as virtual" ); } }; /** * Performs common validations on getters/setters * * If a problem is found, an exception will be thrown. * * @param {string} name getter/setter name * @param {Object.} keywords parsed keywords * * @return {undefined} */ exports.prototype.validateGetterSetter = function( name, value, keywords, prev_data, prev_keywords ) { var prev = ( prev_data ) ? prev_data.member : null, prev_gs = ( ( prev_data && ( prev_data.get || prev_data.set ) ) ? true : false ) ; // abstract getters/setters are not yet supported if ( keywords[ 'abstract' ] ) { throw TypeError( "Cannot declare getter/setter '" + name + "' as abstract" ); } // for const getters/setters, omit the setter if ( keywords[ 'const' ] ) { throw TypeError( "Cannot declare const getter/setter '" + name + "'" ); } // virtual static does not make sense, as static methods cannot be // overridden if ( keywords[ 'virtual' ] && ( keywords[ 'static' ] ) ) { throw TypeError( "Cannot declare static method '" + name + "' as virtual" ); } if ( prev || prev_gs ) { // perform this check first, as it will make more sense than those that // follow, should this condition be satisfied if ( prev_keywords && prev_keywords[ 'private' ] ) { throw TypeError( "Private member name '" + name + "' conflicts with supertype" ); } // To speed up the system we'll simply check for a getter/setter, rather // than checking separately for methods/properties. This is at the // expense of more detailed error messages. They'll live. if ( !( prev_gs ) ) { throw TypeError( "Cannot override method or property '" + name + "' with getter/setter" ); } if ( !( prev_keywords && prev_keywords[ 'virtual' ] ) ) { throw TypeError( "Cannot override non-virtual getter/setter '" + name + "'" ); } if ( !( keywords[ 'override' ] ) ) { throw TypeError( "Attempting to override getter/setter '" + name + "' without 'override' keyword" ); } // do not permit visibility de-escalation if ( this._getVisibilityValue( prev_keywords || {} ) < this._getVisibilityValue( keywords ) ) { throw TypeError( "Cannot de-escalate visibility of getter/setter '" + name + "'" ); } } else if ( keywords[ 'override' ] ) { // using the override keyword without a super method may indicate a bug // in the user's code this._warningHandler( Error( "Getter/setter '" + name + "' using 'override' keyword without super getter/setter" ) ); } } /** * Return the visibility level as a numeric value, where 0 is public and 2 is * private * * @param {Object} keywords keywords to scan for visibility level * * @return {number} visibility level as a numeric value */ exports.prototype._getVisibilityValue = function( keywords ) { if ( keywords[ 'protected' ] ) { return 1; } else if ( keywords[ 'private' ] ) { return 2; } else { // default is public return 0; } } } )( module['MemberBuilderValidator'] = {}, '.' ); /** VisibilityObjectFactory **/ ( function( module, __dirname ) { var exports = module.exports = {}; /** * Contains visibility object factory * * Copyright (C) 2010,2011 Mike Gerwitz * * This file is part of ease.js. * * ease.js is free software: you can redistribute it and/or modify it under the * terms of the GNU Lesser General Public License as published by the Free * Software Foundation, either version 3 of the License, or (at your option) * any later version. * * This program is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License * for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program. If not, see . * * @author Mike Gerwitz */ /** * XXX: tightly coupled */ var util = require( __dirname + '/util' ); /** * Initializes visibility object factory * * The visibility object is the "magic" behind ease.js. This factory creates the * object that holds the varying levels of visibility, which are swapped out and * inherited depending on circumstance. * * @constructor */ module.exports = exports = function VisibilityObjectFactory() { // permit omitting 'new' keyword if ( !( this instanceof exports ) ) { // module.exports instead of exports because Closure Compiler seems to // be confused return new module.exports(); } }; /** * Sets up properties * * This includes all members (including private). Private members will be set up * in a separate object, so that they can be easily removed from the mix. That * object will include the destination object in the prototype, so that the * access should be transparent. This object is returned. * * Properties are expected in the following format. Note that keywords are * ignored: * { public: { prop: [ value, { keyword: true } ] } } * * @param {Object} dest destination object * @param {Object} properties properties to copy * @param {Object=} methods methods to copy * * @return {Object} object containing private members and dest as prototype */ exports.prototype.setup = function setup( dest, properties, methods ) { // create the private layer atop of the destination object var obj = this._createPrivateLayer( dest, properties ); // initialize each of the properties for this instance to // ensure we're not sharing references to prototype values this._doSetup( dest, properties[ 'public' ] ); // Do the same for protected, but only if they do not exist already in // public. The reason for this is because the property object is laid /atop/ // of the public members, meaning that a parent's protected members will // take precedence over a subtype's overriding /public/ members. Uh oh. this._doSetup( dest, properties[ 'protected' ], methods[ 'protected' ], 'public' ); // then add the private parts this._doSetup( obj, properties[ 'private' ], methods[ 'private' ] ); return obj; }; /** * Add an extra layer atop the destination object, which will contain the * private members * * The object provided will be used as the prototype for the new private layer, * so the provided object will be accessible on the prototype chain. * * Subtypes may override this method to alter the functionality of the private * visibility object (e.g. to prevent it from being created). * * @param {Object} atop_of object to add private layer atop of * @param {Object} properties properties * * @return {Object} private layer with given object as prototype */ exports.prototype._createPrivateLayer = function( atop_of, properties ) { /** @constructor */ var obj_ctor = function() {}; obj_ctor.prototype = atop_of; // we'll be returning an instance, so that the prototype takes effect var obj = new obj_ctor(); // All protected properties need to be proxied from the private object // (which will be passed as the context) to the object containing protected // values. Otherwise, the protected property values would be set on the // private object, making them inaccessible to subtypes. this.createPropProxy( atop_of, obj, properties[ 'protected' ] ); return obj; }; /** * Set up destination object by copying over properties and methods * * @param {Object} dest destination object * @param {Object} properties properties to copy * @param {Object} methods methods to copy * @param {boolean} unless_keyword do not set if keyword is set on existing * method * * @return {undefined} */ exports.prototype._doSetup = function( dest, properties, methods, unless_keyword ) { var hasOwn = Array.prototype.hasOwnProperty, pre = null; // copy over the methods if ( methods !== undefined ) { for ( var method_name in methods ) { if ( hasOwn.call( methods, method_name ) ) { pre = dest[ method_name ]; // If requested, do not copy the method over if it already // exists in the destination object. Don't use hasOwn here; // unnecessary overhead and we want to traverse any prototype // chains. We do not check the public object directly, for // example, because we need a solution that will work if a proxy // is unsupported by the engine. // // Also note that we need to allow overriding if it exists in // the protected object (we can override protected with // protected). This is the *last* check to ensure a performance // hit is incured *only* if we're overriding protected with // protected. if ( !unless_keyword || ( pre === undefined ) || !( pre.___$$keywords$$[ unless_keyword ] ) ) { dest[ method_name ] = methods[ method_name ]; } } } } // initialize private/protected properties and store in instance data for ( var prop in properties ) { if ( hasOwn.call( properties, prop ) ) { dest[ prop ] = util.clone( properties[ prop ][ 0 ] ); } } } /** * Creates a proxy for all given properties to the given base * * The proxy uses getters/setters to forward all calls to the base. The * destination object will be used as the proxy. All properties within props * will be used proxied. * * To summarize: for each property in props, all gets and sets will be forwarded * to base. * * Please note that this does not use the JS proxy implementation. That will be * done in the future for engines that support it. * * @param {Object} base object to proxy to * @param {Object} dest object to treat as proxy (set getters/setters on) * @param {Object} props properties to proxy * * @return {Object} returns dest */ exports.prototype.createPropProxy = function( base, dest, props ) { var hasOwn = Object.prototype.hasOwnProperty; for ( var prop in props ) { if ( !( hasOwn.call( props, prop ) ) ) { continue; } ( function( prop ) { // just in case it's already defined, so we don't throw an error dest[ prop ] = undefined; // public properties, when set internally, must forward to the // actual variable Object.defineProperty( dest, prop, { set: function( val ) { base[ prop ] = val; }, get: function() { return base[ prop ]; }, enumerable: true } ); } ).call( null, prop ); } return dest; }; } )( module['VisibilityObjectFactory'] = {}, '.' ); /** FallbackVisibilityObjectFactory **/ ( function( module, __dirname ) { var exports = module.exports = {}; /** * Contains fallback visibility object factory * * Copyright (C) 2010,2011 Mike Gerwitz * * This file is part of ease.js. * * ease.js is free software: you can redistribute it and/or modify it under the * terms of the GNU Lesser General Public License as published by the Free * Software Foundation, either version 3 of the License, or (at your option) * any later version. * * This program is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License * for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program. If not, see . * * @author Mike Gerwitz */ /** * Initializes fallback visibility object factory * * Unlike the standard visibility object, fallback does not create various * layers. This is for the simple fact that setting a value on one of the layers * is not visible to layers beneath it (its prototypes). Fallback is necessary * if proxy support or emulation (via ES5 getters/setters) is unavailable. */ module.exports = exports = function FallbackVisibilityObjectFactory() { // permit omitting 'new' keyword if ( !( this instanceof exports ) ) { // module.exports for Closure Compiler return new module.exports(); } }; /** * "Inherit" from VisibilityObjectFactory */ exports.prototype = require( __dirname + '/VisibilityObjectFactory' )(); /** * Do not create private visibility layer * * We're likely falling back because we cannot properly support the private * visibility layer. Therefore, it will be omitted. * * @param {Object} atop_of will be returned, unmodified * @param {Object} properties ignored * * @return {Object} provided object with no additional layer */ exports.prototype._createPrivateLayer = function( atop_of, properties ) { return atop_of; }; /** * Does not create property proxy * * The fallback implementation is used because proxies are not supported and * cannot be emulated with getters/setters. * * @param {Object} base will be returned, unmodified * @param {Object} dest ignored * @param {Object} props ignored * * @return {Object} given base */ exports.prototype.createPropProxy = function( base, dest, props ) { return base; }; } )( module['FallbackVisibilityObjectFactory'] = {}, '.' ); /** VisibilityObjectFactoryFactory **/ ( function( module, __dirname ) { var exports = module.exports = {}; /** * Contains factory for visibility object factory * * Copyright (C) 2010,2011 Mike Gerwitz * * This file is part of ease.js. * * ease.js is free software: you can redistribute it and/or modify it under the * terms of the GNU Lesser General Public License as published by the Free * Software Foundation, either version 3 of the License, or (at your option) * any later version. * * This program is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License * for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program. If not, see . * * @author Mike Gerwitz * @package core * * XXX: Figure out how to resolve Closure Compiler's warning about shared type * information */ // XXX: Tightly coupled var util = require( __dirname + '/util' ), VisibilityObjectFactory = require( __dirname + '/VisibilityObjectFactory' ), FallbackVisibilityObjectFactory = require( __dirname + '/FallbackVisibilityObjectFactory' ) ; /** * Responsible for instantiating the VisibilityObjectFactory appropriate for the * runtime environment * * This prototype determines what class should be instantiated. If we are within * an ECMAScript 5 environment, we can take full advantage of the standard * visibility object implementation. Otherwise, we are unable to emulate proxies * and must fall back on a less sophisticated implementation that sacrifices * visibility support. */ exports.fromEnvironment = function() { // if falling back, return fallback, otherwise standard return ( util.definePropertyFallback() ) ? FallbackVisibilityObjectFactory() : VisibilityObjectFactory() ; }; } )( module['VisibilityObjectFactoryFactory'] = {}, '.' ); /** class **/ ( function( module, __dirname ) { var exports = module.exports = {}; /** * Contains basic inheritance mechanism * * Copyright (C) 2010,2011 Mike Gerwitz * * This file is part of ease.js. * * ease.js is free software: you can redistribute it and/or modify it under the * terms of the GNU Lesser General Public License as published by the Free * Software Foundation, either version 3 of the License, or (at your option) * any later version. * * This program is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License * for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program. If not, see . * * @author Mike Gerwitz */ var util = require( __dirname + '/util' ), ClassBuilder = require( __dirname + '/ClassBuilder' ), warn = require( __dirname + '/warn' ), Warning = warn.Warning, MethodWrapperFactory = require( __dirname + '/MethodWrapperFactory' ), wrappers = require( __dirname + '/MethodWrappers' ).standard, class_builder = ClassBuilder( require( __dirname + '/MemberBuilder' )( MethodWrapperFactory( wrappers.wrapNew ), MethodWrapperFactory( wrappers.wrapOverride ), MethodWrapperFactory( wrappers.wrapProxy ), require( __dirname + '/MemberBuilderValidator' )( function( warning ) { warn.handle( Warning( warning ) ); } ) ), require( __dirname + '/VisibilityObjectFactoryFactory' ) .fromEnvironment() ) ; /** * This module may be invoked in order to provide a more natural looking class * definition mechanism * * This may not be used to extend existing classes. To extend an existing class, * use the class's extend() method. If unavailable (or extending a non-ease.js * class/object), use the module's extend() method. * * @param {string|Object} namedef optional name or definition * @param {Object=} def class definition if first argument is name * * @return {Function|Object} new class or staging object */ module.exports = function( namedef, def ) { var type = ( typeof namedef ), result = null ; switch ( type ) { // anonymous class case 'object': result = createAnonymousClass.apply( null, arguments ); break; // named class case 'string': result = createNamedClass.apply( null, arguments ); break; default: // we don't know what to do! throw TypeError( "Expecting anonymous class definition or named class definition" ); } return result; }; /** * Creates a class, inheriting either from the provided base class or the * default base class * * @param {Function|Object} baseordfn parent or definition object * @param {Object=} dfn definition object if parent provided * * @return {Function} extended class */ module.exports.extend = function( baseordfn, dfn ) { return extend.apply( this, arguments ); }; /** * Implements an interface or set of interfaces * * @param {...Function} interfaces interfaces to implement * * @return {Object} intermediate interface object */ module.exports.implement = function( interfaces ) { // implement on empty base return createImplement( null, Array.prototype.slice.call( arguments ) ); }; /** * 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 || {}; return ( obj.prototype instanceof ClassBuilder.ClassBase ) ? true : false ; }; /** * Determines whether the provided object is an instance of a class created * through ease.js * * @param {Object} obj object to test * * @return {boolean} true if instance of class (created through ease.js), * otherwise false */ module.exports.isClassInstance = function( obj ) { obj = obj || {}; return ( obj instanceof ClassBuilder.ClassBase ) ? true : false; }; /** * 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 */ module.exports.isInstanceOf = ClassBuilder.isInstanceOf; /** * Alias for isInstanceOf() * * May read better in certain situations (e.g. Cat.isA( Mammal )) and more * accurately conveys the act of inheritance, implementing interfaces and * traits, etc. */ module.exports.isA = module.exports.isInstanceOf; /** * Creates a new anonymous Class from the given class definition * * @param {Object} def class definition * * @return {Function} new anonymous class */ function createAnonymousClass( def ) { // ensure we have the proper number of arguments (if they passed in // too many, it may signify that they don't know what they're doing, // and likely they're not getting the result they're looking for) if ( arguments.length > 1 ) { throw Error( "Expecting one argument for anonymous Class definition; " + arguments.length + " given." ); } return extend( def ); } /** * Creates a new named Class from the given class definition * * @param {string} name class name * @param {Object} def class definition * * @return {Function|Object} new named class or staging object if definition * was not provided */ function createNamedClass( name, def ) { // if too many arguments were provided, it's likely that they're // expecting some result that they're not going to get if ( arguments.length > 2 ) { throw Error( "Expecting at most two arguments for definition of named Class '" + name + "'; " + arguments.length + " given." ); } // if no definition was given, return a staging object, to apply the name to // the class once it is actually created if ( def === undefined ) { return createStaging( name ); } // the definition must be an object else if ( typeof def !== 'object' ) { throw TypeError( "Unexpected value for definition of named Class '" + name + "'; object expected" ); } // add the name to the definition def.__name = name; return extend( def ); } /** * Creates a staging object to stage a class name * * The class name will be applied to the class generated by operations performed * on the staging object. This allows applying names to classes that need to be * extended or need to implement interfaces. * * @param {string} cname desired class name * * @return {Object} object staging the given class name */ function createStaging( cname ) { return { extend: function() { var args = Array.prototype.slice.apply( arguments ); // extend() takes a maximum of two arguments. If only one // argument is provided, then it is to be the class definition. // Otherwise, the first argument is the supertype and the second // argument is the class definition. Either way you look at it, // the class definition is always the final argument. // // We want to add the name to the definition. args[ args.length - 1 ].__name = cname; return extend.apply( null, args ); }, implement: function() { // implement on empty base, providing the class name to be used once // extended return createImplement( null, Array.prototype.slice.call( arguments ), cname ); } }; } /** * Creates an intermediate object to permit implementing interfaces * * This object defers processing until extend() is called. This intermediate * object ensures that a usable class is not generated until after extend() is * called, as it does not make sense to create a class without any * body/definition. * * @param {Object} base base class to implement atop of, or null * @param {Array} ifaces interfaces to implement * @param {string=} cname optional class name once extended * * @return {Object} intermediate implementation object */ function createImplement( base, ifaces, cname ) { // Defer processing until after extend(). This also ensures that implement() // returns nothing usable. return { extend: function() { var args = Array.prototype.slice.call( arguments ), def = args.pop(), ext_base = args.pop() ; // if any arguments remain, then they likely misunderstood what this // method does if ( args.length > 0 ) { throw Error( "Expecting no more than two arguments for extend()" ); } // if a base was already provided for extending, don't allow them to // give us yet another one (doesn't make sense) if ( base && ext_base ) { throw Error( "Cannot override parent " + base.toString() + " with " + ext_base.toString() + " via extend()" ); } // if a name was provided, use it if ( cname ) { def.__name = cname; } // If a base was provided when createImplement() was called, use // that. Otherwise, use the extend() base passed to this function. // If neither of those are available, extend from an empty class. ifaces.push( base || ext_base || extend( {} ) ); return extend.call( null, implement.apply( this, ifaces ), def ); } }; } /** * 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. * * @param {Function|Object} _ parent or definition object * @param {Object=} __ definition object if parent was provided * * @return {Function} extended class */ function extend( _, __ ) { // set up the new class var new_class = class_builder.build.apply( class_builder, arguments ); // set up some additional convenience props setupProps( new_class ); // lock down the new class (if supported) to ensure that we can't add // members at runtime util.freeze( new_class ); return new_class; } /** * Implements interface(s) into an object * * This will copy all of the abstract methods from the interface and merge it * into the given object. * * @param {Object} baseobj base object * @param {...Function} interfaces interfaces to implement into dest * * @return {Object} destination object with interfaces implemented */ var implement = function( baseobj, interfaces ) { var args = Array.prototype.slice.call( arguments ), dest = {}, base = args.pop(), len = args.length, arg = null, implemented = [], make_abstract = false ; // add each of the interfaces for ( var i = 0; i < len; i++ ) { arg = args[ i ]; // copy all interface methods to the class (does not yet deep copy) util.propParse( arg.prototype, { method: function( name, func, is_abstract, keywords ) { dest[ 'abstract ' + name ] = func.definition; make_abstract = true; } } ); implemented.push( arg ); } // xxx: temporary if ( make_abstract ) { dest.___$$abstract$$ = true; } // create a new class with the implemented abstract methods var class_new = module.exports.extend( base, dest ); ClassBuilder.getMeta( class_new ).implemented = implemented; return class_new; } /** * Sets up common properties for the provided function (class) * * @param {function()} func function (class) to set up * * @return {undefined} */ function setupProps( func ) { attachExtend( func ); attachImplement( func ); } /** * Attaches extend method to the given function (class) * * @param {Function} func function (class) to attach method to * * @return {undefined} */ function attachExtend( func ) { /** * Shorthand for extending classes * * This method can be invoked on the object, rather than having to call * Class.extend( this ). * * @param {Object} props properties to add to extended class * * @return {Object} extended class */ util.defineSecureProp( func, 'extend', function( props ) { return extend( this, props ); }); } /** * Attaches implement method to the given function (class) * * Please see the implement() export of this module for more information. * * @param {function()} func function (class) to attach method to * * @return {undefined} */ function attachImplement( func ) { util.defineSecureProp( func, 'implement', function() { return createImplement( func, Array.prototype.slice.call( arguments ) ); }); } } )( module['class'] = {}, '.' ); /** class_final **/ ( function( module, __dirname ) { var exports = module.exports = {}; /** * Wrapper permitting the definition of final classes * * Copyright (C) 2010,2011 Mike Gerwitz * * This file is part of ease.js. * * ease.js is free software: you can redistribute it and/or modify it under the * terms of the GNU Lesser General Public License as published by the Free * Software Foundation, either version 3 of the License, or (at your option) * any later version. * * This program is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License * for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program. If not, see . * * @author Mike Gerwitz */ var Class = require( __dirname + '/class' ); /** * Creates a final class * * @return {Function} final class */ exports = module.exports = function() { markFinal( arguments ); // forward everything to Class var result = Class.apply( this, arguments ); if ( !Class.isClass( result ) ) { finalOverride( result ); } return result; }; /** * Creates a final class from a class extend operation * * @return {Function} final class */ exports.extend = function() { markFinal( arguments ); return Class.extend.apply( this, arguments ); }; /** * Causes a definition to be flagged as final * * This function assumes the last argument to be the definition, which is the * common case, and modifies the object referenced by that argument. * * @param {!Arguments} args arguments to parse * * @return {undefined} */ function markFinal( args ) { // the last argument _should_ be the definition var dfn = args[ args.length - 1 ]; if ( typeof dfn === 'object' ) { // mark as abstract dfn.___$$final$$ = true; } } /** * Overrides object members to permit final classes * * @param {Object} obj object to override * * @return {undefined} */ function finalOverride( obj ) { var extend = obj.extend; // wrap extend, applying the abstract flag obj.extend = function() { markFinal( arguments ); return extend.apply( this, arguments ); }; } } )( module['class_final'] = {}, '.' ); /** FallbackMemberBuilder **/ ( function( module, __dirname ) { var exports = module.exports = {}; /** * Handles building members (properties, methods) in a pre-ES5 environment * * Copyright (C) 2010,2011 Mike Gerwitz * * This file is part of ease.js. * * ease.js is free software: you can redistribute it and/or modify it under the * terms of the GNU Lesser General Public License as published by the Free * Software Foundation, either version 3 of the License, or (at your option) * any later version. * * This program is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License * for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program. If not, see . * * @author Mike Gerwitz */ /** * Supertype */ var MemberBuilder = require( __dirname + '/MemberBuilder' ); /** * Responsible for building class members */ module.exports = exports = function FallbackMemberBuilder( wrap_method, wrap_override ) { // permit omitting 'new' keyword if ( !( this instanceof module.exports ) ) { return new module.exports( wrap_method, wrap_override ); } // invoke parent constructor module.exports.prototype.constructor.call( this, wrap_method, wrap_override ); }; // inherit from MemberBuilder module.exports.prototype = new MemberBuilder(); module.exports.constructor = module.exports; /** * Getters/setters are unsupported in a pre-ES5 environment * * Simply throw an exception, as it clearly represents that the developer did * not account for the possibility that their software may have been executed in * a pre-ES5 environment. */ exports.prototype.buildGetterSetter = function() { throw Error( 'Getters/setters are unsupported in this environment' ); }; } )( module['FallbackMemberBuilder'] = {}, '.' ); /** interface **/ ( function( module, __dirname ) { var exports = module.exports = {}; /** * Contains interface module * * Copyright (C) 2010,2011 Mike Gerwitz * * This file is part of ease.js. * * ease.js is free software: you can redistribute it and/or modify it under the * terms of the GNU Lesser General Public License as published by the Free * Software Foundation, either version 3 of the License, or (at your option) * any later version. * * This program is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License * for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program. If not, see . * * @author Mike Gerwitz */ var util = require( __dirname + '/util' ), MethodWrapperFactory = require( __dirname + '/MethodWrapperFactory' ), wrappers = require( __dirname + '/MethodWrappers' ).standard, member_builder = require( __dirname + '/MemberBuilder' )( MethodWrapperFactory( wrappers.wrapNew ), MethodWrapperFactory( wrappers.wrapOverride ), MethodWrapperFactory( wrappers.wrapProxy ), require( __dirname + '/MemberBuilderValidator' )() ), Class = require( __dirname + '/class' ) ; /** * This module may be invoked in order to provide a more natural looking * interface definition * * Only new interfaces may be created using this method. They cannot be * extended. To extend an existing interface, call its extend() method, or use * the extend() method of this module. * * @param {string|Object} namedef optional name or definition * @param {Object=} def interface definition if first arg is name * * @return {Function|Object} new interface or staging object */ module.exports = function( namedef, def ) { var type = ( typeof namedef ), result = null ; switch ( type ) { // anonymous interface case 'object': result = createAnonymousInterface.apply( null, arguments ); break; // named class case 'string': result = createNamedInterface.apply( null, arguments ); break; default: // we don't know what to do! throw TypeError( "Expecting anonymous interface definition or named " + "interface definition" ); } return result; }; /** * Creates an interface * * @return {Function} extended interface */ module.exports.extend = function() { return extend.apply( this, arguments ); }; /** * Determines whether the provided object is an interface created through * ease.js * * @param {Object} obj object to test * * @return {boolean} true if interface (created through ease.js), otherwise * false */ module.exports.isInterface = function( obj ) { obj = obj || {}; return ( obj.prototype instanceof Interface ) ? true : false ; }; /** * Default interface implementation * * @return {undefined} */ function Interface() {} /** * Creates a new anonymous Interface from the given interface definition * * @param {Object} def interface definition * * @return {Function} new anonymous interface */ function createAnonymousInterface( def ) { // ensure we have the proper number of arguments (if they passed in // too many, it may signify that they don't know what they're doing, // and likely they're not getting the result they're looking for) if ( arguments.length > 1 ) { throw Error( "Expecting one argument for Interface definition; " + arguments.length + " given." ); } return extend( def ); } /** * Creates a new named interface from the given interface definition * * @param {string} name interface name * @param {Object} def interface definition * * @return {Function} new named interface */ function createNamedInterface( name, def ) { // if too many arguments were provided, it's likely that they're // expecting some result that they're not going to get if ( arguments.length > 2 ) { throw Error( "Expecting two arguments for definition of named Interface '" + name + "'; " + arguments.length + " given." ); } // the definition must be an object if ( typeof def !== 'object' ) { throw TypeError( "Unexpected value for definition of named Interface '" + name + "'; object expected" ); } // add the name to the definition def.__name = name; return extend( def ); } var extend = ( function( extending ) { return function extend() { // ensure we'll be permitted to instantiate interfaces for the base extending = true; var args = Array.prototype.slice.call( arguments ), props = args.pop() || {}, base = args.pop() || Interface, prototype = new base(), iname = '', members = member_builder.initMembers( prototype, prototype, prototype ) ; // grab the name, if one was provided if ( iname = props.__name ) { // we no longer need it delete props.__name; } // sanity check inheritCheck( prototype ); var new_interface = createInterface( iname ); try { util.propParse( props, { assumeAbstract: true, property: function() { // should never get to this point because of assumeAbstract throw TypeError( 'Unexpected internal error' ); }, getset: function() { // should never get to this point because of assumeAbstract throw TypeError( 'Unexpected internal error' ); }, method: function( name, value, is_abstract, keywords ) { // all members must be public if ( keywords[ 'protected' ] || keywords[ 'private' ] ) { throw TypeError( iname + " member " + name + " must be public" ); } member_builder.buildMethod( members, null, name, value, keywords ); } } ); } catch ( e ) { // alter the message to include our name e.message = "Failed to define interface " + ( ( iname ) ? iname : '(anonymous)' ) + ": " + e.message ; // re-throw throw e; } attachExtend( new_interface ); attachStringMethod( new_interface, iname ); new_interface.prototype = prototype; new_interface.constructor = new_interface; // freeze the interface (preventing additions), if supported util.freeze( new_interface ); // we're done; let's not allow interfaces to be instantiated anymore extending = false; return new_interface; }; /** * Creates a new interface constructor function * * @param {string=} iname interface name * * @return {function()} */ function createInterface( iname ) { return function() { // allows us to extend the interface without throwing an exception // (since the prototype requires an instance) if ( !extending ) { // only called if someone tries to create a new instance of an // interface throw Error( "Interface" + ( ( iname ) ? ( iname + ' ' ) : '' ) + " cannot be instantiated" ); } }; } } )( false ); /** * Assures that the parent object is a valid object to inherit from * * This method allows inheriting from any object (note that it will likely cause * errors if not an interface), but will place restrictions on objects like * Classes that do not make sense to inherit from. This will provide a more * friendly error, with suggestions on how to resolve the issue, rather than a * cryptic error resulting from inheritance problems. * * This method will throw an exception if there is a violation. * * @param {Object} prototype prototype to check for inheritance flaws * * @return {undefined} */ function inheritCheck( prototype ) { // if we're inheriting from another interface, then we're good if ( !( prototype instanceof Interface ) ) { throw new TypeError( "Interfaces may only extend other interfaces" ); } } /** * Attaches extend method to the given function (interface) * * @param {Function} func function (interface) to attach method to * * @return {undefined} */ function attachExtend( func ) { /** * Shorthand for extending interfaces * * This method can be invoked on the object, rather than having to call * Interface.extend( this ). * * @param {Object} props properties to add to extended interface * * @return {Object} extended interface */ util.defineSecureProp( func, 'extend', function( props ) { return extend( this, props ); }); } /** * Provides more sane/useful output when interface is converted to a string * * @param {Object} func interface * @param {string=} iname interface name * * @return {undefined} */ function attachStringMethod( func, iname ) { func.toString = ( iname ) ? function() { return '[object Interface <' + iname + '>]'; } : function() { return '[object Interface]'; } ; } } )( module['interface'] = {}, '.' ); /** class_abstract **/ ( function( module, __dirname ) { var exports = module.exports = {}; /** * Wrapper permitting the definition of abstract classes * * This doesn't actually introduce any new functionality. Rather, it sets a flag * to allow abstract methods within a class, forcing users to clearly state * that a class is abstract. * * Copyright (C) 2010,2011 Mike Gerwitz * * This file is part of ease.js. * * ease.js is free software: you can redistribute it and/or modify it under the * terms of the GNU Lesser General Public License as published by the Free * Software Foundation, either version 3 of the License, or (at your option) * any later version. * * This program is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License * for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program. If not, see . * * @author Mike Gerwitz */ var Class = require( __dirname + '/class' ); /** * Creates an abstract class * * @return {Function} abstract class */ module.exports = exports = function() { markAbstract( arguments ); // forward everything to Class var result = Class.apply( this, arguments ); // if we're using the temporary object, then override its methods to permit // abstract classes if ( !Class.isClass( result ) ) { abstractOverride( result ); } return result; }; /** * Creates an abstract class from a class extend operation * * @return {Function} abstract class */ exports.extend = function() { markAbstract( arguments ); return Class.extend.apply( this, arguments ); }; /** * Creates an abstract class implementing the given members * * Simply wraps the class module's implement() method. * * @return {Object} abstract class */ exports.implement = function() { var impl = Class.implement.apply( this, arguments ); abstractOverride( impl ); return impl; }; /** * Causes a definition to be flagged as abstract * * This function assumes the last argument to be the definition, which is the * common case, and modifies the object referenced by that argument. * * @param {Arguments} args arguments to parse * * @return {undefined} */ function markAbstract( args ) { // the last argument _should_ be the definition var dfn = args[ args.length - 1 ]; if ( typeof dfn === 'object' ) { // mark as abstract dfn.___$$abstract$$ = true; } } /** * Overrides object members to permit abstract classes * * @param {Object} obj object to override * * @return {Object} obj */ function abstractOverride( obj ) { var extend = obj.extend, impl = obj.implement; // wrap and apply the abstract flag, only if the method is defined (it may // not be under all circumstances, e.g. after an implement()) impl && ( obj.implement = function() { return abstractOverride( impl.apply( this, arguments ) ); } ); // wrap extend, applying the abstract flag obj.extend = function() { markAbstract( arguments ); return extend.apply( this, arguments ); }; return obj; } } )( module['class_abstract'] = {}, '.' ); /** version **/ ( function( module, __dirname ) { var exports = module.exports = {}; /** * Provides version information * * Copyright (C) 2010,2011 Mike Gerwitz * * This file is part of ease.js. * * ease.js is free software: you can redistribute it and/or modify it under the * terms of the GNU Lesser General Public License as published by the Free * Software Foundation, either version 3 of the License, or (at your option) * any later version. * * This program is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License * for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program. If not, see . * * @author Mike Gerwitz */ /*** DO NOT MODIFY; generated by verset ***/ var major = 0, minor = 2, rev = 0, suffix = 'dev', version = [ major, minor, rev, suffix ]; version.major = major; version.minor = minor; version.rev = rev; version.suffix = suffix; version.toString = function() { return this.join( '.' ) .replace( /\.([^.]+)$/, '-$1' ) .replace( /-$/, '' ); }; module.exports = version; } )( module['version'] = {}, '.' ); // the following should match the exports of /index.js ns_exports.Class = module['class'].exports; ns_exports.AbstractClass = module['class_abstract'].exports; ns_exports.FinalClass = module['class_final'].exports; ns_exports.Interface = module['interface'].exports; ns_exports.version = module['version'].exports; } )( easejs );