Nickolay - Test.Run-0.08

Documentation | Source
;
;Joose = function () { throw "Modules may not be instantiated." }

Joose.top           = this

Joose.VERSION       = 3.01
Joose.AUTHORITY     = 'jsan:NPLATONOV'


// Static helpers for Arrays
Joose.A = {

    each : function (array, func, scope) {
        scope = scope || this
        
        for (var i = 0, len = array.length; i < len; i++) 
            if (func.call(scope, array[i], i) === false) return false
    },
    
    
    exists : function (array, value) {
        for (var i = 0, len = array.length; i < len; i++) if (array[i] == value) return true
            
        return false
    },
    
    
    map : function (array, func, scope) {
        scope = scope || this
        
        var res = []
        
        for (var i = 0, len = array.length; i < len; i++) 
            res.push( func.call(scope, array[i], i) )
            
        return res
    },
    

    grep : function (array, func) {
        var a = []
        
        Joose.A.each(array, function (t) {
            if (func(t)) a.push(t)
        })
        
        return a
    },
    
    
    remove : function (array, removeEle) {
        var a = []
        
        Joose.A.each(array, function (t) {
            if (t !== removeEle) a.push(t)
        })
        
        return a
    }
    
}

// Static helpers for Strings
Joose.S = {
    
    saneSplit : function (str, delimeter) {
        var res = (str || '').split(delimeter)
        
        if (res.length == 1 && !res[0]) res.shift()
        
        return res
    },
    

    uppercaseFirst : function (string) { 
        return string.substr(0, 1).toUpperCase() + string.substr(1, string.length - 1)
    }
    
}


// Static helpers for objects
Joose.O = {

    each : function (object, func, scope) {
        scope = scope || this
        
        for (var i in object) 
            if (func.call(scope, object[i], i) === false) return false
        
        if (Joose.is_IE) 
            return Joose.A.each([ 'toString', 'constructor', 'hasOwnProperty' ], function (el) {
                
                if (object.hasOwnProperty(el)) return func.call(scope, object[el], el)
            })
    },
    
    
    eachOwn : function (object, func, scope) {
        scope = scope || this
        
        return Joose.O.each(object, function (value, name) {
            if (object.hasOwnProperty(name)) return func.call(scope, value, name)
        }, scope)
    },
    
    
    copy : function (source, target) {
        target = target || {}
        
        Joose.O.each(source, function (value, name) { target[name] = value })
        
        return target
    },
    
    
    copyOwn : function (source, target) {
        target = target || {}
        
        Joose.O.eachOwn(source, function (value, name) { target[name] = value })
        
        return target
    },
    
    
    getMutableCopy : function (object) {
        var f = function () {}
        
        f.prototype = object
        
        return new f()
    },
    
    
    extend : function (target, source) {
        return Joose.O.copy(source, target)
    },
    
    
    isEmpty : function (object) {
        for (var i in object) if (object.hasOwnProperty(i)) return false
        
        return true
    },
    
    
    isInstance: function (obj) {
        return obj && obj.meta && obj.constructor == obj.meta.c
    },
    
    
    wantArray : function (obj) {
        if (obj instanceof Array) return obj
        
        return [ obj ]
    },
    
    
    isFunction : function (obj) {
        return typeof obj == 'function' && obj.constructor != RegExp
    }
}


//initializers

Joose.I = {
    Array       : function () { return [] },
    Object      : function () { return {} },
    Function    : function () { return function () {} },
    Now         : function () { return new Date() }
}

Joose.is_IE         = '\v' == 'v'
Joose.is_NodeJS     = Boolean(typeof process != 'undefined' && process.pid);
Joose.Proto = function () { throw "Modules may not be instantiated." }

Joose.Proto.Empty = function () { throw "Joose.Proto.Empty can't be instantiated" }
    
Joose.Proto.Empty.meta = {};
(function () {

    Joose.Proto.Object = function () {
        throw "Joose.Proto.Object can't be instantiated"
    }
    
    
    var SUPER = function () {
        var self = SUPER.caller
        
        if (self == SUPERARG) self = self.caller
        
        if (!self.SUPER) throw "Invalid call to SUPER"
        
        return self.SUPER[self.methodName].apply(this, arguments)
    }
    
    
    var SUPERARG = function () {
        return this.SUPER.apply(this, arguments[0])
    }
    
    
    
    Joose.Proto.Object.prototype = {
        
        SUPERARG : SUPERARG,
        SUPER : SUPER,
        
        INNER : function () {
            throw "Invalid call to INNER"
        },                
        
        
        BUILD : function (config) {
            return arguments.length == 1 && typeof config == 'object' && config || {}
        },
        
        
        initialize: function () {
        },
        
        
        toString: function () {
            return "a " + this.meta.name
        }
        
    }
        
    Joose.Proto.Object.meta = {
        constructor     : Joose.Proto.Object,
        
        methods         : Joose.O.copy(Joose.Proto.Object.prototype),
        attributes      : {}
    }
    
    Joose.Proto.Object.prototype.meta = Joose.Proto.Object.meta

})();
;(function () {

    Joose.Proto.Class = function () {
        return this.initialize(this.BUILD.apply(this, arguments)) || this
    }
    
    var bootstrap = {
        
        VERSION             : null,
        AUTHORITY           : null,
        
        constructor         : Joose.Proto.Class,
        superClass          : null,
        
        name                : null,
        
        attributes          : null,
        methods             : null,
        
        meta                : null,
        c                   : null,
        
        defaultSuperClass   : Joose.Proto.Object,
        
        
        BUILD : function (name, extend) {
            this.name = name
            
            return { __extend__ : extend || {} }
        },
        
        
        initialize: function (props) {
            var extend      = props.__extend__
            
            this.VERSION    = extend.VERSION
            this.AUTHORITY  = extend.AUTHORITY
            
            delete extend.VERSION
            delete extend.AUTHORITY
            
            this.c = this.extractConstructor(extend)
            
            this.adaptConstructor(this.c)
            
            if (extend.constructorOnly) {
                delete extend.constructorOnly
                return
            }
            
            this.construct(extend)
        },
        
        
        construct : function (extend) {
            if (!this.prepareProps(extend)) return
            
            var superClass = this.superClass = this.extractSuperClass(extend)
            this.processSuperClass(superClass)
            
            this.adaptPrototype(this.c.prototype)
            
            this.finalize(extend)
        },
        
        
        finalize : function (extend) {
            this.processStem(extend)
            
            this.extend(extend)
        },
        
        
        //if the extension returns false from this method it should re-enter 'construct'
        prepareProps : function (extend) {
            return true
        },
        
        
        extractConstructor : function (extend) {
            var res = extend.hasOwnProperty('constructor') ? extend.constructor : this.defaultConstructor()
            
            delete extend.constructor
            
            return res
        },
        
        
        extractSuperClass : function (extend) {
            var res = extend.isa || this.defaultSuperClass
            
            delete extend.isa
            
            return res
        },
        
        
        processStem : function () {
            // '|| {}' for non-Joose superclasses
            var superMeta       = this.superClass.meta || {}
            
            this.methods        = Joose.O.getMutableCopy(superMeta.methods || {})
            this.attributes     = Joose.O.getMutableCopy(superMeta.attributes || {})
        },
        
        
        initInstance : function (instance, props) {
            Joose.O.copyOwn(props, instance)
        },
        
        
        defaultConstructor: function () {
            return function () {
                var args = this.BUILD.apply(this, arguments)
                
                var thisMeta    = this.meta
                
                thisMeta.initInstance(this, args)
                
                return thisMeta.hasMethod('initialize') && this.initialize(args) || this
            }
        },
        
        
        processSuperClass: function (superClass) {
            this.c.prototype    = Joose.O.getMutableCopy(superClass.prototype)
            this.c.superClass   = superClass.prototype
        },
        
        
        adaptConstructor: function (c) {
            c.meta = this
            
            if (!c.hasOwnProperty('toString')) c.toString = function () { return this.meta.name }
        },
    
        
        adaptPrototype: function (proto) {
            //this will fix weird semantic of native "constructor" property to more intuitive (idea borrowed from Ext)
            proto.constructor   = this.c
            proto.meta          = this
        },
        
        
        addMethod: function (name, func) {
            func.SUPER = this.superClass.prototype
            
            //chrome don't allow to redefine the "name" property
            func.methodName = name
            
            this.methods[name] = func
            this.c.prototype[name] = func
        },
        
        
        addAttribute: function (name, init) {
            this.attributes[name] = init
            this.c.prototype[name] = init
        },
        
        
        removeMethod : function (name) {
            delete this.methods[name]
            delete this.c.prototype[name]
        },
    
        
        removeAttribute: function (name) {
            delete this.attributes[name]
            delete this.c.prototype[name]
        },
        
        
        hasMethod: function (name) { 
            return Boolean(this.methods[name])
        },
        
        
        hasAttribute: function (name) { 
            return this.attributes[name] !== undefined
        },
        
    
        hasOwnMethod: function (name) { 
            return this.hasMethod(name) && this.methods.hasOwnProperty(name)
        },
        
        
        hasOwnAttribute: function (name) { 
            return this.hasAttribute(name) && this.attributes.hasOwnProperty(name)
        },
        
        
        extend : function (props) {
            Joose.O.eachOwn(props, function (value, name) {
                if (name != 'meta' && name != 'constructor') 
                    if (Joose.O.isFunction(value) && !value.meta) 
                        this.addMethod(name, value) 
                    else 
                        this.addAttribute(name, value)
            }, this)
        },
        
        
        subClassOf : function (classObject, extend) {
            return this.subClass(extend, null, classObject)
        },
    
    
        subClass : function (extend, name, classObject) {
            extend      = extend        || {}
            extend.isa  = classObject   || this.c
            
            return new this.constructor(name, extend).c
        },
        
        
        instantiate : function () {
            var f = function () {}
            
            f.prototype = this.c.prototype
            
            var obj = new f()
            
            return this.c.apply(obj, arguments) || obj
        }
    }
    
    //micro bootstraping
    
    Joose.Proto.Class.prototype = Joose.O.getMutableCopy(Joose.Proto.Object.prototype)
    
    Joose.O.extend(Joose.Proto.Class.prototype, bootstrap)
    
    Joose.Proto.Class.prototype.meta = new Joose.Proto.Class('Joose.Proto.Class', bootstrap)
    
    
    
    Joose.Proto.Class.meta.addMethod('isa', function (someClass) {
        var f = function () {}
        
        f.prototype = this.c.prototype
        
        return new f() instanceof someClass
    })
})();
Joose.Managed = function () { throw "Modules may not be instantiated." }

Joose.Managed.Property = new Joose.Proto.Class('Joose.Managed.Property', {
    
    name            : null,
    
    init            : null,
    value           : null,
    
    definedIn       : null,
    
    
    initialize : function (props) {
        Joose.Managed.Property.superClass.initialize.call(this, props)
        
        this.computeValue()
    },
    
    
    computeValue : function () {
        this.value = this.init
    },    
    
    
    //targetClass is still open at this stage
    preApply : function (targetClass) {
    },
    

    //targetClass is already open at this stage
    postUnApply : function (targetClass) {
    },
    
    
    apply : function (target) {
        target[this.name] = this.value
    },
    
    
    isAppliedTo : function (target) {
        return target[this.name] == this.value
    },
    
    
    unapply : function (from) {
        if (!this.isAppliedTo(from)) throw "Unapply of property [" + this.name + "] from [" + from + "] failed"
        
        delete from[this.name]
    },
    
    
    cloneProps : function () {
        return {
            name        : this.name, 
            init        : this.init,
            definedIn   : this.definedIn
        }
    },

    
    clone : function (name) {
        var props = this.cloneProps()
        
        props.name = name || props.name
        
        return new this.constructor(props)
    }
    
    
}).c;
Joose.Managed.Property.ConflictMarker = new Joose.Proto.Class('Joose.Managed.Property.ConflictMarker', {
    
    isa : Joose.Managed.Property,

    apply : function (target) {
        throw "Attempt to apply ConflictMarker [" + this.name + "] to [" + target + "]"
    }
    
}).c;
Joose.Managed.Property.Requirement = new Joose.Proto.Class('Joose.Managed.Property.Requirement', {
    
    isa : Joose.Managed.Property,

    
    apply : function (target) {
        if (!target.meta.hasMethod(this.name)) throw "Requirement [" + this.name + "], defined in [" + this.definedIn.definedIn.name + "] is not satisfied for class [" + target + "]"
    },
    
    
    unapply : function (from) {
    }
    
}).c;
Joose.Managed.Property.Attribute = new Joose.Proto.Class('Joose.Managed.Property.Attribute', {
    
    isa : Joose.Managed.Property,
    
    slot                : null,
    
    
    initialize : function () {
        Joose.Managed.Property.Attribute.superClass.initialize.apply(this, arguments)
        
        this.slot = this.name
    },
    
    
    apply : function (target) {
        target.prototype[this.slot] = this.value
    },
    
    
    isAppliedTo : function (target) {
        return target.prototype[this.slot] == this.value
    },
    
    
    unapply : function (from) {
        if (!this.isAppliedTo(from)) throw "Unapply of property [" + this.name + "] from [" + from + "] failed"
        
        delete from.prototype[this.slot]
    },
    
    
    getRawValueFrom : function (instance) {
        return instance[this.slot]
    },
    
    
    setRawValueTo : function (instance, value) {
        instance[this.slot] = value
    }
    
}).c;
Joose.Managed.Property.MethodModifier = new Joose.Proto.Class('Joose.Managed.Property.MethodModifier', {
    
    isa : Joose.Managed.Property,

    
    prepareWrapper : function () {
        throw "Abstract method [prepareWrapper] of " + this + " was called"
    },
    
    
    apply : function (target) {
        var name            = this.name
        var targetProto     = target.prototype
        var isOwn           = targetProto.hasOwnProperty(name)
        var original        = targetProto[name]
        var superProto      = target.meta.superClass.prototype
        
        
        var originalCall = isOwn ? original : function () { 
            return superProto[name].apply(this, arguments) 
        }
        
        var methodWrapper = this.prepareWrapper({
            name            : name,
            modifier        : this.value, 
            
            isOwn           : isOwn,
            originalCall    : originalCall, 
            
            superProto      : superProto,
            
            target          : target
        })
        
        if (isOwn) methodWrapper._original = original
        methodWrapper._contain = this.value
        
        targetProto[name] = methodWrapper
    },
    
    
    isAppliedTo : function (target) {
        var targetCont = target.prototype[this.name]
        
        return targetCont && targetCont._contain == this.value
    },
    
    
    unapply : function (from) {
        var name = this.name
        var fromProto = from.prototype
        var original = fromProto[name]._original
        
        if (!this.isAppliedTo(from)) throw "Unapply of method [" + name + "] from class [" + from + "] failed"
        
        //if modifier was applied to own method - restore it
        if (original) 
            fromProto[name] = original
        //otherwise - just delete it, to reveal the inherited method 
        else
            delete fromProto[name]
    }
    
}).c;
Joose.Managed.Property.MethodModifier.Override = new Joose.Proto.Class('Joose.Managed.Property.MethodModifier.Override', {
    
    isa : Joose.Managed.Property.MethodModifier,

    
    prepareWrapper : function (params) {
        
        var modifier        = params.modifier
        var originalCall    = params.originalCall
        var superProto      = params.superProto
        var superMetaConst  = superProto.meta.constructor
        
        //call to Joose.Proto level, require some additional processing
        var isCallToProto = (superMetaConst == Joose.Proto.Class || superMetaConst == Joose.Proto.Object) && !(params.isOwn && originalCall.IS_OVERRIDE) 
        
        var original = originalCall
        
        if (isCallToProto) original = function () {
            var beforeSUPER = this.SUPER
            
            this.SUPER  = superProto.SUPER
            
            var res = originalCall.apply(this, arguments)
            
            this.SUPER = beforeSUPER
            
            return res
        }

        var override = function () {
            
            var beforeSUPER = this.SUPER
            
            this.SUPER  = original
            
            var res = modifier.apply(this, arguments)
            
            this.SUPER = beforeSUPER
            
            return res
        }
        
        override.IS_OVERRIDE = true
        
        return override
    }
    
    
}).c;
Joose.Managed.Property.MethodModifier.Put = new Joose.Proto.Class('Joose.Managed.Property.MethodModifier.Put', {
    
    isa : Joose.Managed.Property.MethodModifier.Override,


    prepareWrapper : function (params) {
        
        if (params.isOwn) throw "Method [" + params.name + "] is applying over something [" + params.originalCall + "] in class [" + params.target + "]"
        
        return Joose.Managed.Property.MethodModifier.Put.superClass.prepareWrapper.call(this, params)
    }
    
    
}).c;
Joose.Managed.Property.MethodModifier.After = new Joose.Proto.Class('Joose.Managed.Property.MethodModifier.After', {
    
    isa : Joose.Managed.Property.MethodModifier,

    
    prepareWrapper : function (params) {
        
        var modifier        = params.modifier
        var originalCall    = params.originalCall
        
        return function () {
            var res = originalCall.apply(this, arguments)
            modifier.apply(this, arguments)
            return res
        }
    }    

    
}).c;
Joose.Managed.Property.MethodModifier.Before = new Joose.Proto.Class('Joose.Managed.Property.MethodModifier.Before', {
    
    isa : Joose.Managed.Property.MethodModifier,

    
    prepareWrapper : function (params) {
        
        var modifier        = params.modifier
        var originalCall    = params.originalCall
        
        return function () {
            modifier.apply(this, arguments)
            return originalCall.apply(this, arguments)
        }
    }
    
}).c;
Joose.Managed.Property.MethodModifier.Around = new Joose.Proto.Class('Joose.Managed.Property.MethodModifier.Around', {
    
    isa : Joose.Managed.Property.MethodModifier,

    prepareWrapper : function (params) {
        
        var modifier        = params.modifier
        var originalCall    = params.originalCall
        
        var me
        
        var bound = function () {
            return originalCall.apply(me, arguments)
        }
            
        return function () {
            me = this
            
            var boundArr = [ bound ]
            boundArr.push.apply(boundArr, arguments)
            
            return modifier.apply(this, boundArr)
        }
    }
    
}).c;
Joose.Managed.Property.MethodModifier.Augment = new Joose.Proto.Class('Joose.Managed.Property.MethodModifier.Augment', {
    
    isa : Joose.Managed.Property.MethodModifier,

    
    prepareWrapper : function (params) {
        
        var AUGMENT = function () {
            
            //populate callstack to the most deep non-augment method
            var callstack = []
            
            var self = AUGMENT
            
            do {
                callstack.push(self.IS_AUGMENT ? self._contain : self)
                
                self = self.IS_AUGMENT && (self._original || self.SUPER[self.methodName])
            } while (self)
            
            
            //save previous INNER
            var beforeINNER = this.INNER
            
            //create new INNER
            this.INNER = function () {
                var innerCall = callstack.pop()
                
                return innerCall ? innerCall.apply(this, arguments) : undefined
            }
            
            //augment modifier results in hypotetical INNER call of the same method in subclass 
            var res = this.INNER.apply(this, arguments)
            
            //restore previous INNER chain
            this.INNER = beforeINNER
            
            return res
        }
        
        AUGMENT.methodName  = params.name
        AUGMENT.SUPER       = params.superProto
        AUGMENT.IS_AUGMENT  = true
        
        return AUGMENT
    }
    
}).c;
Joose.Managed.PropertySet = new Joose.Proto.Class('Joose.Managed.PropertySet', {
    
    isa                       : Joose.Managed.Property,

    properties                : null,
    
    propertyMetaClass         : Joose.Managed.Property,
    
    
    initialize : function (props) {
        Joose.Managed.PropertySet.superClass.initialize.call(this, props)
        
        //XXX this guards the meta roles :)
        this.properties = props.properties || {}
    },
    
    
    addProperty : function (name, props) {
        var metaClass = props.meta || this.propertyMetaClass
        delete props.meta
        
        props.definedIn     = this
        props.name          = name
        
        return this.properties[name] = new metaClass(props)
    },
    
    
    addPropertyObject : function (object) {
        return this.properties[object.name] = object
    },
    
    
    removeProperty : function (name) {
        var prop = this.properties[name]
        
        delete this.properties[name]
        
        return prop
    },
    
    
    haveProperty : function (name) {
        return this.properties[name] != null
    },
    

    haveOwnProperty : function (name) {
        return this.haveProperty(name) && this.properties.hasOwnProperty(name)
    },
    
    
    getProperty : function (name) {
        return this.properties[name]
    },
    
    
    //includes inherited properties (probably you wants 'eachOwn', which process only "own" (including consumed from Roles) properties) 
    each : function (func, scope) {
        Joose.O.each(this.properties, func, scope || this)
    },
    
    
    eachOwn : function (func, scope) {
        Joose.O.eachOwn(this.properties, func, scope || this)
    },
    
    
    //synonym for each
    eachAll : function (func, scope) {
        this.each(func, scope)
    },
    
    
    cloneProps : function () {
        var props = Joose.Managed.PropertySet.superClass.cloneProps.call(this)
        
        props.propertyMetaClass     = this.propertyMetaClass
        
        return props
    },
    
    
    clone : function (name) {
        var clone = this.cleanClone(name)
        
        clone.properties = Joose.O.copyOwn(this.properties)
        
        return clone
    },
    
    
    cleanClone : function (name) {
        var props = this.cloneProps()
        
        props.name = name || props.name
        
        return new this.constructor(props)
    },
    
    
    alias : function (what) {
        var props = this.properties
        
        Joose.O.each(what, function (aliasName, originalName) {
            var original = props[originalName]
            
            if (original) this.addPropertyObject(original.clone(aliasName))
        }, this)
    },
    
    
    exclude : function (what) {
        var props = this.properties
        
        Joose.A.each(what, function (name) {
            delete props[name]
        })
    },
    
    
    beforeConsumedBy : function () {
    },
    
    
    flattenTo : function (target) {
        var targetProps = target.properties
        
        this.eachOwn(function (property, name) {
            var targetProperty = targetProps[name]
            
            if (targetProperty instanceof Joose.Managed.Property.ConflictMarker) return
            
            if (targetProperty == null) {
                target.addPropertyObject(property)
                return
            }
            
            if (targetProperty == property) return
            
            target.removeProperty(name)
            target.addProperty(name, {
                meta : Joose.Managed.Property.ConflictMarker
            })
        }, this)
    },
    
    
    composeTo : function (target) {
        this.eachOwn(function (property, name) {
            if (!target.haveOwnProperty(name)) target.addPropertyObject(property)
        })
    },
    
    
    composeFrom : function () {
        if (!arguments.length) return
        
        var flattening = this.cleanClone()
        
        Joose.A.each(arguments, function (arg) {
            var isDescriptor    = !(arg instanceof Joose.Managed.PropertySet)
            var propSet         = isDescriptor ? arg.propertySet : arg
            
            propSet.beforeConsumedBy(this, flattening)
            
            if (isDescriptor) {
                if (arg.alias || arg.exclude)   propSet = propSet.clone()
                if (arg.alias)                  propSet.alias(arg.alias)
                if (arg.exclude)                propSet.exclude(arg.exclude)
            }
            
            propSet.flattenTo(flattening)
        }, this)
        
        flattening.composeTo(this)
    },
    
    
    preApply : function (target) {
        this.eachOwn(function (property) {
            property.preApply(target)
        })
    },
    
    
    apply : function (target) {
        this.eachOwn(function (property) {
            property.apply(target)
        })
    },
    
    
    unapply : function (from) {
        this.eachOwn(function (property) {
            property.unapply(from)
        })
    },
    
    
    postUnApply : function (target) {
        this.eachOwn(function (property) {
            property.postUnApply(target)
        })
    }
    
}).c
;
(function () {
    
    var __ID__ = 1
    

    Joose.Managed.PropertySet.Mutable = new Joose.Proto.Class('Joose.Managed.PropertySet.Mutable', {
        
        isa                 : Joose.Managed.PropertySet,
    
        ID                  : null,
        
        derivatives         : null,
        
        opened              : null,
        
        composedFrom        : null,
        
        
        initialize : function (props) {
            Joose.Managed.PropertySet.Mutable.superClass.initialize.call(this, props)
            
            //initially opened
            this.opened             = 1
            this.derivatives        = {}
            this.ID                 = __ID__++
            this.composedFrom       = []
        },
        
        
        addComposeInfo : function () {
            this.ensureOpen()
            
            Joose.A.each(arguments, function (arg) {
                this.composedFrom.push(arg)
                
                var propSet = arg instanceof Joose.Managed.PropertySet ? arg : arg.propertySet
                    
                propSet.derivatives[this.ID] = this
            }, this)
        },
        
        
        removeComposeInfo : function () {
            this.ensureOpen()
            
            Joose.A.each(arguments, function (arg) {
                
                var i = 0
                
                while (i < this.composedFrom.length) {
                    var propSet = this.composedFrom[i]
                    propSet = propSet instanceof Joose.Managed.PropertySet ? propSet : propSet.propertySet
                    
                    if (arg == propSet) {
                        delete propSet.derivatives[this.ID]
                        this.composedFrom.splice(i, 1)
                    } else i++
                }
                
            }, this)
        },
        
        
        ensureOpen : function () {
            if (!this.opened) throw "Mutation of closed property set: [" + this.name + "]"
        },
        
        
        addProperty : function (name, props) {
            this.ensureOpen()
            
            return Joose.Managed.PropertySet.Mutable.superClass.addProperty.call(this, name, props)
        },
        
    
        addPropertyObject : function (object) {
            this.ensureOpen()
            
            return Joose.Managed.PropertySet.Mutable.superClass.addPropertyObject.call(this, object)
        },
        
        
        removeProperty : function (name) {
            this.ensureOpen()
            
            return Joose.Managed.PropertySet.Mutable.superClass.removeProperty.call(this, name)
        },
        
        
        composeFrom : function () {
            this.ensureOpen()
            
            return Joose.Managed.PropertySet.Mutable.superClass.composeFrom.apply(this, this.composedFrom)
        },
        
        
        open : function () {
            this.opened++
            
            if (this.opened == 1) {
            
                Joose.O.each(this.derivatives, function (propSet) {
                    propSet.open()
                })
                
                this.deCompose()
            }
        },
        
        
        close : function () {
            if (!this.opened) throw "Unmatched 'close' operation on property set: [" + this.name + "]"
            
            if (this.opened == 1) {
                this.reCompose()
                
                Joose.O.each(this.derivatives, function (propSet) {
                    propSet.close()
                })
            }
            this.opened--
        },
        
        
        reCompose : function () {
            this.composeFrom()
        },
        
        
        deCompose : function () {
            this.eachOwn(function (property, name) {
                if (property.definedIn != this) this.removeProperty(name)
            }, this)
        }
        
    }).c
    
    
})()

;
Joose.Managed.StemElement = function () { throw "Modules may not be instantiated." }

Joose.Managed.StemElement.Attributes = new Joose.Proto.Class('Joose.Managed.StemElement.Attributes', {
    
    isa                     : Joose.Managed.PropertySet.Mutable,
    
    propertyMetaClass       : Joose.Managed.Property.Attribute
    
}).c
;
Joose.Managed.StemElement.Methods = new Joose.Proto.Class('Joose.Managed.StemElement.Methods', {
    
    isa : Joose.Managed.PropertySet.Mutable,
    
    propertyMetaClass : Joose.Managed.Property.MethodModifier.Put,

    
    preApply : function () {
    },
    
    
    postUnApply : function () {
    }
    
}).c;
Joose.Managed.StemElement.Requirements = new Joose.Proto.Class('Joose.Managed.StemElement.Requirements', {

    isa                     : Joose.Managed.PropertySet.Mutable,
    
    propertyMetaClass       : Joose.Managed.Property.Requirement,
    
    
    
    alias : function () {
    },
    
    
    exclude : function () {
    },
    
    
    flattenTo : function (target) {
        this.each(function (property, name) {
            if (!target.haveProperty(name)) target.addPropertyObject(property)
        })
    },
    
    
    composeTo : function (target) {
        this.flattenTo(target)
    },
    
    
    preApply : function () {
    },
    
    
    postUnApply : function () {
    }
    
}).c;
Joose.Managed.StemElement.MethodModifiers = new Joose.Proto.Class('Joose.Managed.StemElement.MethodModifiers', {

    isa                     : Joose.Managed.PropertySet.Mutable,
    
    propertyMetaClass       : null,
    
    
    addProperty : function (name, props) {
        var metaClass = props.meta
        delete props.meta
        
        props.definedIn         = this
        props.name              = name
        var modifier            = new metaClass(props)
        
        if (!this.properties[name]) this.properties[name] = []
        this.properties[name].push(modifier)
        
        return modifier
    },
    

    addPropertyObject : function (object) {
        var name = object.name
        
        if (!this.properties[name]) this.properties[name] = []
        
        this.properties[name].push(object)
        
        return object
    },
    
    
    //remove only the last modifier
    removeProperty : function (name) {
        if (!this.haveProperty(name)) return undefined
        
        var modifier = this.properties[name].pop()
        
        //if all modifiers were removed - clearing the properties
        if (!this.properties[name].length) Joose.Managed.StemElement.MethodModifiers.superClass.removeProperty.call(this, name)
        
        return modifier
    },
    
    
    alias : function () {
    },
    
    
    exclude : function () {
    },
    
    
    flattenTo : function (target) {
        var targetProps = target.properties
        
        this.each(function (modifiersArr, name) {
            var targetModifiersArr = targetProps[name]
            
            if (targetModifiersArr == null) targetModifiersArr = targetProps[name] = []
            
            Joose.A.each(modifiersArr, function (modifier) {
                if (!Joose.A.exists(targetModifiersArr, modifier)) targetModifiersArr.push(modifier)
            })
            
        }, this)
    },
    
    
    composeTo : function (target) {
        this.flattenTo(target)
    },

    
    deCompose : function () {
        this.each(function (modifiersArr, name) {
            var i = 0
            while (i < modifiersArr.length) if (modifiersArr[i].definedIn != this) modifiersArr.splice(i, 1); else i++
            
        }, this)
    },
    
    
    preApply : function (target) {
    },

    
    postUnApply : function (target) {
    },
    
    
    apply : function (target) {
        this.each(function (modifiersArr, name) {
            Joose.A.each(modifiersArr, function (modifier) {
                modifier.apply(target)
            })
        })
    },
    
    
    unapply : function (from) {
        this.each(function (modifiersArr, name) {
            for (var i = modifiersArr.length - 1; i >=0 ; i--) modifiersArr[i].unapply(from)
        })
    }
    
    
    
}).c;
Joose.Managed.PropertySet.Composition = new Joose.Proto.Class('Joose.Managed.PropertySet.Composition', {
    
    isa                         : Joose.Managed.PropertySet.Mutable,
    
    propertyMetaClass           : Joose.Managed.PropertySet.Mutable,
    
    processOrder                : null,

    
    each : function (func, scope) {
        var props = this.properties
        
        Joose.A.each(this.processOrder, function (name) {
            func.call(scope || this, props[name], name)
        }, this)
    },
    
    
    eachR : function (func, scope) {
        var props           = this.properties
        var processOrder    = this.processOrder
        
        for(var i = processOrder.length - 1; i >= 0; i--) 
            func.call(scope || this, props[ processOrder[i] ], processOrder[i])
    },
    
    
    clone : function (name) {
        var clone = this.cleanClone(name)
        
        this.each(function (property) {
            clone.addPropertyObject(property.clone())
        })
        
        return clone
    },
    
    
    alias : function (what) {
        this.each(function (property) {
            property.alias(what)
        })
    },
    
    
    exclude : function (what) {
        this.each(function (property) {
            property.exclude(what)
        })
    },
    
    
    flattenTo : function (target) {
        var targetProps = target.properties
        
        this.each(function (property, name) {
            var subTarget = targetProps[name] || target.addProperty(name, {
                meta : property.constructor
            })
            
            property.flattenTo(subTarget)
        })
    },
    
    
    composeTo : function (target) {
        var targetProps = target.properties
        
        this.each(function (property, name) {
            var subTarget = targetProps[name] || target.addProperty(name, {
                meta : property.constructor
            })
            
            property.composeTo(subTarget)
        })
    },
    
    
    
    deCompose : function () {
        this.eachR(function (property) {
            property.open()
        })
        
        Joose.Managed.PropertySet.Composition.superClass.deCompose.call(this)
    },
    
    
    reCompose : function () {
        Joose.Managed.PropertySet.Composition.superClass.reCompose.call(this)
        
        this.each(function (property) {
            property.close()
        })
    },
    
    
    unapply : function (from) {
        this.eachR(function (property) {
            property.unapply(from)
        })
    }
    
}).c
;
Joose.Managed.Stem = new Joose.Proto.Class('Joose.Managed.Stem', {
    
    isa                  : Joose.Managed.PropertySet.Composition,
    
    targetMeta           : null,
    
    attributesMC         : Joose.Managed.StemElement.Attributes,
    methodsMC            : Joose.Managed.StemElement.Methods,
    requirementsMC       : Joose.Managed.StemElement.Requirements,
    methodsModifiersMC   : Joose.Managed.StemElement.MethodModifiers,
    
    processOrder         : [ 'attributes', 'methods', 'requirements', 'methodsModifiers' ],
    
    
    initialize : function (props) {
        Joose.Managed.Stem.superClass.initialize.call(this, props)
        
        var targetMeta = this.targetMeta
        
        this.addProperty('attributes', {
            meta : this.attributesMC,
            
            //it can be no 'targetMeta' in clones
            properties : targetMeta ? targetMeta.attributes : {}
        })
        
        
        this.addProperty('methods', {
            meta : this.methodsMC,
            
            properties : targetMeta ? targetMeta.methods : {}
        })
        
        
        this.addProperty('requirements', {
            meta : this.requirementsMC
        })
        
        
        this.addProperty('methodsModifiers', {
            meta : this.methodsModifiersMC
        })
    },
    
    
    reCompose : function () {
        var c       = this.targetMeta.c
        
        this.preApply(c)
        
        Joose.Managed.Stem.superClass.reCompose.call(this)
        
        this.apply(c)
    },
    
    
    deCompose : function () {
        var c       = this.targetMeta.c
        
        this.unapply(c)
        
        Joose.Managed.Stem.superClass.deCompose.call(this)
        
        this.postUnApply(c)
    }
    
    
}).c
;
Joose.Managed.Builder = new Joose.Proto.Class('Joose.Managed.Builder', {
    
    targetMeta          : null,
    
    
    _buildStart : function (targetMeta, props) {
        targetMeta.stem.open()
        
        Joose.A.each([ 'trait', 'traits', 'removeTrait', 'removeTraits', 'does', 'doesnot', 'doesnt' ], function (builder) {
            if (props[builder]) {
                this[builder](targetMeta, props[builder])
                delete props[builder]
            }
        }, this)
    },
    
    
    _extend : function (props) {
        if (Joose.O.isEmpty(props)) return
        
        var targetMeta = this.targetMeta
        
        this._buildStart(targetMeta, props)
        
        Joose.O.eachOwn(props, function (value, name) {
            var handler = this[name]
            
            if (!handler) throw "Unknow builder [" + name + "] was used during extending of [" + targetMeta.c + "]"
            
            handler.call(this, targetMeta, value)
        }, this)
        
        this._buildComplete(targetMeta, props)
    },
    

    _buildComplete : function (targetMeta, props) {
        targetMeta.stem.close()
    },
    
    
    methods : function (targetMeta, info) {
        Joose.O.eachOwn(info, function (value, name) {
            targetMeta.addMethod(name, value)
        })
    },
    

    removeMethods : function (targetMeta, info) {
        Joose.A.each(info, function (name) {
            targetMeta.removeMethod(name)
        })
    },
    
    
    have : function (targetMeta, info) {
        Joose.O.eachOwn(info, function (value, name) {
            targetMeta.addAttribute(name, value)
        })
    },
    
    
    havenot : function (targetMeta, info) {
        Joose.A.each(info, function (name) {
            targetMeta.removeAttribute(name)
        })
    },
    

    havent : function (targetMeta, info) {
        this.havenot(targetMeta, info)
    },
    
    
    after : function (targetMeta, info) {
        Joose.O.each(info, function (value, name) {
            targetMeta.addMethodModifier(name, value, Joose.Managed.Property.MethodModifier.After)
        })
    },
    
    
    before : function (targetMeta, info) {
        Joose.O.each(info, function (value, name) {
            targetMeta.addMethodModifier(name, value, Joose.Managed.Property.MethodModifier.Before)
        })
    },
    
    
    override : function (targetMeta, info) {
        Joose.O.each(info, function (value, name) {
            targetMeta.addMethodModifier(name, value, Joose.Managed.Property.MethodModifier.Override)
        })
    },
    
    
    around : function (targetMeta, info) {
        Joose.O.each(info, function (value, name) {
            targetMeta.addMethodModifier(name, value, Joose.Managed.Property.MethodModifier.Around)
        })
    },
    
    
    augment : function (targetMeta, info) {
        Joose.O.each(info, function (value, name) {
            targetMeta.addMethodModifier(name, value, Joose.Managed.Property.MethodModifier.Augment)
        })
    },
    
    
    removeModifier : function (targetMeta, info) {
        Joose.A.each(info, function (name) {
            targetMeta.removeMethodModifier(name)
        })
    },
    
    
    does : function (targetMeta, info) {
        Joose.A.each(Joose.O.wantArray(info), function (desc) {
            targetMeta.addRole(desc)
        })
    },
    

    doesnot : function (targetMeta, info) {
        Joose.A.each(Joose.O.wantArray(info), function (desc) {
            targetMeta.removeRole(desc)
        })
    },
    
    
    doesnt : function (targetMeta, info) {
        this.doesnot(targetMeta, info)
    },
    
    
    trait : function () {
        this.traits.apply(this, arguments)
    },
    
    
    traits : function (targetMeta, info) {
        if (targetMeta.firstPass) return
        
        if (!targetMeta.meta.isDetached) throw "Can't apply trait to not detached class"
        
        targetMeta.meta.extend({
            does : info
        })
    },
    
    
    removeTrait : function () {
        this.removeTraits.apply(this, arguments)
    },
     
    
    removeTraits : function (targetMeta, info) {
        if (!targetMeta.meta.isDetached) throw "Can't remove trait from not detached class"
        
        targetMeta.meta.extend({
            doesnot : info
        })
    }
    
    
    
}).c;
Joose.Managed.Class = new Joose.Proto.Class('Joose.Managed.Class', {
    
    isa                         : Joose.Proto.Class,
    
    stem                        : null,
    stemClass                   : Joose.Managed.Stem,
    stemClassCreated            : false,
    
    builder                     : null,
    builderClass                : Joose.Managed.Builder,
    builderClassCreated         : false,
    
    isDetached                  : false,
    firstPass                   : true,
    
    // a special instance, which, when passed as 1st argument to constructor, signifies that constructor should
    // skips traits processing for this instance
    skipTraitsAnchor            : {},
    
    
    //build for metaclasses - collects traits from roles
    BUILD : function () {
        var sup = Joose.Managed.Class.superClass.BUILD.apply(this, arguments)
        
        var props   = sup.__extend__
        
        var traits = Joose.O.wantArray(props.trait || props.traits || [])
        delete props.trait
        delete props.traits
        
        Joose.A.each(Joose.O.wantArray(props.does || []), function (arg) {
            var role = (arg.meta instanceof Joose.Managed.Class) ? arg : arg.role
            
            if (role.meta.meta.isDetached) traits.push(role.meta.constructor)
        })
        
        if (traits.length) props.traits = traits 
        
        return sup
    },
    
    
    initInstance : function (instance, props) {
        Joose.O.each(this.attributes, function (attribute, name) {
            
            if (attribute instanceof Joose.Managed.Attribute) 
                attribute.initFromConfig(instance, props)
            else 
                if (props.hasOwnProperty(name)) instance[name] = props[name]
        })
    },
    
    
    // we are using the same constructor for usual and meta- classes
    defaultConstructor: function () {
        return function (skipTraitsAnchor, params) {
            var thisMeta    = this.meta
            var skipTraits  = skipTraitsAnchor == thisMeta.skipTraitsAnchor
            
            var props   = this.BUILD.apply(this, skipTraits ? params : arguments)
            // either looking for traits in __extend__ (meta-class) or in usual props (usual class)
            var extend  = props.__extend__ || props
            
            var traits = extend.trait || extend.traits
            
            if (traits || extend.detached) {
                delete extend.trait
                delete extend.traits
                delete extend.detached
                
                if (!skipTraits) {
                    var classWithTrait  = thisMeta.subClass({ does : traits || [] }, thisMeta.name)
                    var meta            = classWithTrait.meta
                    meta.isDetached     = true
                    
                    return meta.instantiate(thisMeta.skipTraitsAnchor, arguments)
                }
            }
            
            thisMeta.initInstance(this, props)
            
            return thisMeta.hasMethod('initialize') && this.initialize(props) || this
        }
    },
    
    
    finalize: function (extend) {
        Joose.Managed.Class.superClass.finalize.call(this, extend)
        
        this.stem.close()
        
        this.afterMutate()
    },
    
    
    processStem : function () {
        Joose.Managed.Class.superClass.processStem.call(this)
        
        this.builder    = new this.builderClass({ targetMeta : this })
        this.stem       = new this.stemClass({ name : this.name, targetMeta : this })
        
        var builderClass = this.getClassInAttribute('builderClass')
        
        if (builderClass) {
            this.builderClassCreated = true
            this.addAttribute('builderClass', this.subClassOf(builderClass))
        }
        
        
        var stemClass = this.getClassInAttribute('stemClass')
        
        if (stemClass) {
            this.stemClassCreated = true
            this.addAttribute('stemClass', this.subClassOf(stemClass))
        }
    },
    
    
    extend : function (props) {
        if (props.builder) {
            this.getBuilderTarget().meta.extend(props.builder)
            delete props.builder
        }
        
        if (props.stem) {
            this.getStemTarget().meta.extend(props.stem)
            delete props.stem
        }
        
        this.builder._extend(props)
        
        this.firstPass = false
        
        if (!this.stem.opened) this.afterMutate()
    },
    
    
    getBuilderTarget : function () {
        var builderClass = this.getClassInAttribute('builderClass')
        if (!builderClass) throw "Attempt to extend a builder on non-meta class"
        
        return builderClass
    },
    

    getStemTarget : function () {
        var stemClass = this.getClassInAttribute('stemClass')
        if (!stemClass) throw "Attempt to extend a stem on non-meta class"
        
        return stemClass
    },
    
    
    getClassInAttribute : function (attributeName) {
        var attrClass = this.getAttribute(attributeName)
        if (attrClass instanceof Joose.Managed.Property.Attribute) attrClass = attrClass.value
        
        return attrClass
    },
    
    
    addMethodModifier: function (name, func, type) {
        var props = {}
        
        props.init = func
        props.meta = type
        
        return this.stem.properties.methodsModifiers.addProperty(name, props)
    },
    
    
    removeMethodModifier: function (name) {
        return this.stem.properties.methodsModifiers.removeProperty(name)
    },
    
    
    addMethod: function (name, func, props) {
        props = props || {}
        props.init = func
        
        return this.stem.properties.methods.addProperty(name, props)
    },
    
    
    addAttribute: function (name, init, props) {
        props = props || {}
        props.init = init
        
        return this.stem.properties.attributes.addProperty(name, props)
    },
    
    
    removeMethod : function (name) {
        return this.stem.properties.methods.removeProperty(name)
    },

    
    removeAttribute: function (name) {
        return this.stem.properties.attributes.removeProperty(name)
    },
    
    
    hasMethod: function (name) {
        return this.stem.properties.methods.haveProperty(name)
    },
    
    
    hasAttribute: function (name) { 
        return this.stem.properties.attributes.haveProperty(name)
    },
    
    
    hasOwnMethod: function (name) {
        return this.stem.properties.methods.haveOwnProperty(name)
    },
    
    
    hasOwnAttribute: function (name) { 
        return this.stem.properties.attributes.haveOwnProperty(name)
    },
    

    getMethod : function (name) {
        return this.stem.properties.methods.getProperty(name)
    },
    
    
    getAttribute : function (name) {
        return this.stem.properties.attributes.getProperty(name)
    },
    
    
    eachRole : function (roles, func, scope) {
        Joose.A.each(roles, function (arg, index) {
            var role = (arg.meta instanceof Joose.Managed.Class) ? arg : arg.role
            
            func.call(scope || this, arg, role, index)
        }, this)
    },
    
    
    addRole : function () {
        
        this.eachRole(arguments, function (arg, role) {
            
            this.beforeRoleAdd(role)
            
            var desc = arg
            
            //compose descriptor can contain 'alias' and 'exclude' fields, in this case actual reference should be stored
            //into 'propertySet' field
            if (role != arg) {
                desc.propertySet = role.meta.stem
                delete desc.role
            } else
                desc = desc.meta.stem
            
            this.stem.addComposeInfo(desc)
            
        }, this)
    },
    
    
    beforeRoleAdd : function (role) {
        var roleMeta = role.meta
        
        if (roleMeta.builderClassCreated) this.getBuilderTarget().meta.extend({
            does : [ roleMeta.getBuilderTarget() ]
        })
        
        if (roleMeta.stemClassCreated) this.getStemTarget().meta.extend({
            does : [ roleMeta.getStemTarget() ]
        })
        
        if (roleMeta.meta.isDetached && !this.firstPass) this.builder.traits(this, roleMeta.constructor)
    },
    
    
    beforeRoleRemove : function (role) {
        var roleMeta = role.meta
        
        if (roleMeta.builderClassCreated) this.getBuilderTarget().meta.extend({
            doesnt : [ roleMeta.getBuilderTarget() ]
        })
        
        if (roleMeta.stemClassCreated) this.getStemTarget().meta.extend({
            doesnt : [ roleMeta.getStemTarget() ]
        })
        
        if (roleMeta.meta.isDetached && !this.firstPass) this.builder.removeTraits(this, roleMeta.constructor)
    },
    
    
    removeRole : function () {
        this.eachRole(arguments, function (arg, role) {
            this.beforeRoleRemove(role)
            
            this.stem.removeComposeInfo(role.meta.stem)
        }, this)
    },
    
    
    getRoles : function () {
        var roles = []
        
        Joose.A.each(this.stem.composedFrom, function (composeDesc) {
            //compose descriptor can contain 'alias' and 'exclude' fields, in this case actual reference is stored
            //into 'propertySet' field
            if (!(composeDesc instanceof Joose.Managed.PropertySet)) composeDesc = composeDesc.propertySet
            
            roles.push(composeDesc.targetMeta.c)
        })
        
        return roles
    },
    
    
    does : function (role) {
        var myRoles = this.getRoles()
        
        for (var i = 0; i < myRoles.length; i++) if (role == myRoles[i]) return true
        for (var i = 0; i < myRoles.length; i++) if (myRoles[i].meta.does(role)) return true
        
        var superMeta = this.superClass.meta
        
        // considering the case of inheriting from non-Joose classes
        if (this.superClass != Joose.Proto.Empty && superMeta && superMeta.meta && superMeta.meta.hasMethod('does')) return superMeta.does(role)
        
        return false
    },
    
    
    getMethods : function () {
        return this.stem.properties.methods
    },
    
    
    getAttributes : function () {
        return this.stem.properties.attributes
    },
    
    
    afterMutate : function () {
    }
    
    
}).c;
Joose.Managed.Role = new Joose.Managed.Class('Joose.Managed.Role', {
    
    isa                         : Joose.Managed.Class,
    
    have : {
        defaultSuperClass       : Joose.Proto.Empty,
        
        builderRole             : null,
        stemRole                : null
    },
    
    
    methods : {
        
        defaultConstructor : function () {
            return function () {
                throw "Roles cant be instantiated"
            }
        },
        

        processSuperClass : function () {
            if (this.superClass != this.defaultSuperClass) throw "Roles can't inherit from anything"
        },
        
        
        getBuilderTarget : function () {
            if (!this.builderRole) {
                this.builderRole = new this.constructor().c
                this.builderClassCreated = true
            }
            
            return this.builderRole
        },
        
    
        getStemTarget : function () {
            if (!this.stemRole) {
                this.stemRole = new this.constructor().c
                this.stemClassCreated = true
            }
            
            return this.stemRole
        },
        
    
        addRequirement : function (methodName) {
            this.stem.properties.requirements.addProperty(methodName, {})
        }
        
    },
    

    stem : {
        methods : {
            
            apply : function () {
            },
            
            
            unapply : function () {
            }
        }
    },
    
    
    builder : {
        methods : {
            requires : function (targetClassMeta, info) {
                Joose.A.each(Joose.O.wantArray(info), function (methodName) {
                    targetClassMeta.addRequirement(methodName)
                }, this)
            }
        }
    }
    
}).c;
Joose.Managed.Attribute = new Joose.Managed.Class('Joose.Managed.Attribute', {
    
    isa : Joose.Managed.Property.Attribute,
    
    have : {
        is              : null,
        
        builder         : null,
        
        isPrivate       : false,
        
        role            : null,
        
        publicName      : null,
        setterName      : null,
        getterName      : null,
        
        //indicates the logical readableness/writeableness of the attribute
        readable        : false,
        writeable       : false,
        
        //indicates the physical presense of the accessor (may be absent for "combined" accessors for example)
        hasGetter       : false,
        hasSetter       : false,
        
        required        : false
    },
    
    
    after : {
        initialize : function () {
            var name = this.name
            
            this.publicName = name.replace(/^_+/, '')
            
            this.slot = this.isPrivate ? '$$' + name : name
            
            this.setterName = this.setterName || this.getSetterName()
            this.getterName = this.getterName || this.getGetterName()
            
            this.readable  = this.hasGetter = /^r/i.test(this.is)
            this.writeable = this.hasSetter = /^.w/i.test(this.is)
        }
    },
    
    
    override : {
        
        computeValue : function () {
            if (!Joose.O.isFunction(this.init)) this.SUPER()
        },
        
        
        preApply : function (targetClass) {
            targetClass.meta.extend({
                methods : this.getAccessorsFor(targetClass)
            })
        },
        
        
        postUnApply : function (from) {
            from.meta.extend({
                removeMethods : this.getAccessorsFrom(from)
            })
        }
        
    },
    
    
    methods : {
        
        getAccessorsFor : function (targetClass) {
            var targetMeta = targetClass.meta
            var setterName = this.setterName
            var getterName = this.getterName
            
            var methods = {}
            
            if (this.hasSetter && !targetMeta.hasMethod(setterName)) {
                methods[setterName] = this.getSetter()
                methods[setterName].ACCESSOR_FROM = this
            }
            
            if (this.hasGetter && !targetMeta.hasMethod(getterName)) {
                methods[getterName] = this.getGetter()
                methods[getterName].ACCESSOR_FROM = this
            }
            
            return methods
        },
        
        
        getAccessorsFrom : function (from) {
            var targetMeta = from.meta
            var setterName = this.setterName
            var getterName = this.getterName
            
            var setter = this.hasSetter && targetMeta.getMethod(setterName)
            var getter = this.hasGetter && targetMeta.getMethod(getterName)
            
            var removeMethods = []
            
            if (setter && setter.value.ACCESSOR_FROM == this) removeMethods.push(setterName)
            if (getter && getter.value.ACCESSOR_FROM == this) removeMethods.push(getterName)
            
            return removeMethods
        },
        
        
        getGetterName : function () {
            return 'get' + Joose.S.uppercaseFirst(this.publicName)
        },


        getSetterName : function () {
            return 'set' + Joose.S.uppercaseFirst(this.publicName)
        },
        
        
        getSetter : function () {
            var slot = this.slot
            
            return function (value) {
                this[slot] = value
                return this
            }
        },
        
        
        getGetter : function () {
            var slot = this.slot
            
            return function () {
                return this[slot]
            }
        },
        
        
        getValueFrom : function (instance) {
            var getterName      = this.getterName
            
            if (this.readable && instance.meta.hasMethod(getterName)) return instance[getterName]()
            
            return instance[this.slot]
        },
        
        
        setValueTo : function (instance, value) {
            var setterName      = this.setterName
            
            if (this.writeable && instance.meta.hasMethod(setterName)) 
                instance[setterName](value)
            else
                instance[this.slot] = value
        },
        
        
        initFromConfig : function (instance, config) {
            var name            = this.name
            
            var value, isSet = false
            
            if (config.hasOwnProperty(name)) {
                value = config[name]
                isSet = true
            } else 
                if (Joose.O.isFunction(this.init)) {
                    
                    value = this.init.call(instance, name, config)
                    isSet = true
                    
                } else if (this.builder) {
                    
                    value = instance[ this.builder.replace(/^this\./, '') ](name, config)
                    isSet = true
                } 
            
            if (isSet)
                this.setValueTo(instance, value)
            else 
                if (this.required) throw "Required attribute [" + name + "] is missed during initialization of " + instance
        }
    }

}).c
;
Joose.Managed.PropertySet.Namespace = new Joose.Proto.Class('Joose.Managed.PropertySet.Namespace', {
    
    isa : Joose.Managed.PropertySet,
    
    propertyMetaClass       : null,
    
    targetMeta              : null,
    
    container               : null,
    
    
    initialize : function (props) {
        Joose.Managed.PropertySet.Namespace.superClass.initialize.call(this, props)
        
        this.container = this.targetMeta.c
    },
    
    
    
    addProperty : function (name, value) {
        if (value && value.meta && value.meta.meta.hasAttribute('ns')) value.meta.parentNs = this.targetMeta.ns
        
        return this.container[name] = this.properties[name] = value
    },
    

    removeProperty : function (name) {
        try {
            delete this.container[name]
        } catch(e) {
            this.container[name] = undefined
        }
        
        return Joose.Managed.PropertySet.Namespace.superClass.removeProperty.call(this, name)
    }
    
}).c
;
Joose.Managed.Attribute.Builder = new Joose.Managed.Role('Joose.Managed.Attribute.Builder', {
    
    
    have : {
        defaultAttributeClass : Joose.Managed.Attribute
    },
    
    builder : {
        
        methods : {
            
            has : function (targetClassMeta, info) {
                Joose.O.eachOwn(info, function (props, name) {
                    if (typeof props != 'object' || props == null || props.constructor == RegExp) props = { init : props }
                    
                    props.meta = props.meta || targetClassMeta.defaultAttributeClass
                    
                    if (/^__/.test(name)) {
                        name = name.replace(/^_+/, '')
                        
                        props.isPrivate = true
                    }
                    
                    targetClassMeta.addAttribute(name, props.init, props)
                }, this)
            },
            
            
            hasnot : function (targetClassMeta, info) {
                this.havenot(targetClassMeta, info)
            },
            
            
            hasnt : function (targetClassMeta, info) {
                this.hasnot(targetClassMeta, info)
            }
        }
            
    }
    
}).c
;
Joose.Managed.My = new Joose.Managed.Role('Joose.Managed.My', {
    
    have : {
        myClass                         : null,
        
        needToReAlias                   : false
    },
    
    
    methods : {
        createMy : function (extend) {
            var thisMeta = this.meta
            var isRole = this instanceof Joose.Managed.Role
            
            var myExtend = extend.my || {}
            delete extend.my
            
            // Symbiont will generally have the same meta class as its hoster, excepting the cases, when the superclass also have the symbiont. 
            // In such cases, the meta class for symbiont will be inherited (unless explicitly specified)
            
            if (!isRole) myExtend.isa = myExtend.isa || this.superClass.meta.myClass 

            if (!myExtend.meta && !myExtend.isa) myExtend.meta = this.constructor
            
            var createdClass = this.myClass = Class(myExtend)
            
            this.c.prototype.my = this.c.my = isRole ? createdClass : new createdClass({ HOST : this.c })
            
            this.needToReAlias = true
        },
        
        
        aliasStaticMethods : function () {
            this.needToReAlias = false
            
            var c           = this.c
            var myProto     = this.myClass.prototype
            
            Joose.O.eachOwn(c, function (property, name) {
                if (property.IS_ALIAS) delete c[ name ] 
            })
            
            this.myClass.meta.stem.properties.methods.each(function (method, name) {
                
                if (!c[ name ])
                    (c[ name ] = function () {
                        return myProto[ name ].apply(c.my, arguments)
                    }).IS_ALIAS = true
            })
        }
    },
    
    
    override : {
        
        extend : function (props) {
            var myClass = this.myClass
            
            if (!myClass && this.superClass.meta.myClass) this.createMy(props)
            
            if (props.my) {
                if (!myClass) 
                    this.createMy(props)
                else {
                    this.needToReAlias = true
                    
                    myClass.meta.extend(props.my)
                    delete props.my
                }
            }
            
            this.SUPER(props)
            
            if (this.needToReAlias && !(this instanceof Joose.Managed.Role)) this.aliasStaticMethods()
        }  
    },
    
    
    before : {
        
        addRole : function () {
            var myStem
            
            Joose.A.each(arguments, function (arg) {
                //instanceof Class to allow treat classes as roles
                var role = (arg.meta instanceof Joose.Managed.Class) ? arg : arg.role
                
                if (role.meta.meta.hasAttribute('myClass') && role.meta.myClass) {
                    
                    if (!this.myClass) {
                        this.createMy({
                            my : {
                                does : role.meta.myClass
                            }
                        })
                        return
                    }
                    
                    myStem = this.myClass.meta.stem
                    if (!myStem.opened) myStem.open()
                    
                    myStem.addComposeInfo(role.my.meta.stem)
                }
            }, this)
            
            if (myStem) {
                myStem.close()
                
                this.needToReAlias = true
            }
        },
        
        
        removeRole : function () {
            if (!this.myClass) return
            
            var myStem = this.myClass.meta.stem
            myStem.open()
            
            Joose.A.each(arguments, function (role) {
                if (role.meta.meta.hasAttribute('myClass') && role.meta.myClass) {
                    myStem.removeComposeInfo(role.my.meta.stem)
                    
                    this.needToReAlias = true
                }
            }, this)
            
            myStem.close()
        }
        
    }
    
}).c;
Joose.Namespace = function () { throw "Modules may not be instantiated." }

Joose.Namespace.Able = new Joose.Managed.Role('Joose.Namespace.Able', {

    have : {
        parentNs                : null,
        
        ns                      : null,
        
        bodyFunc                : null
    },
    
    
    before : {
        extend : function (extend) {
            if (extend.body) {
                this.bodyFunc = extend.body
                delete extend.body
            }
        }
    },
    
    
    after: {
        //at this point targetMeta will contain 'c' which is a container for Joose.Managed.PropertySet.Namespace
        adaptConstructor: function (extend) {
            var localName = (this.name || '').split('.').pop()
            
            //XXX check that 'ns' is overwritten after planting
            this.ns = new Joose.Managed.PropertySet.Namespace({ name : localName, targetMeta : this })
        },
        
        
        afterMutate : function () {
            var bodyFunc = this.bodyFunc
            delete this.bodyFunc
            
            if (bodyFunc) Joose.Namespace.Manager.my.executeIn(this.c, bodyFunc)
        }
    }
    
}).c;
Joose.Managed.Bootstrap = new Joose.Managed.Role('Joose.Managed.Bootstrap', {
    
    does   : [ Joose.Namespace.Able, Joose.Managed.My, Joose.Managed.Attribute.Builder ]
    
}).c
;
Joose.Meta = function () { throw "Modules may not be instantiated." }


Joose.Meta.Object = new Joose.Proto.Class('Joose.Meta.Object', {
    
    isa             : Joose.Proto.Object
    
}).c;
Joose.Meta.Class = new Joose.Managed.Class('Joose.Meta.Class', {
    
    isa                         : Joose.Managed.Class,
    
    does                        : Joose.Managed.Bootstrap,
    
    have : {
        defaultSuperClass       : Joose.Meta.Object
    }
    
}).c

;
Joose.Meta.Role = new Joose.Meta.Class('Joose.Meta.Role', {
    
    isa                         : Joose.Managed.Role,
    
    does                        : Joose.Managed.Bootstrap
    
}).c;
Joose.Namespace.Keeper = new Joose.Meta.Class('Joose.Namespace.Keeper', {
    
    isa : Joose.Meta.Class,
    
    have : {
        externalConstructor             : null
    },
    
    
    methods: {
        
        defaultConstructor: function () {
            return function () {
                //constructors should assume that meta is attached to 'arguments.callee' (not to 'this') 
                var thisMeta = arguments.callee.meta
                
                if (thisMeta instanceof Joose.Namespace.Keeper) throw "Module [" + thisMeta.c + "] may not be instantiated."
                
                var externalConstructor = thisMeta.externalConstructor
                
                if (typeof externalConstructor == 'function') {
                    
                    externalConstructor.meta = thisMeta
                    
                    return externalConstructor.apply(this, arguments)
                }
                
                throw "NamespaceKeeper of [" + thisMeta.name + "] was planted incorrectly."
            }
        },
        
        
        //withClass should be not constructed yet on this stage (see Joose.Proto.Class.construct)
        //it should be on the 'constructorOnly' life stage (should already have constructor)
        plant: function (withClass) {
            this.copyNamespaceState(withClass)
            
            var keeper = this.c
            
            keeper.meta = withClass.meta
            
            keeper.meta.c = keeper
            keeper.meta.externalConstructor = withClass
        },

        
        copyNamespaceState : function (targetClass) {
            var targetMeta = targetClass.meta
            
            targetMeta.parentNs             = this.parentNs
            
            targetMeta.ns                   = this.ns
        }
        
    }
    
}).c


;
Joose.Namespace.Manager = new Joose.Managed.Class('Joose.Namespace.Manager', {
    
    have : {
        global      : null,
        globalNs    : null,
        
        current     : null
    },
    
    
    methods : {
        
        initialize : function () {
            var globalKeeper = this.global = new Joose.Namespace.Keeper('').c
            
            var globalNs = this.globalNs = globalKeeper.meta.ns
            
            
            globalNs.container      = Joose.is_NodeJS && global || Joose.top

            globalKeeper.meta.parentNs    = globalKeeper
            
            this.current = [ globalKeeper ]
        },
        
        
        getCurrent: function () {
            return this.current[0]
        },
        
        
        executeIn : function (ns, func) {
            var current = this.current
            
            var scope = ns.meta.ns ? ns.meta.ns.container : ns
            
            current.unshift(ns)
            var res = func.call(scope, ns)
            current.shift()
            
            return res
        },
        
        
        earlyCreate : function (name, metaClass, props) {
            props.constructorOnly = true
            
            return new metaClass(name, props).c
        },
        
        
        //this function establishing the full "namespace chain" (including the last element)
        create : function (nsName, metaClass, extend) {
            //if no name provided, then we creating an anonymous class, so just skip all the namespace manipulations
            if (!nsName) return new metaClass(nsName, extend).c
            
            var me = this
            
            if (/^\./.test(nsName)) return this.executeIn(this.global, function () {
                return me.create(nsName.replace(/^\./, ''), metaClass, extend)
            })
            
            props = extend || {}
            
            var parts = Joose.S.saneSplit(nsName, '.')
            var object  = this.getCurrent()
            var soFar   = Joose.S.saneSplit(object.meta.name, '.')
            
            for(var i = 0; i < parts.length; i++) {
                var part = parts[i]
                var isLast = i == parts.length - 1
                
                if (part == "meta" || part == "my" || !part) throw "Module name [" + nsName + "] may not include a part called 'meta' or 'my' or empty part."
                
                var cur = (object == this.global ? this.global.meta.ns.container : object)[part]//object.meta.ns.getProperty(part)
                
                soFar.push(part)
                var soFarName = soFar.join(".")
                var needFinalize = false
                var nsKeeper
                
                if (typeof cur == "undefined") {
                    if (isLast) {
                        nsKeeper = this.earlyCreate(soFarName, metaClass, props)
                        needFinalize = true
                    } else
                        nsKeeper = new Joose.Namespace.Keeper(soFarName).c
                    
                    if (object.meta) 
                        object.meta.ns.addProperty(part, nsKeeper)
                    else
                        object[part] = nsKeeper
                    
                    cur = nsKeeper
                } else if (isLast && cur && cur.meta) {
                    //XXX needs cleanup and sanitizing
                    if (cur.meta.constructor == metaClass && extend)
                        cur.meta.extend(props)
                    else if (cur.meta instanceof Joose.Namespace.Keeper && metaClass != Joose.Namespace.Keeper) { 
                        cur.meta.plant(this.earlyCreate(soFarName, metaClass, props))
                        needFinalize = true
                    } 
                    else if (metaClass != Joose.Namespace.Keeper)
                        throw "Re-declaration of class " + soFarName + "with different meta is not allowed"
                } else 
                    if (isLast && !(cur && cur.meta && cur.meta.meta && cur.meta.meta.hasAttribute('ns'))) throw "Trying to setup module " + soFarName + " failed. There is already something: " + cur
                
                if (needFinalize) cur.meta.construct(props)
                    
                object = cur
            }
            
            return object
        },
        
        
        
//        //this function establishing the full "namespace chain" (including the last element)
//        prepareNamespace : function (nsName) {
//            
//            var parts = Joose.S.saneSplit(nsName, '.')
//            var object  = this.getCurrent()
//            var soFar   = Joose.S.saneSplit(object.meta.name, '.')
//            
//            for(var i = 0; i < parts.length; i++) {
//                var part = parts[i]
//                
//                if (part == "meta" || part == "my" || !part) throw "Module name [" + nsName + "] may not include a part called 'meta' or 'my' or empty part."
//                
//                var cur = (object == this.global ? this.global.meta.ns.container : object)[part]
//                
//                soFar.push(part)
//                
//                if (cur === undefined) {
//                    var nsKeeper = new Joose.Namespace.Keeper(soFar.join(".")).c
//                    
//                    var objectMeta = object.meta
//                    
//                    if (objectMeta && objectMeta.ns) 
//                        objectMeta.ns.addProperty(part, nsKeeper)
//                    else
//                        object[part] = nsKeeper
//                    
//                    cur = nsKeeper
//                }
//                    
//                object = cur
//            }
//            
//            if (!(object && object.meta && object.meta.ns)) throw "Trying to setup module " + soFarName + " failed. There is already something: " + object
//            
//            return object
//        },
        
        
        prepareProperties : function (name, props, defaultMeta, callback) {
            if (name && typeof name != 'string') {
                props = name
                name = null
            }
            
            var meta
            
            if (props && props.meta) {
                meta = props.meta
                delete props.meta
            }
            
            if (!meta)
                if (props && typeof props.isa == 'function')
                    meta = props.isa.meta.constructor
                else
                    meta = defaultMeta
            
            return callback.call(this, name, meta, props)
        },
        
        
        getDefaultHelperFor : function (metaClass) {
            var me = this
            
            return function (name, props) {
                return me.prepareProperties(name, props, metaClass, function (name, meta, props) {
                    return me.create(name, meta, props)
                })
            }
        },
        
        
        register : function (helperName, metaClass, func) {
            var me = this
            
            if (this.meta.hasMethod(helperName)) {
                var helper = function () {
                    return me[helperName].apply(me, arguments)
                }
                
                if (!Joose.top[helperName]) Joose.top[helperName]  = helper
                if (!Joose[helperName])     Joose[helperName]      = helper
                
                // declaring NodeJS global
                if (Joose.is_NodeJS && !global[helperName]) global[helperName]      = helper
            } else {
                var methods = {}
                
                methods[helperName] = func || this.getDefaultHelperFor(metaClass)
                
                this.meta.extend({
                    methods : methods
                })
                
                this.register(helperName)
            }
        },
        
        
        Module : function (name, props) {
            return this.prepareProperties(name, props, Joose.Namespace.Keeper, function (name, meta, props) {
                if (typeof props == 'function') props = { body : props }    
                
                return this.create(name, meta, props)
            })
        }
        
        
        
    }
    
}).c

Joose.Namespace.Manager.my = new Joose.Namespace.Manager()

Joose.Namespace.Manager.my.register('Class', Joose.Meta.Class)
Joose.Namespace.Manager.my.register('Role', Joose.Meta.Role)
Joose.Namespace.Manager.my.register('Module');
;
;//Role('JooseX.Attribute.Delegated', {
//    
////    have : {
////        trigger        : null
////    }, 
////
////    
////    before : {
////        computeValue : function() {
////            if (this.trigger) this.is = 'rw'
////        }
////    },
////    
////    
////    after : {
////        
////        computeValue : function() {
////            if (this.trigger) {
////                var after = {}    
////                
////                after[this.setterName] = this.trigger    
////                
////                this.role.meta.extend({ after : after })
////            }
////        }
////        
////    }
//    
//});
Role('JooseX.Attribute.Trigger', {
    
    have : {
        trigger        : null
    }, 

    
    after : {
        initialize : function() {
            if (this.trigger) this.writeable = this.hasSetter = true
        }
    },
    
    
    override : {
        
        getSetter : function() {
            var original    = this.SUPER()
            var trigger     = this.trigger
            
            if (!trigger) return original
            
            var me = this
            
            return function () {
                var res = original.apply(this, arguments)
                
                trigger.call(this, me.getValueFrom(this))
                
                return res
            }
        }
    }
})    


/**

Name
====


JooseX.Attribute.Trigger - call a function after attribute has been changed via setter call


SYNOPSIS
========

            Class('Some.Class', { 
                has : {
                    someAttribuute : {
                        init : 'foo',
                        
                        trigger : function (newValue) {
                            this.triggerCalled = true
                        } 
                    }
                }
            })
            
            var instance = new Some.Class()
            
            instance.triggerCalled == false //trigger wasn't called yet
            
            instance.setSomeAttibute('bar')
            
            instance.triggerCalled == true //trigger was called


DESCRIPTION
===========

`JooseX.Attribute.Trigger` is a role, which triggers a function call, after an attribute was changed via setter call.

It is called as a method (in the scope of the instance), and receives the new value as argument. 

Trigger is also called when an attribute's value is passed to the constructor.

**Note**, that trigger will not be called for the value of attribute's `init` option, as it only translates the value to the prototype of the class 


SEE ALSO
========

[Main documentation page](../Attribute.html)



AUTHORS
=======

Nickolay Platonov <nplatonov@cpan.org>



COPYRIGHT AND LICENSE
=====================

Copyright (c) 2009, Nickolay Platonov

All rights reserved.

Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:

* Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
* Neither the name of Nickolay Platonov nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. 

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 


*/;
Role('JooseX.Attribute.Lazy', {
    
    
    have : {
        lazy        : null
    }, 
    
    
    before : {
        computeValue : function() {
            if (typeof this.init == 'function' && this.lazy) {
                this.lazy = this.init    
                delete this.init    
            }
        }
    },
    
    
    after : {
        initialize : function() {
            if (this.lazy) this.readable = this.hasGetter = true
        }
    },
    
    
    override : {
        
        getGetter : function() {
            var original    = this.SUPER()
            var lazy        = this.lazy
            
            if (!lazy) return original
            
            var me      = this    
            var slot    = this.slot    
            
            return function () {
                if (!this.hasOwnProperty(slot)) {
                    var initializer = typeof lazy == 'function' ? lazy : this[ lazy.replace(/^this\./, '') ]
                    
                    var init = initializer.call(this, me)
                    
                    me.setValueTo(this, init)
                }
                
                return original.call(this)    
            }
        },
        
        
//        getSetter : function() {
//            var original    = this.SUPER()
//            var lazy        = this.lazy
//            
//            if (!lazy) return original
//            
//            var name    = this.name    
//            
//            return function () {
//                return original.apply(this, arguments)    
//            }
//        },
        
        
        getRawValueFrom : function (instance) {
            var original = this.SUPER(instance)
            
            if (!this.lazy) return original
            
            return {
                predicate   : instance.hasOwnProperty(this.slot),
                value       : original
            }
        },
        
        
        setRawValueTo : function (instance, value) {
            if (!this.lazy) return this.SUPERARG(arguments)
            
            if (!value.predicate) 
                delete instance[ this.slot ]
            else
                this.SUPER(instance, value.value)
        }
    }
    
})


/**

Name
====


JooseX.Attribute.Lazy - a role, deferring the attribute initialization untill first call to getter


SYNOPSIS
========

            Class('Some.Class', { 
                has : {
                    lazyAttribute : {
                        init : function () {
                            return this.someCostlyComputation()
                        },
                        
                        lazy : true
                    },
                    
                    // -or-
                    
                    lazyAttribute : {
                        lazy : function () {
                            return this.someCostlyComputation()
                        }
                    },
                    
                    // -or-
                    
                    lazyAttribute : {
                        lazy : 'this.someCostlyComputation'
                    },
                    
                    // -or-
                    
                    lazyAttribute : {
                        lazy : 'someCostlyComputation'
                    }
                },
                
                
                methods : {
                    someCostlyComputation : function () {
                        ...
                    }
                }
            })
            
            var instance = new Some.Class()
            
            instance.lazyAttribute == undefined // true, initializer of lazy attribute wasn't called yet
            
            var lazy = instance.getLazyAttibute()
            
            instance.lazyAttribute != undefined // true, initializer of lazy attribute was called


DESCRIPTION
===========

Joose lets you defer attribute population by making an attribute lazy (see the [Synopsis][] for syntax) 

When `lazy` flag is set, the default is not generated until the getter method is called, rather than at object construction time. 
There are several reasons you might choose to do this.

First, if the default value for this attribute depends on some other attributes, then the attribute must be lazy. 
During object construction, defaults are not generated in a predictable order, so you cannot count on some other attribute being populated when generating a default.

Second, there's often no reason to calculate a default before it's needed. Making an attribute lazy lets you defer the cost until the attribute is needed. 
If the attribute is never needed, you save some CPU time.


SEE ALSO
========

[Main documentation page](../Attribute.html)



AUTHORS
=======

Nickolay Platonov <nplatonov@cpan.org>



COPYRIGHT AND LICENSE
=====================

Copyright (c) 2009, Nickolay Platonov

All rights reserved.

Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:

* Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
* Neither the name of Nickolay Platonov nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. 

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 


*/
;
Role('JooseX.Attribute.Accessor.Combined', {
    
    
    have : {
        isCombined        : false
    }, 
    
    
    after : {
        initialize : function() {
            this.isCombined = this.isCombined || /..c/i.test(this.is)
            
            if (this.isCombined) {
                this.slot = '$$' + this.name
                
                this.hasGetter = true
                this.hasSetter = false
                
                this.setterName = this.getterName = this.publicName
            }
        }
    },
    
    
    override : {
        
        getGetter : function() {
            var getter    = this.SUPER()
            
            if (!this.isCombined) return getter
            
            var setter    = this.getSetter()
            
            var me = this
            
            return function () {
                
                if (!arguments.length) {
                    if (me.readable) return getter.call(this)
                    throw "Call to getter of unreadable attribute: [" + me.name + "]"
                }
                
                if (me.writeable) return setter.apply(this, arguments)
                
                throw "Call to setter of read-only attribute: [" + me.name + "]"    
            }
        }
    }
    
})


/**

Name
====


JooseX.Attribute.Accessor.Combined - a role, combining setter and getter methods into one, ala perl


SYNOPSIS
========

            Class('Some.Class', { 
                has : {
                    attr : {
                        is : 'rwc',
                        
                        init : 'some init value'
                    }
                }
            })
            
            var instance = new Some.Class()
            
            instance.attr() == 'some init value'  // call to combined accessor as getter
            
            instance.attr('some other value')     // call to combined accessor as setter
            
            instance.attr() == 'some other value' // attribute was changed


DESCRIPTION
===========

This role combines the getter and setter methods into one, having the same name as attribute itself.

Call this method without arguments will be directed to the actual getter call.

Call with some arguments provided will be directed to setter call.

Its safe to combine this role with Lazy and Trigger, but it should be applied after them.


USAGE
=====

To enable this role provide the trailing `c` character in attribute's `is` option: 

            has : {
                attr : {
                    is : 'rwc',
                    
                    init : 'some init value'
                }
            }
            
Alternatively, explicitly specify `isCombined` option with some `true` value:

            has : {
                attr : {
                    is : 'rw',
                    
                    isCombined : true,
                    
                    init : 'some init value'
                }
            }
            

SEE ALSO
========

[Main documentation page](../Attribute.html)



AUTHORS
=======

Nickolay Platonov <nplatonov@cpan.org>



COPYRIGHT AND LICENSE
=====================

Copyright (c) 2009, Nickolay Platonov

All rights reserved.

Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:

* Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
* Neither the name of Nickolay Platonov nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. 

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 


*/
;
Joose.Managed.Attribute.meta.extend({
    does : [ JooseX.Attribute.Trigger, JooseX.Attribute.Lazy, JooseX.Attribute.Accessor.Combined ]
})            


/**

Name
====


JooseX.Attribute - Additional features for Joose attributes


SYNOPSIS
========

        <!-- Joose  -->
        <script type="text/javascript" src="/jsan/Task/Joose/Core.js"></script>
        
        <!-- Bootstraping Joose attributes with additional features -->
        <script type="text/javascript" src="/jsan/Task/JooseX/Attribute/Bootstrap.js"></script>


DESCRIPTION
===========

`JooseX.Attribute` is a collection of meta-roles for Joose attributes, which enhance them with advanced features.

To use the new features, add the bootstraping source file as in Synopsis.




Please refer to documentation of each role for details:

> - [JooseX.Attribute.Trigger](Attribute/Trigger.html)

> - [JooseX.Attribute.Lazy](Attribute/Lazy.html)

> - [JooseX.Attribute.Accessor.Combined](Attribute/Accessor/Combined.html)


GETTING HELP
============

This extension is supported via github issues tracker: <http://github.com/SamuraiJack/JooseX-Attribute/issues>

For general Joose questions you can also visit #joose on irc.freenode.org or the forum at: <http://joose.it/forum>
 


SEE ALSO
========

Web page of this module: <http://github.com/SamuraiJack/JooseX-Attribute/>

General documentation for Joose: <http://openjsan.org/go/?l=Joose>


BUGS
====

All complex software has bugs lurking in it, and this module is no exception.

Please report any bugs through the web interface at <http://github.com/SamuraiJack/JooseX-Attribute/issues>



AUTHORS
=======

Nickolay Platonov <nplatonov@cpan.org>



COPYRIGHT AND LICENSE
=====================

Copyright (c) 2009, Nickolay Platonov

All rights reserved.

Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:

* Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
* Neither the name of Nickolay Platonov nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. 

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 


*/
;
;
;Class('JooseX.Namespace.Depended.Manager', {
    
    my : {
    
        have : {
            
            INC                             : Joose.is_NodeJS ? require.paths : [ 'lib', '/jsan' ],
            
            disableCaching                  : true,
            
            resources                       : {},
            
            resourceTypes                   : {},
            
            ANONYMOUS_RESOURCE_COUNTER      : 0
        },
    
        
        
        methods : {
            
            //get own resource of some thing (resource will be also attached to that abstract thing)
            //if the something is requesting own resource its considered loaded
            getMyResource : function (type, token, me) {
                var resource = this.getResource({
                    type : type,
                    token : token
                })
                
                if (resource.attachedTo && resource.attachedTo != me) throw resource + " is already attached to [" + resource.attachedTo + "]"
                
                resource.attachedTo     = me
                resource.loaded         = true
                resource.loading        = false
                
                return resource
            },
            
            
            getResource : function (descriptor) {
                var type, token, requiredVersion
                
                if (typeof descriptor == 'object') {
                    type                = descriptor.type
                    token               = descriptor.token
                    requiredVersion     = descriptor.version
                    
                    delete descriptor.version
                    
                } else 
                    if (typeof descriptor == 'string') {
                    
                        var match = /^(\w+):\/\/(.+)/.exec(descriptor)
                        
                        if (!match) {
                            type = 'joose'
                            token = descriptor
                        } else {
                            type = match[1]
                            token = match[2]
                        }
                    }
                    
                if (!token) {
                    token = '__ANONYMOUS_RESOURCE__' + this.ANONYMOUS_RESOURCE_COUNTER++
                    descriptor = undefined
                }
                
                var id = type + '://' + token
                
                var resource = this.resources[id]
                
                if (!resource) {
                    var resourceClass = this.resourceTypes[type]
                    if (!resourceClass) throw "Unknown resource type: [" + type + "]"
                    
                    resource = this.resources[id] = new resourceClass(typeof descriptor == 'object' ? descriptor : { 
                        token : token,
                        
                        type : type
                    })
                }
                
                resource.setRequiredVersion(requiredVersion)
                
                return resource
            },
            
            
            registerResourceClass : function (typeName, resourceClass) {
                this.resourceTypes[typeName] = resourceClass
            },
            
            
            use : function (dependenciesInfo, callback, scope) {
                var nsManager   = Joose.Namespace.Manager.my
                var global      = nsManager.global
                
                Class({
                    use    : dependenciesInfo,
                    
                    body   : function () {
                        if (callback) nsManager.executeIn(global, function (ns) {
                            callback.call(scope || this, ns)
                        })
                    }
                })
            }      
        }
    }
})

use = function (dependenciesInfo, callback, scope) {
    JooseX.Namespace.Depended.Manager.my.use(dependenciesInfo, callback, scope) 
}

use.paths = JooseX.Namespace.Depended.Manager.my.INC


Joose.I.FutureClass = function (className) { return function () { return eval(className) } }


/**

Name
====


JooseX.Namespace.Depended.Manager - A global collection of all resources


SYNOPSIS
========

        JooseX.Namespace.Depended.Manager.my.registerResourceClass('custom-type', JooseX.Namespace.Depended.Resource.Custom)
        

DESCRIPTION
===========

`JooseX.Namespace.Depended.Manager` is a global collection of all resources. 

**Note:** Its a pure [static](http://openjsan.org/go?l=Joose.Manual.Static) class - all its methods and properties are static.


METHODS
=======

### registerResourceClass

> `void registerResourceClass(String type, Class constructor)`

> After you've created your custom resource class, you need to register it with call to this method.

> Then you can refer to new resources with the following descriptors: 

                {
                    type    : 'custom-type',
                    token   : 'some-token'
                }



GETTING HELP
============

This extension is supported via github issues tracker: <http://github.com/SamuraiJack/JooseX-Namespace-Depended-Manager/issues>

For general Joose questions you can also visit #joose on irc.freenode.org or the forum at: [http://joose.it/forum](http://joose.it/forum)
 


SEE ALSO
========

Authoring [JooseX.Namespace.Depended](Authoring.html)

Abstract base resource class: [JooseX.Namespace.Depended.Resource](Resource.html)

General documentation for Joose: <http://openjsan.org/go/?l=Joose>


BUGS
====

All complex software has bugs lurking in it, and this module is no exception.

Please report any bugs through the web interface at [http://github.com/SamuraiJack/JooseX-Namespace-Depended-Manager/issues](http://github.com/SamuraiJack/JooseX-Namespace-Depended-Manager/issues)



AUTHORS
=======

Nickolay Platonov [nplatonov@cpan.org](mailto:nplatonov@cpan.org)



COPYRIGHT AND LICENSE
=====================

Copyright (c) 2009, Nickolay Platonov

All rights reserved.

Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:

* Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
* Neither the name of Nickolay Platonov nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. 

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 


*/
;
Class('JooseX.Namespace.Depended.Resource', {
    
    has : {
        
        attachedTo          : null,
        
        type                : null,
        token               : null,
        
        id                  : null,
        
        loading             : false,
        loaded              : false,
        ready               : false,
        
        loadedFromURL       : null,
        
        readyListeners      : Joose.I.Array,
        
        dependencies        : Joose.I.Object,
        
        onBeforeReady       : { is : 'rw', init : null },
        readyDelegated      : false,
        
        version             : { is : 'rw', init : null },
        requiredVersion     : { is : 'rw', init : null },
        
        hasReadyCheckScheduled  : false
    },
    
    
    after: {
        
        initialize: function () {
            if (!this.id) this.id = this.type + '://' + this.token
        }
        
    },

    
    
    methods: {
        
        setOnBeforeReady : function (func) {
            if (this.onBeforeReady) throw "Can't redefine 'onBeforeReady' for " + this
            
            this.onBeforeReady = func
        },
        
        
        setVersion : function (version) {
            if (!version) return
            
            if (this.version) throw "Cant redefine version of " + this
            
            var requiredVersion = this.requiredVersion
            
            if (requiredVersion && version < requiredVersion) throw "Versions conflict on " + this + " required [" + requiredVersion + "], got [" + version + "]"
                
            this.version = version
        },
        
        
        setRequiredVersion : function (version) {
            if (!version) return
            
            var requiredVersion = this.requiredVersion
            
            if (!requiredVersion || version > requiredVersion) 
                if (this.isLoaded() || this.loading)
                    throw "Cant increase required version - " + this + " is already loaded"
                else
                    this.requiredVersion = version
        },
        
        
        toString : function () {
            return "Resource: id=[" + this.id + "], type=[" + this.meta.name + "]"
        },
        
        
        addDescriptor : function (descriptor) {
            var resource = JooseX.Namespace.Depended.Manager.my.getResource(descriptor)
            
            //if there is already such dependency or the resource is ready
            if (this.dependencies[ resource.id ] || resource.isReady()) return
            
            var me = this
            //pushing listener to the end(!) of the list
            resource.readyListeners.push(function () {
                
                delete me.dependencies[resource.id]
                me.checkReady()
            })
            
            //adding dependency
            this.dependencies[resource.id] = resource
            
            //we are not ready, since there are depedencies to load                
            this.ready = false
        },
        
        
        handleDependencies : function () {
            // || {} required for classes on which this Role was applied after they were created - they have this.dependencies not initialized
            Joose.O.eachOwn(this.dependencies || {}, function (resource) {
                resource.handleLoad()
            })
            
            this.checkReady()
        },
        
        
        checkReady : function () {
            if (!Joose.O.isEmpty(this.dependencies) || this.hasReadyCheckScheduled) return
            
            if (this.onBeforeReady) {
                
                if (!this.readyDelegated) {
                    this.readyDelegated = true
                    
                    var me = this
                    
                    this.onBeforeReady(function(){
                        me.fireReady()
                    }, me)
                }
            } else 
                this.fireReady()
        },
        
        
        fireReady: function () {
            this.ready = true
            
            var listeners = this.readyListeners
            
            this.readyListeners = []
            
            Joose.A.each(listeners, function (listener) {
                listener()
            })
        },
        
        
        isReady : function () {
            return this.ready
        },
        
        
        isLoaded : function () {
            return this.loaded || this.ready
        },
        
        
        handleLoad: function() {
            
            if (this.isLoaded()) {
                this.checkReady()
                return
            }
            
            if (this.loading) return
            this.loading = true
            
            var urls = Joose.O.wantArray(this.getUrls())
            
            var me = this

            // this delays the 'checkReady' until the resourse will be *fully* materialized
            // *fully* means that even the main class of the resource is already "ready"
            // the possible other classes in the same file could be not
            // see 110_several_classes_in_file.t.js, 120_script_tag_transport.t.js for example
            me.hasReadyCheckScheduled = true
            
            var onsuccess = function (resourceBlob, url) {
                me.loaded = true
                me.loading = false
                
                me.loadedFromURL = url
                
                me.materialize(resourceBlob, url)
                
                me.hasReadyCheckScheduled = false
                
                
                me.checkReady()
            }
            
            var onerror = function (e) {
                //if no more urls
                if (!urls.length) throw me + " not found" 
                
                me.load(urls.shift(), onsuccess, onerror)
            }
            
            this.load(urls.shift(), onsuccess, onerror)
        },
        

        getUrls: function () {
            throw "Abstract resource method 'getUrls' was called"
        },
        
        
        load : function (url, onsuccess, onerror) {
            throw "Abstract resource method 'load' was called"
        },
        
        
        materialize : function (resourceBlob) {
            throw "Abstract resource method 'materialize' was called"
        }
        
    }
})


/**

Name
====


JooseX.Namespace.Depended.Resource - Abstract resource class 


SYNOPSIS
========
        
        //mostly for subclassing only
        Class("JooseX.Namespace.Depended.Resource.JooseClass", {
        
            isa : JooseX.Namespace.Depended.Resource,
            
            ...
        })


DESCRIPTION
===========

`JooseX.Namespace.Depended.Resource` is an abstract resource class. Its not supposed to be used directly, instead you should use
one of its subclasses.


ATTRIBUTES
==========

### attachedTo

> `Object attachedTo`

> An arbitrary object to which this resource is attached (its a corresponding class in JooseX.Namespace.Depended)


### type

> `String type`

> A type of resource  - plain string. `JooseX.Namespace.Depended.Manager` maintain a collection of resource types, accessible 


### token

> `String token`

> A token of resource  - plain string with arbitrary semantic. Each subclass should provide this semantic along with `token -> url` conertion method (locator)  


### id

> `String id`

> An id of resource - is computed as `type + '://' + token'


### loading

> `Boolean loading`

> A sign whether this resource is currently loading

  
### loaded

> `Boolean loaded`

> A sign whether this resource is already loaded


### ready

> `Boolean ready`

> A sign whether this resource is considered ready. Resource is ready, when its loaded, and all its dependencies are ready.


### loadedFromURL

> `String loadedFromURL`

> An url, from which the resource was loaded.


### readyListeners

> `Array[Function] readyListeners`

> An array of functions, which will be called after this resource becomes ready. Functions will be called sequentially. 


### dependencies

> `Object dependencies`

> An object containing the dependencies of this resource. Keys are the `id`s of resources and the values - the resource instances itself.

 
### onBeforeReady

> `Function onBeforeReady`

> A function, which will be called, right after the all dependencies of the resource became ready, but before its own `readyListeners` will be called.
It supposed to perform any needed additional actions to post-process the loaded resource.

> Function will receive two arguments - the 1st is the callback, which should be called when `onBeforeReady` will finish its work. 2nd is the resource instance.

  
### version

> `r/w Number version`

> A version of this resource. Currently is handled as Number, this may change in future releases.

  
### requiredVersion

> `r/w Number requiredVersion`

> A *requiredVersion* version of this resource. Required here means the maximum version from all references to this resource. 



METHODS
=======

### addDescriptor

> `void addDescriptor(Object|String descriptor)`

> Add the resource, described with passed descriptor as the dependency for this resource.


### getUrls

> `String|Array[String] getUrls()`

> Abstract method, will throw an exception if not overriden. It should return the array of urls (or a single url) from which this resource can be potentially loaded. 
This method should take into account the `JooseX.Namespace.Depended.Manager.my.INC` setting


### load

> `void load(String url, Function onsuccess, Function onerror)`

> Abstract method, will throw an exception if not overriden. It should load the content of the resource from the passed `url`. If there was an error during loading
(for example file not found) should not throw the exception. Instead, should call the `onerror` continuation with it (exception instance).

> After successfull loading, should call the `onsuccess` continuation with the resource content as 1st argument, and `url` as 2nd: `onsuccess(text, url)`


### materialize

> `void materialize(String resourceBlob, String url)`

> Abstract method, will throw an exception if not overriden. It should "materialize" the resource. The concrete semantic of this action is determined by resource nature.
For example this method can create some tag in the DOM tree, or execute the code or something else.

> Currently this method is supposed to operate synchronously, this may change in future releases. 
 

SEE ALSO
========

Web page of this package: <http://github.com/SamuraiJack/JooseX-Namespace-Depended-Resource/>

General documentation for Joose: <http://openjsan.org/go/?l=Joose>


BUGS
====

All complex software has bugs lurking in it, and this module is no exception.

Please report any bugs through the web interface at <http://github.com/SamuraiJack/JooseX-Namespace-Depended-Resource/issues>



AUTHORS
=======

Nickolay Platonov <nplatonov@cpan.org>



COPYRIGHT AND LICENSE
=====================

Copyright (c) 2009-2010, Nickolay Platonov

All rights reserved.

Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:

* Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
* Neither the name of Nickolay Platonov nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. 

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 


*/
;
Role('JooseX.Namespace.Depended.Materialize.Eval', {
    
    requires : [ 'handleLoad' ],
    
    methods : {
        
        materialize : function (resourceBlob) {
            ;(function (){
                eval(resourceBlob)
            })()
        }
    }
})

/**

Name
====


JooseX.Namespace.Depended.Materialize.Eval - materializator, which treat the resource content as JavaScript code, and use `eval` function to evalute it 


SYNOPSIS
========
        
        //generally for consuming only
        
        Class("JooseX.Namespace.Depended.Resource.Custom", {
        
            isa : JooseX.Namespace.Depended.Resource,
            
            does : [ JooseX.Namespace.Depended.Materialize.Eval, ...]
            
            ...
        })


DESCRIPTION
===========

`JooseX.Namespace.Depended.Materialize.Eval` is a materializator role. It provide the implementation of `materialize` method. 


SEE ALSO
========

Authoring [JooseX.Namespace.Depended](../Authoring.html)

Abstract base resource class: [JooseX.Namespace.Depended.Resource](../Resource.html)


General documentation for Joose: <http://openjsan.org/go/?l=Joose>


AUTHORS
=======

Nickolay Platonov <nplatonov@cpan.org>



COPYRIGHT AND LICENSE
=====================

Copyright (c) 2009-2010, Nickolay Platonov

All rights reserved.

Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:

* Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
* Neither the name of Nickolay Platonov nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. 

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 


*/;
Class('JooseX.Namespace.Depended.Resource.JooseClass', {
    
    isa : JooseX.Namespace.Depended.Resource,
    
    // NOTE : we don't add the default materialization and transport roles here - they'll be added
    // in one of the Bootstrap/*.js files
    
    methods : {
        
        getUrls : function () {
            var urls = []
            var className = this.token.split('.')
            
            Joose.A.each(JooseX.Namespace.Depended.Manager.my.INC, function (libroot) {
                libroot = libroot.replace(/\/$/, '')
                urls.push( [ libroot ].concat(className).join('/') + '.js' + (JooseX.Namespace.Depended.Manager.my.disableCaching ? '?disableCaching=' + new Date().getTime() : '') )
            })
            
            return urls
        }
        
    },
    
    
    override : {
        
        addDescriptor : function (descriptor) {
            if (typeof descriptor == 'object' && !descriptor.token) 
                Joose.O.eachOwn(descriptor, function (version, name) {
                    this.addDescriptor({
                        type : 'joose',
                        token : name,
                        version : version
                    })
                }, this)
            else
                this.SUPER(descriptor)
        }

    }

})

JooseX.Namespace.Depended.Manager.my.registerResourceClass('joose', JooseX.Namespace.Depended.Resource.JooseClass)
;
Class('JooseX.Namespace.Depended.Resource.NonJoose', {
    
    isa : JooseX.Namespace.Depended.Resource.JooseClass,
    
    
    have : {
        presence : null
    },
    
    
    after: {
        
        initialize: function () {
            var me = this
            
            if (!this.presence) this.presence = function () {
                return eval(me.token)
            }
        }
        
    },

    
    methods : {
        
        isLoaded : function () {
            var isPresent = false
            
            try {
                isPresent = this.presence()
            } catch (e) {
            }
            
            return isPresent || this.SUPER()
        },
        
        
        isReady : function () {
            return this.isLoaded()
        }
    }

})

JooseX.Namespace.Depended.Manager.my.registerResourceClass('nonjoose', JooseX.Namespace.Depended.Resource.NonJoose)
;
Role('JooseX.Namespace.Depended.Transport.NodeJS', {

    requires : [ 'handleLoad' ],
    
    override : {
        
        load: function (url, onsuccess, onerror) {
            var fs = require('fs')
            
            fs.readFile(url, function (err, data) {
                if (err) {
                    onerror(err)
                    
                    return
                }
                
                onsuccess(data, url)
            })            
        }
    }
})


/**

Name
====


JooseX.Namespace.Depended.Transport.Node - transport, which use the `fs.readFile()` call of NodeJS, to load the content of resource. 


SYNOPSIS
========
        
        //generally for consuming only
        
        Class("JooseX.Namespace.Depended.Resource.Custom", {
        
            isa : JooseX.Namespace.Depended.Resource,
            
            does : [ JooseX.Namespace.Depended.Transport.Node, ...]
            
            ...
        })


DESCRIPTION
===========

`JooseX.Namespace.Depended.Transport.Node` is a transport role. It provide the implementation of `load` method, 
which use the `fs.readFile()` call of NodeJS for resource loading. 



SEE ALSO
========

Authoring [JooseX.Namespace.Depended](../Authoring.html)

Abstract base resource class: [JooseX.Namespace.Depended.Resource](../Resource.html)


General documentation for Joose: <http://openjsan.org/go/?l=Joose>


AUTHORS
=======

Nickolay Platonov <nplatonov@cpan.org>



COPYRIGHT AND LICENSE
=====================

Copyright (c) 2009-2010, Nickolay Platonov

All rights reserved.

Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:

* Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
* Neither the name of Nickolay Platonov nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. 

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 


*/;
Role('JooseX.Namespace.Depended.Materialize.NodeJS', {
    
    requires : [ 'handleLoad' ],
    
    methods : {
        
        materialize : function (resourceBlob, url) {
            // global scope
            if (process.global == global)
                process.binding('evals').Script.runInThisContext(resourceBlob + '', url)
            else
                // running in Test.Run
                (function (){
                    eval(resourceBlob + '')
                })()
        }
    }
})

/**

Name
====


JooseX.Namespace.Depended.Materialize.NodeJS - materializator, which execute the code, using the `Script.runInThisContext` call of NodeJS. 


SYNOPSIS
========
        
        //generally for consuming only
        
        Class("JooseX.Namespace.Depended.Resource.Custom", {
        
            isa : JooseX.Namespace.Depended.Resource,
            
            does : [ JooseX.Namespace.Depended.Materialize.NodeJS, ...]
            
            ...
        })


DESCRIPTION
===========

`JooseX.Namespace.Depended.Materialize.NodeJS` is a materializator role. It provide the implementation of `materialize` method. 


SEE ALSO
========

Authoring [JooseX.Namespace.Depended](../Authoring.html)

Abstract base resource class: [JooseX.Namespace.Depended.Resource](../Resource.html)


General documentation for Joose: <http://openjsan.org/go/?l=Joose>


AUTHORS
=======

Nickolay Platonov <nplatonov@cpan.org>



COPYRIGHT AND LICENSE
=====================

Copyright (c) 2009-2010, Nickolay Platonov

All rights reserved.

Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:

* Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
* Neither the name of Nickolay Platonov nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. 

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 


*/;
Class('JooseX.Namespace.Depended.Resource.Require', {
    
    isa     : JooseX.Namespace.Depended.Resource.JooseClass,
    
    
    methods : {
        
        getUrls : function () {
            return [ this.token ]
        },
        
        
        load: function (url, onsuccess, onerror) {
            
            require.async(url, function (err) {
                if (err instanceof Error) 
                    onerror(err)
                else
                    onsuccess('', url)
            })
            
        },

        
        materialize : function () {
        }
        
    }

})

JooseX.Namespace.Depended.Manager.my.registerResourceClass('require', JooseX.Namespace.Depended.Resource.Require)
;
Role('JooseX.Namespace.Depended', {
    
    meta : Joose.Managed.Role,
    
    requires : [ 'prepareProperties' ],
    
    
    have : {
        containResources                    : [ 'use', 'meta', 'isa', 'does', 'trait', 'traits' ]
    },

    
    override: {
        
        prepareProperties : function (name, extend, defaultMeta, callback) {
            
            if (name && typeof name != 'string') {
                extend = name
                name = null
            }
            
            extend = extend || {}
            
            var summaredDeps = []
            
            var extendMy = extend.my
            
            //gathering all the related resourses from various builders
            //also gathering resourses of 'my'
            Joose.A.each(this.containResources, function (propName) {
                
                this.collectDependencies(extend[propName], summaredDeps, extend, propName)
                    
                if (extendMy && extendMy[propName]) this.collectDependencies(extendMy[propName], summaredDeps, extendMy, propName)
            }, this)
            

            //and from externally collected additional resources 
            this.alsoDependsFrom(extend, summaredDeps)
            
            
            var resource = JooseX.Namespace.Depended.Manager.my.getResource({
                type : 'joose',
                token : name
            })
            
            
            if (extend.VERSION) {
                resource.setVersion(extend.VERSION)
                
                delete extend.VERSION
            }
            
            //BEGIN executes right after the all dependencies are loaded, but before this module becomes ready (before body())
            //this allows to manually control the "ready-ness" of module (custom pre-processing)
            //BEGIN receives the function (callback), which should be called at the end of custom processing 
            if (extend.BEGIN) {
                resource.setOnBeforeReady(extend.BEGIN)
                
                delete extend.BEGIN
            }
            
            Joose.A.each(summaredDeps, function (descriptor) {
                resource.addDescriptor(descriptor)
            })
            
            
            //skip constructing for classes w/o dependencies 
            if (Joose.O.isEmpty(resource.dependencies)) {
                this.inlineDependencies(extend)
                
                var res = this.SUPER(name, extend, defaultMeta, callback)
                
                //this will allow to classes which don't have dependencies to be ready synchronously
                resource.checkReady()
                
                return res
            } else {
                // defer the dependencies loading, because they actually could be provided later in the same bundle file
                // this, however, affect performance, so bundles should be created in the dependencies-ordered way
                setTimeout(function () {
                    resource.handleDependencies()
                }, 0)
                
                // debugging warning
                if (typeof ENABLE_DEFERRED_DEPS_WARNING != 'undefined')
                    console.log('Deferred deps handling for class [' + name + '], deps = [' + JSON.stringify(resource.dependencies) + ']')
            }
            

            
            var me = this
        
            //unshift is critical for correct order of readyListerens processing!
            //constructing is delaying until resource will become ready 
            resource.readyListeners.unshift(function () {
                me.inlineDependencies(extend)
                
                me.prepareProperties(name, extend, defaultMeta, callback)
            })
            
            return this.create(name, Joose.Namespace.Keeper, {})
        },
        
        
        create : function () {
            var meta = this.SUPERARG(arguments).meta
            
            meta.resource = meta.resource || JooseX.Namespace.Depended.Manager.my.getMyResource('joose', meta.name, meta.c)
            
            return meta.c
        }
    },
    //eof override
    
    
    methods : {
        
        alsoDependsFrom : function (extend, summaredDeps) {
        },
        
        
        collectDependencies : function (from, to, extend, propName) {
            Joose.A.each(Joose.O.wantArray(from), function (descriptor) {
                if (descriptor && typeof descriptor != 'function') to.push(descriptor)
            })
        },
        
        
        inlineDependencies : function (extend) {
            this.inlineDeps(extend)
            
            var extendMy = extend.my
            
            if (extendMy) this.inlineDeps(extendMy)
        },
        
        
        inlineDeps : function (extend) {
            delete extend.use
            
            Joose.A.each(this.containResources, function (propName) {
                
                if (extend[propName]) {
                
                    var descriptors = []
                    
                    Joose.A.each(Joose.O.wantArray(extend[propName]), function (descriptor, index) {
                        
                        if (typeof descriptor == 'function')
                            descriptors.push(descriptor)
                        else
                            if (typeof descriptor == 'object')
                                if (descriptor.token)
                                    descriptors.push(eval(descriptor.token)) 
                                else
                                    Joose.O.each(descriptor, function (version, name) { 
                                        descriptors.push(eval(name)) 
                                    })
                            else 
                                if (typeof descriptor == 'string')
                                    descriptors.push(eval(descriptor))
                                else 
                                    throw "Wrong dependency descriptor format: " + descriptor
                        
                    })
                    
                    if (propName != 'isa' && propName != 'meta')
                        extend[propName] = descriptors
                    else
                        if (descriptors.length > 1) 
                            throw "Cant specify several super- or meta- classes"
                        else
                            extend[propName] = descriptors[0]
                        
                }
            })
        }
    }
})


Joose.Namespace.Manager.meta.extend({
    does : JooseX.Namespace.Depended
})


Joose.Namespace.Keeper.meta.extend({
    
    after: {
        
        copyNamespaceState: function (targetClass) {
            targetClass.meta.resource = this.resource
        }
    }
});
JooseX.Namespace.Depended.Resource.JooseClass.meta.extend({
    
    does : [ JooseX.Namespace.Depended.Transport.NodeJS, JooseX.Namespace.Depended.Materialize.NodeJS ]
})


JooseX.Namespace.Depended.Manager.my.disableCaching = false

Joose.Namespace.Manager.my.containResources.unshift('require')



JooseX.Namespace.Depended.meta.extend({
    
    override : {
        
        collectDependencies : function (from, to, extend, propName) {
            if (propName != 'require') return this.SUPERARG(arguments)
            
            if (!from) return
            
            Joose.A.each(Joose.O.wantArray(from), function (url) {
                to.push({
                    type    : 'require',
                    token   : url
                })
            })
            
            delete extend.require
        }
    }
})
;
;
;Class('JooseX.CPS.Continuation', {
    
    has : {
        parent          : null,
        previous        : null,
        
        statements      : Joose.I.Array,
        type            : 'Sequential',
        
        entered         : false,
        leaved          : false,
        
        defaultScope    : null,
        
        nextFunc        : null,
        
        catchFunc       : null,
        catchScope      : null,
        
        finallyFunc     : null,
        finallyScope    : null
    },
    
    
    methods : {
        
        deriveChild : function (config) {
            config              = config || {}
            
            config.parent       = this
            
            return new this.constructor(config)
        },
        
        
        deriveSibling : function (config) {
            config              = config || {}
            
            config.parent       = this.parent
            config.previous     = this
            
            return new this.constructor(config)
        },
        
        
        getNextFunc : function () {
            return this.nextFunc || this.parent && this.parent.getNextFunc()
        },
        
        
        getScope    : function () {
            var prev    = this.previous || this.parent
            
            return this.defaultScope || prev && prev.getScope() || Joose.top
        },
        
        
        entry : function () {
            if (this.entered)   throw "Can't re-enter the continuation + [" + this + "]"
            
            this.entered    = true
            
            this[ 'runCore' + this.type ].apply(this, arguments)
        },
        
        
        runCoreSequential : function () {
            var me          = this
            var statements  = this.statements
            
            if (statements.length) {
                var statement = statements.shift()
                
                var child = this.deriveChild({
                    defaultScope    : statement.scope,
                    
                    nextFunc        : function () {
                        me.runCoreSequential.apply(me, arguments)
                    }
                })
                
                this.run(statement.func, statement.scope, statement.args || arguments, child)
                
            } else
                this.leave.apply(this, arguments)
        },
        
        
        runCoreParallel : function () {
            var args        = arguments
            
            var statements  = this.statements
            var length      = statements.length
            var me          = this
            
            var results     = []
            var counter     = 0
            
            Joose.A.each(statements, function (statement, index) {
                
                me.deriveChild().TRY(statement.func, statement.scope, statement.args || args).THEN(function () {
                    counter++
                    results[index] = arguments
                    
                    this.CONT.CONTINUE()
                }).CATCH(function () {
                    counter++
                    results[index] = arguments
                    
                    this.CONT.CONTINUE()
                }).FINALLY(function () {
                        
                    if (counter == length) me.leave(results)
                }).NOW()
            })
        },
        
        
        
        run : function (func, scope, args, statement) {
            var glob                = Joose.top
            var prevScopeStatement  = scope.CONT
            var prevGlobStatement   = glob.__GLOBAL_CNT__

            glob.__GLOBAL_CNT__ = scope.CONT = statement
            
            try {
                if (func.apply(scope, args) !== undefined) throw "ERROR: Value returned from continued function (use `CONTINUE(value)` instead)" 
            } catch (e) {
                
                // if statement is already leaved, then we are just propagating the exception from the further statements
                if (statement.leaved) throw e
                
                statement.THROW(e)
            } finally {
                scope.CONT              = prevScopeStatement
                glob.__GLOBAL_CNT__     = prevGlobStatement
            }
        },
        

        
        leave : function () {
            var args            = arguments
            
            var finallyFunc     = this.finallyFunc
            
            
            if (finallyFunc) {
                delete this.finallyFunc
                
                var finallyScope     = this.finallyScope
                var me               = this
                
                var finallyStatement = this.deriveChild({
                    defaultScope    : finallyScope,
                    
                    nextFunc        : function () {
                        me.leave.apply(me, args)
                    }
                })
                
                this.run(finallyFunc, finallyScope, [], finallyStatement)
                
                return
            }
            
            
            if (this.leaved)   throw "Can't re-leave the continuation + [" + this + "]"

            this.leaved         = true
            
            var nextFunc        = this.getNextFunc()
            if (nextFunc) nextFunc.apply(Joose.top, args)
        },
        
        
        THROW : function (exception) {
            var args        = arguments
            
            var catchFunc   = this.catchFunc
            
            if (catchFunc) {
                delete this.catchFunc
                
                var catchScope      = this.catchScope
                var me              = this
                
                var catchStatement = this.deriveChild({
                    defaultScope    : catchScope,
                    
                    nextFunc        : function () {
                        me.leave.apply(me, arguments)
                    }
                })
                
                this.run(catchFunc, catchScope, args, catchStatement)

                return
            } 
            
            var parent      = this.parent
            
            if (parent) {
                this.nextFunc = function () {
                    parent.THROW.apply(parent, args)
                }
                
                this.leave()
                
                return
            } 
            
            throw exception
        },
        
        
        CONTINUE : function () {
            return this.leave.apply(this, arguments)
        },
        
        
        RETURN : function () {
            if (this.parent) this.nextFunc = this.parent.getNextFunc()
            
            return this.leave.apply(this, arguments)
        },
        
        
        TRY : function (func, scope, args) {
            if (this.leaved)            throw "Can't call 'TRY' for [" + this + "] - its already leaved"
            if (!func)                  throw "Invalid parameters for 'TRY' in [" + this + "]"
            
            if (this.catchFunc || this.finallyFunc) return this.NEXT.apply(this, arguments)
            
            this.statements.push({
                func    : func,
                scope   : scope || this.getScope(),
                args    : args
            })
            
            this.defaultScope = scope || this.defaultScope
                
            return this
        },
        
        
        THEN : function () {
            if (this.type == 'Parallel') return this.NEXT.apply(this, arguments)
            
            return this.TRY.apply(this, arguments)
        },
        
        
        CATCH : function (func, scope) {
            if (this.leaved)            throw "Can't call 'CATCH' for [" + this + "] - its already leaved"
            if (!func)                  throw "Invalid parameters for 'CATCH' in [" + this + "]"
            if (this.catchFunc)         throw "Can't redefine 'CATCH' for [" + this + "]"
            
            this.catchFunc      = func
            this.catchScope     = scope || this.getScope()
            
            return this 
        },
        
        
        FINALLY : function (func, scope) {
            if (this.leaved)            throw "Can't call 'FINALLY' for [" + this + "] - its already leaved"
            if (!func)                  throw "Invalid parameters for 'FINALLY' in [" + this + "]"
            if (this.finallyFunc)       throw "Can't redefine 'FINALLY' for [" + this + "]"
            
            this.finallyFunc      = func
            this.finallyScope     = scope || this.getScope()
            
            return this
        },
        
        
        
        NEXT : function (func, scope, args) {
            if (this.leaved)            throw "Can't call 'NEXT' for [" + this + "] - its already leaved"
            
            var next = this.deriveSibling()
            
            this.nextFunc = function () {
                next.entry.apply(next, arguments)
            }
            
            return next.TRY(func, scope, args)
        },
        
        
        AND : function () {
            this.type = 'Parallel'
            
            return this.TRY.apply(this, arguments)
        },
        
        
        NOW : function () {
            var root = this.getNearestNotEntered()
            
            if (!root)                  throw "Can't launch  [" + this + "]"
            
            root.entry.apply(root, arguments)
        },
        
        
        getNearestNotEntered : function () {
            if (this.entered) return null
            
            var prev = this.previous || this.parent 
            
            if (prev) {
                var root = prev.getNearestNotEntered()
                
                if (root) return root
            }
            
            return this
        },
        
        
        // Delegates
        getCONTINUE : function () {
            var me = this
            
            return function () {
                me.CONTINUE.apply(me, arguments)
            }
        },
        
        
        getRETURN : function () {
            var me = this
            
            return function () {
                me.RETURN.apply(me, arguments)
            }
        },
        
        
        getTHROW : function () {
            var me = this
            
            return function () {
                me.THROW.apply(me, arguments)
            }
        },
        
        
        // Synonyms
        and : function () {
            return this.AND.apply(this, arguments)
        },
        
        
        then : function () {
            return this.THEN.apply(this, arguments)
        },
        
        
        next : function () {
            return this.NEXT.apply(this, arguments)
        },
        
        
        now : function () {
            return this.NOW.apply(this, arguments)
        },

        
        except : function () {
            return this.CATCH.apply(this, arguments)
        },
        
        
        ensure : function () {
            return this.FINALLY.apply(this, arguments)
        }
        
    }
    //eof methods
})


TRY = function () {
    var continuation = new JooseX.CPS.Continuation()
    
    return continuation.TRY.apply(continuation, arguments)
}


/**

Name
====


JooseX.CPS.Continuation - A continuation class


SYNOPSIS
========

        TRY = function () {
            var continuation = new JooseX.CPS.Continuation()
            
            return continuation.TRY.apply(continuation, arguments)
        }


DESCRIPTION
===========

`JooseX.CPS.Continuation` implements a continuation - an underlaying basis for `JooseX.CPS` trait.


ISA
===

None.


DOES
====

None.


TRAITS
======

None.


ATTRIBUTES
==========

### parent

> `JooseX.CPS.Continuation parent`

> A parent for this continuation. Can be asked for default scope or for the [nextFunc]


### previous

> `JooseX.CPS.Continuation previous`

> A previous continuation for this continuation. Can be asked for default scope or for the [nextFunc]


### statements

> `Array statements`

> An array of statements. Each statement is an object like : 
    
            {
                func    : ... , // function to execute
                scope   : ... , // scope into which execute the function
                args    : ...   // arguments for function 
            }


### type

> `String type`

> The type of this continuation. Can be 'Sequential' or 'Parallel's


### entered

> `Boolean entered`

> The sign whether this continuation was already entered - i.e. activated.


### leaved

> `Boolean leaved`

> The sign whether this continuation was already leaved - i.e. the `CONTINUE` or `THROW` method were called.


### defaultScope

> `Object defaultScope`

> The default scope which will be supplied to the statements if not provided explicitly. Once passed to `TRY`, propagates to the further statements. 


### nextFunc

> `Function nextFunc`

> If present, this function will be called, when leaving this continuation. Will be called in the global scope, with the arguments from the method, initated the leave. 


### catchFunc/finallyFunc

> `Function catchFunc/finallyFunc`

> The functions for `CATCH/FINALLY` statements accordingly. 


### catchScope/finallyScope

> `Object catchScope/finallyScope`

> The scopes for `CATCH/FINALLY` statements accordingly.



METHODS
=======

### TRY

> `JooseX.CPS.Continuation TRY(Function func, Object scope?, Array args?)`

> Add a statement to the current continuation. If continuation already contains `CATCH` or `FINALLY` statements - then delegate to `NEXT` and return a next continuation instance.
otherwise return current continuation.


### THEN

> `JooseX.CPS.Continuation THEN(Function func, Object scope?, Array args?)`

> Alias for `TRY` with a single exception. If the type of the continuation is `Parallel` then delegate to `NEXT` and return a next continuation instance.

> Has a lower-case synonym : 'then'


### CATCH

> `JooseX.CPS.Continuation CATCH(Function func, Object scope?)`

> Add a `CATCH` statement to the current continuation. 

> Has a lower-case synonym : 'except'


### FINALLY

> `JooseX.CPS.Continuation FINALLY(Function func, Object scope?)`

> Add a `FINALLY` statement to the current continuation. 

> Has a lower-case synonym : 'ensure'


### NEXT

> `JooseX.CPS.Continuation NEXT(Function func, Object scope?, Array args?)`

> Derive a sibling continuation, chaining it after itself. Return newly created continuation.

> Has a lower-case synonym : 'next'


### AND

> `JooseX.CPS.Continuation AND(Function func, Object scope?, Array args?)`

> Alias for `TRY` that also switch a type of the continuation to `Parallel`.

> Has a lower-case synonym : 'and'


### NOW

> `JooseX.CPS.Continuation NOW()`

> Activates current continuation graph by looking the 1st not yet entered continuation. After finding it, delegate to its `entry` method with the passed arguments.

> Has a lower-case synonym : 'now'


### getCONTINUE

> `Function getCONTINUE()`

> Return a function, binded to the `CONTINUE` method of itself.


### getRETURN

> `Function getRETURN()`

> Return a function, binded to the `RETURN` method of itself.


### getTHROW

> `Function getTHROW()`

> Return a function, binded to the `THROW` method of itself.


GETTING HELP
============

This extension is supported via github issues tracker: <http://github.com/SamuraiJack/JooseX-CPS/issues>

For general Joose questions you can also visit the [#joose](http://webchat.freenode.net/?randomnick=1&channels=joose&prompt=1) on irc.freenode.org, or the forum at <http://joose.it/forum>



SEE ALSO
========

[Main documentation page](../CPS.html)

General documentation for Joose: <http://openjsan.org/go/?l=Joose>



AUTHORS
=======

Nickolay Platonov [nplatonov@cpan.org](mailto:nplatonov@cpan.org)



COPYRIGHT AND LICENSE
=====================

Copyright (c) 2009, Nickolay Platonov

All rights reserved.

Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:

* Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
* Neither the name of Nickolay Platonov nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. 

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 


*/
;
Class('JooseX.CPS.MethodModifier.Override', {
    
    meta : Joose.Meta.Class,
    
    isa : Joose.Managed.Property.MethodModifier.Override,
    
    use : 'JooseX.CPS.Continuation',
    
    
    methods : {
    
        prepareWrapper : function (params) {
            
            var overriden = this.SUPER(params)
            
            var continued = function () {
                
                var cont = Joose.top.__GLOBAL_CNT__ || new JooseX.CPS.Continuation()
                
                return cont.TRY(overriden, this, arguments)
            }
            
            continued.IS_CONTINUED = true
            
            return continued
        }
    }
})
;
Class('JooseX.CPS.MethodModifier.After', {
    
    meta : Joose.Meta.Class,
    
    isa : Joose.Managed.Property.MethodModifier,
    
    use : 'JooseX.CPS.Continuation',
    
    
    methods : {
        
        prepareWrapper : function (params) {
            
            var name            = params.name
            var modifier        = params.modifier
            var isOwn           = params.isOwn
            var original        = params.target.prototype[name]
            var superProto      = params.superProto
            var originalCall    = params.originalCall
            
            
            var continuedOriginal = function () {
                var isContinued     = isOwn ? original.IS_CONTINUED : superProto[name].IS_CONTINUED
                
                if (isContinued) 
                    originalCall.apply(this, arguments).NOW()
                else
                    this.CONTINUE(originalCall.apply(this, arguments))
            }
            
            
            var continued = function () {
                
                var cont = Joose.top.__GLOBAL_CNT__ || new JooseX.CPS.Continuation()
                
                var args = arguments
                
                return cont.TRY(continuedOriginal, this, arguments).THEN(function () {
                    
                    var res = arguments
                    
                    this.CONT.TRY(modifier, this, args).THEN(function () {
                        
                        this.CONTINUE.apply(this, res)
                    }).NOW()
                    
                }, this)
            }
            
            continued.IS_CONTINUED = true
            
            return continued
        }
    }
});
Class('JooseX.CPS.MethodModifier.Before', {
    
    meta : Joose.Meta.Class,
    
    isa : Joose.Managed.Property.MethodModifier,
    
    use : 'JooseX.CPS.Continuation',
    
    
    methods : {
        
        prepareWrapper : function (params) {
            
            var name            = params.name
            var modifier        = params.modifier
            var isOwn           = params.isOwn
            var original        = params.target.prototype[name]
            var superProto      = params.superProto
            var originalCall    = params.originalCall
            
            
            var then = function () {
                var isContinued     = isOwn ? original.IS_CONTINUED : superProto[name].IS_CONTINUED
                
                if (isContinued) 
                    originalCall.apply(this, arguments).NOW()
                else
                    this.CONTINUE(originalCall.apply(this, arguments))
            }
            
            
            var continued = function () {
                
                var cont = Joose.top.__GLOBAL_CNT__ || new JooseX.CPS.Continuation()
                
                return cont.TRY(function () {
                    
                    this.CONT.TRY(modifier, this, arguments).NOW()
                    
                }, this, arguments).THEN(then, this, arguments)
            }
            
            continued.IS_CONTINUED = true
            
            return continued
        }
    }
});
Class('JooseX.CPS.MethodModifier.Put', {
    
    isa : 'JooseX.CPS.MethodModifier.Override',
    
    
    methods : {
        
        prepareWrapper : function (params) {
            
            if (params.isOwn) throw "Method [" + params.name + "] is applying over something [" + params.originalCall + "] in class [" + params.target + "]"
            
            return this.SUPER(params)
        }
    }
});
Class('JooseX.CPS.Builder', {
    
    meta : Joose.Meta.Class,
    
    use : [
        'JooseX.CPS.MethodModifier.Put',
        'JooseX.CPS.MethodModifier.After',
        'JooseX.CPS.MethodModifier.Before',
        'JooseX.CPS.MethodModifier.Override'
    ],
    
    isa : Joose.Managed.Builder,
    
    
    methods : {
        
        methods : function (meta, info) {
            var methods = meta.stem.properties.methods
            
            Joose.O.eachOwn(info, function (value, name) {
                methods.addProperty(name, {
                    meta : JooseX.CPS.MethodModifier.Put,
                    init : value
                })
            })
        },
        
    
        after : function (meta, info) {
            Joose.O.each(info, function (value, name) {
                meta.addMethodModifier(name, value, JooseX.CPS.MethodModifier.After)
            }, this)
        },
        
        
        before : function (meta, info) {
            Joose.O.each(info, function (value, name) {
                meta.addMethodModifier(name, value, JooseX.CPS.MethodModifier.Before)
            }, this)
        },
        
        
        override : function (meta, info) {
            Joose.O.each(info, function (value, name) {
                meta.addMethodModifier(name, value, JooseX.CPS.MethodModifier.Override)
            }, this)
        },
        
        
        have : function () {
            throw "'have' builder is not supported in the 'continued' section"
        },
        
        
        havenot : function () {
            throw "'havenot' builder is not supported in the 'continued' section"
        },
        
    
        around : function () {
            throw "'around' builder is not supported in the 'continued' section"
        },
        
        
        augment : function () {
            throw "'augment' builder is not supported in the 'continued' section"
        },
        
        
        does : function () {
            throw "'does' builder is not supported in the 'continued' section"
        },
        
    
        doesnot : function () {
            throw "'doesnot' builder is not supported in the 'continued' section"
        }
    }
})

;
Role('JooseX.CPS.ControlFlow', {
    
    use : [ 'JooseX.CPS.Continuation' ],
    
    has : {
        CONT            : null,
        RESULT          : null,
        RESULTS         : null
    },
    
    
    methods : {
        
        TRY : function (func, scope, args) {
            return this.CONT.TRY(func, scope || this, args)
        },
        
        
        AND : function (func, scope, args) {
            return this.CONT.AND(func, scope || this, args)
        },
        
        
        THEN : function (func, scope, args) {
            return this.CONT.THEN(func, scope || this, args)
        },
        
        
        NEXT : function (func, scope, args) {
            return this.CONT.NEXT(func, scope || this, args)
        },
        
        
        NOW : function () {
            var cont = this.CONT
            
            return cont.NOW.apply(cont, arguments)
        },
        
        
        CONTINUE : function () {
            var cont = this.CONT
            
            cont.CONTINUE.apply(cont, arguments)
        },
        
        
        RETURN : function () {
            var cont = this.CONT
            
            cont.RETURN.apply(cont, arguments)
        },
        
        
        THROW : function () {
            var cont = this.CONT
            
            cont.THROW.apply(cont, arguments)
        },
        
        
        getCONTINUE : function () {
            return this.CONT.getCONTINUE()
        },
        
        
        getRETURN : function () {
            return this.CONT.getRETURN()
        },
        
        
        getTHROW : function () {
            return this.CONT.getTHROW()
        }
        
//        ,
//        detachScope : function () {
//            this.CONT = new JooseX.CPS.Continuation()
//            
//            return this
//        }
        
    }
    //eof methods

});
Role('JooseX.CPS', {
    
    use : [ 'JooseX.CPS.Builder', 'JooseX.CPS.ControlFlow' ], 

    
    has : {
        continuedBuilder : null
    },
    
    
    after : {
        
        processStem : function () {
            this.continuedBuilder = new JooseX.CPS.Builder({ targetMeta : this })
            
            this.addRole(JooseX.CPS.ControlFlow)
        }
    },
    
    
    builder : {
        
        methods : {
            
            continued : function (meta, info) {
                
                meta.continuedBuilder._extend(info)
            }
        }
    }
});
;
Class('Scope.Provider', {
    
    have : {
        scope       : null
    },
    
        
    methods : {
        
        setup : function (callback) {
            throw "Abstract method `setup` of Scope.Provider called"
        },
        
        
        cleanup : function () {
            throw "Abstract method `cleanup` of Scope.Provider called"
        },
        
        
        runCode : function (text, callback) {
            throw "Abstract method `runCode` of Scope.Provider called"
        },
        
        
        runScript : function (url, callback) {
            throw "Abstract method `runScript` of Scope.Provider called"
        }
    }
});
Role('Scope.Provider.Role.WithDOM', {
    
    requires    : [ 'getDocument' ],
    
    have : {
        parentWindow    : null
    },
    
    
    after : {
        
        initialize : function () {
            this.parentWindow = this.parentWindow || window
        }
    },
    
        
    methods : {
        
        runCode : function (text, callback) {
            this.getDocument().body.appendChild(this.createScriptTag(text))
            
            callback()
        },
        
        
        runScript : function (url, callback) {
            this.getDocument().body.appendChild(this.createScriptTag(null, url, callback))
        },
        
        
        createScriptTag : function (text, url, callback) {
            var node = this.getDocument().createElement("script")
            
            node.setAttribute("type", "text/javascript")
            
            if (url) node.setAttribute("src", url)
            
            if (text) node.text = text
            
            if (callback) node.onload = node.onreadystatechange = function() {
                if (!node.readyState || node.readyState == "loaded" || node.readyState == "complete" || node.readyState == 4 && node.status == 200)
                    //surely for IE6..
                    setTimeout(callback, 1)
            }
            
            return node
        }        
    }
})


/**

Name
====

Scope.Provider.Role.WithDOM - role for scope provider, which uses `script` tag for running the code.


SYNOPSIS
========

        Class('Scope.Provider.IFrame', {
            
            isa     : Scope.Provider,
            
            does    : Scope.Provider.Role.WithDOM,
            
            ...
        })

DESCRIPTION
===========

`Scope.Provider.Role.WithDOM` requires the implementation of the `getDocument` method, which should return the
document into which the `script` tags will be created.

In return, this role provides the implementation of `runCode` and `runScript`.




GETTING HELP
============

This extension is supported via github issues tracker: <http://github.com/SamuraiJack/Scope-Provider/issues>

For general Joose questions you can also visit [#joose](http://webchat.freenode.net/?randomnick=1&channels=joose&prompt=1) 
on irc.freenode.org or the forum at: <http://joose.it/forum>
 


SEE ALSO
========

Web page of this module: <http://github.com/SamuraiJack/Scope-Provider/>

General documentation for Joose: <http://openjsan.org/go/?l=Joose>


BUGS
====

All complex software has bugs lurking in it, and this module is no exception.

Please report any bugs through the web interface at <http://github.com/SamuraiJack/Scope-Provider/issues>



AUTHORS
=======

Nickolay Platonov <nplatonov@cpan.org>





COPYRIGHT AND LICENSE
=====================

This software is Copyright (c) 2010 by Nickolay Platonov <nplatonov@cpan.org>.

This is free software, licensed under:

  The GNU Lesser General Public License, Version 3, June 2007

*/;
Class('Scope.Provider.IFrame', {
    
    isa     : Scope.Provider,
    
    does    : Scope.Provider.Role.WithDOM,
    
    
    have : {
        sourceURL       : '/jsan/Scope/Provider/static/stub.html',
        
        iframe          : null
    },
    

    methods : {
        
        getDocument : function () {
            return this.iframe.contentWindow.document
        },
        
        
        setup : function (callback) {
            var me      = this
            var doc     = this.parentWindow.document
            var iframe  = this.iframe = doc.createElement('iframe')
            
            var subCallback = function () {
                callback(me)
            }
                
            if (iframe.attachEvent) 
                iframe.attachEvent('onload', subCallback)
            else
                iframe.onload = subCallback
                
            iframe.src = this.sourceURL
                
            doc.body.appendChild(iframe)
                
            this.scope = iframe.contentWindow
        },
        
        
        cleanup : function () {
            var iframe      = this.iframe
            
            iframe.onload   = null
            
            this.parentWindow.document.body.removeChild(iframe)
        }
        
    }
})

/**

Name
====

Scope.Provider.IFrame - scope provider, which uses the iframe.


SYNOPSIS
========

        var provider = new Scope.Provider.IFrame()
        
        provider.setup(function () {
        
            if (provider.scope.SOME_GLOBAL == 'some_value') {
                ...
            }
            
            provider.runCode(text, callback)
            
            ...
            
            provider.runScript(url, callback)
            
            ...
            
            provider.cleanup()        
        })


DESCRIPTION
===========

`Scope.Provider.IFrame` is an implementation of the scope provider, which uses the iframe, 
to create a new scope.


ISA
===

[Scope.Provider](../Provider.html)


DOES
====

[Scope.Provider.Role.WithDOM](Role/WithDOM.html)



GETTING HELP
============

This extension is supported via github issues tracker: <http://github.com/SamuraiJack/Scope-Provider/issues>

For general Joose questions you can also visit [#joose](http://webchat.freenode.net/?randomnick=1&channels=joose&prompt=1) 
on irc.freenode.org or the forum at: <http://joose.it/forum>
 


SEE ALSO
========

Web page of this module: <http://github.com/SamuraiJack/Scope-Provider/>

General documentation for Joose: <http://openjsan.org/go/?l=Joose>


BUGS
====

All complex software has bugs lurking in it, and this module is no exception.

Please report any bugs through the web interface at <http://github.com/SamuraiJack/Scope-Provider/issues>



AUTHORS
=======

Nickolay Platonov <nplatonov@cpan.org>





COPYRIGHT AND LICENSE
=====================

This software is Copyright (c) 2010 by Nickolay Platonov <nplatonov@cpan.org>.

This is free software, licensed under:

  The GNU Lesser General Public License, Version 3, June 2007

*/;
Class('Scope.Provider.Window', {
    
    isa     : Scope.Provider,

    does    : Scope.Provider.Role.WithDOM,
    
    
    have : {
        popupWindow     : null,
        
        sourceURL       : '/jsan/Scope/Provider/static/stub-window.html'
    },
    

    methods : {
        
        setup : function (callback) {
            var me      = this
            var popup   = this.scope = this.popupWindow = this.parentWindow.open(src, '_blank')
            
            var subCallback = function () {
                callback(me)
            }
            
            if (!popup) {
                alert('Please enable popups for the host with this test suite running')
                throw 'Please enable popups for the host with this test suite running'
            }
            
            popup.onload = subCallback
        },
        
        
        getDocument : function () {
            return this.popup.document
        },
        
        
        cleanup : function () {
            this.popupWindow.onload = null
            this.popupWindow.close()
        }
    }
})

/**

Name
====

Scope.Provider.Window - scope provider, which uses the popup browser window.


SYNOPSIS
========

        var provider = new Scope.Provider.Window()
        
        provider.setup(function () {
        
            if (provider.scope.SOME_GLOBAL == 'some_value') {
                ...
            }
            
            provider.runCode(text, callback)
            
            ...
            
            provider.runScript(url, callback)
            
            ...
            
            provider.cleanup()        
        })


DESCRIPTION
===========

`Scope.Provider.Window` is an implementation of the scope provider, which uses the popup browser window, 
to create a new scope.


ISA
===

[Scope.Provider](../Provider.html)


DOES
====

[Scope.Provider.Role.WithDOM](Role/WithDOM.html)



GETTING HELP
============

This extension is supported via github issues tracker: <http://github.com/SamuraiJack/Scope-Provider/issues>

For general Joose questions you can also visit [#joose](http://webchat.freenode.net/?randomnick=1&channels=joose&prompt=1) 
on irc.freenode.org or the forum at: <http://joose.it/forum>
 


SEE ALSO
========

Web page of this module: <http://github.com/SamuraiJack/Scope-Provider/>

General documentation for Joose: <http://openjsan.org/go/?l=Joose>


BUGS
====

All complex software has bugs lurking in it, and this module is no exception.

Please report any bugs through the web interface at <http://github.com/SamuraiJack/Scope-Provider/issues>



AUTHORS
=======

Nickolay Platonov <nplatonov@cpan.org>





COPYRIGHT AND LICENSE
=====================

This software is Copyright (c) 2010 by Nickolay Platonov <nplatonov@cpan.org>.

This is free software, licensed under:

  The GNU Lesser General Public License, Version 3, June 2007

*/;
Class('Scope.Provider.NodeJS', {
    
    isa     : Scope.Provider,

    
    have : {
        runner          : null
    },
    

    methods : {
        
        setup : function (callback) {
            var Script  = process.binding('evals').Script
            
            var runnerSource = function (code) {
                return (function () { return eval(code) })()
            }
            
            var runner  = this.runner   = Script.runInNewContext('__RUNNER__ = ' + runnerSource.toString(), {})
            var scope   = this.scope    = runner('this')
            
            Joose.O.extend(scope, {
                process       : process,
                require       : require,
                
                global        : scope,
                
                setTimeout    : setTimeout,
                clearTimeout  : clearTimeout,
                setInterval   : setInterval,
                clearInterval : clearInterval
            })
            
            callback(this)
        },
        
        
        runCode : function (text, callback) {
            this.runner(text)
            
            callback()
        },
        
        
        runScript : function (url, callback) {
            var content = require('fs').readFileSync(url)
            
            this.runCode(content, callback)
        },
        
        
        cleanup : function () {
        }
    }
})


/**

Name
====

Scope.Provider.NodeJS - scope provider, which uses the `Script.runInNewContext` call of the NodeJS.


SYNOPSIS
========

        var provider = new Scope.Provider.NodeJS()
        
        provider.setup(function () {
        
            if (provider.scope.SOME_GLOBAL == 'some_value') {
                ...
            }
            
            provider.runCode(text, callback)
            
            ...
            
            provider.runScript(url, callback)
            
            ...
            
            provider.cleanup()        
        })


DESCRIPTION
===========

`Scope.Provider.NodeJS` is an implementation of the scope provider, 
which uses the `Script.runInNewContext` call of the NodeJS platform.


ISA
===

[Scope.Provider](../Provider.html)



GETTING HELP
============

This extension is supported via github issues tracker: <http://github.com/SamuraiJack/Scope-Provider/issues>

For general Joose questions you can also visit [#joose](http://webchat.freenode.net/?randomnick=1&channels=joose&prompt=1) 
on irc.freenode.org or the forum at: <http://joose.it/forum>
 


SEE ALSO
========

Web page of this module: <http://github.com/SamuraiJack/Scope-Provider/>

General documentation for Joose: <http://openjsan.org/go/?l=Joose>


BUGS
====

All complex software has bugs lurking in it, and this module is no exception.

Please report any bugs through the web interface at <http://github.com/SamuraiJack/Scope-Provider/issues>



AUTHORS
=======

Nickolay Platonov <nplatonov@cpan.org>





COPYRIGHT AND LICENSE
=====================

This software is Copyright (c) 2010 by Nickolay Platonov <nplatonov@cpan.org>.

This is free software, licensed under:

  The GNU Lesser General Public License, Version 3, June 2007

*/;
;
Class('Test.Run.Result', {
    
    have : {
        description : null
    }
        
})
//eof Test.Run.Result

;
Class('Test.Run.Result.Diagnostic', {
    
    isa : Test.Run.Result,
    

    methods : {
        
        toString : function () {
            return '# ' + this.description
        }
        
    }    
    
})

;
Class('Test.Run.Result.Assertion', {
    
    isa : Test.Run.Result,
    

    have : {
        passed      : null,
        
        annotation  : null,
        
        index       : null,
        
        isSkipped   : false,
        isTodo      : false
    },
    
    
    methods : {
        
        toString : function () {
            var text = (this.passed ? 'ok' : 'not ok') + ' ' + this.index + ' - ' + this.description
            
            if (this.annotation) text += '\n' + this.annotation
            
            return text
        }
        
    }
        
})

;
Role('Test.Run.Test.More', {
    
    methods : {
        
        like : function (string, regex, desc) {
            if (regex instanceof RegExp) 
                this.ok(string.match(regex), desc)
            else
                this.ok(string.indexOf(regex) != -1, desc)
        },
        
        
        unlike : function(string, regex, desc) {
            if (regex instanceof RegExp) 
                this.ok(!string.match(regex), desc)
            else
                this.ok(string.indexOf(regex) == -1, desc)
        },
        
        
        throwsOk : function () {
            this.throws_ok.apply(this, arguments)
        },
        
        
        livesOk : function () {
            this.lives_ok.apply(this, arguments)
        },
        
        
        throws_ok : function(func, expected, desc) {
            if (typeof func != 'function') throw 'throws_ok accepts a function as 1st argument'
            
            var e = this.topScope.__EXCEPTION_CATCHER__(func)
            
            if (e instanceof this.topScope.Error)
                //IE uses non-standard 'description' property for error msg
                e = e.message || e.description
            
            this.like('' + e, expected, desc + ' (got [' + e + '], expected [' + expected + '])')
        },
        
        
        lives_ok : function (func, desc) {
            if (typeof func != 'function') throw 'lives_ok accepts a function as 1st argument'
            
            var e = this.topScope.__EXCEPTION_CATCHER__(func)
            
            if (e) 
                this.fail(desc)
            else
                this.pass(desc)
        },
        
        
        isaOk : function (value, className, desc) {
            this.isa_ok(value, className, desc)
        },
        
        
        isa_ok : function (value, className, desc) {
            try {
                if (typeof className == 'string') className = eval(className)
            } catch (e) {
                this.fail("Exception [" + e + "] caught, when evaluting the class name [" + className + "]")
            }
            
            this.ok(value instanceof className, desc)
        }
        
    }
        
})
//eof Test.Run.Test.More
;
Class('Test.Run.Test', {
    
    does        : [ Test.Run.Test.More ],
    
    
    have : {
        url                 : null,
        
        assertPlanned       : null,
        assertCount         : 0,
        
        results             : null,
        
        run                 : null,
        
        harness             : null,
        
        failed              : false,
        failedException     : null,
        
        startDate           : null,
        endDate             : null,
        
        topScope            : null,
        
        transparentEx       : false,
        passThroughEx       : false,
        
        isDone              : false,
        
        timeoutsCount       : 0,
        timeoutIds          : null,
        finished            : false,
        
        callback            : null
    },
    
    
    after : {
    
        initialize : function (config) {
            if (Object.prototype.toString.call(this.run) != "[object Function]") throw "The body of test absent"
            
            this.results        = []
            this.timeoutIds     = {}
        }
        
    },
    
    
    methods : {
        
        toString : function() {
            return this.url
        },
        
        
        plan : function (value) {
            if (this.assertPlanned != null) throw "Test plan can't be changed"
            
            this.assertPlanned = value
        },
        
        
        addResult : function (result) {
            if (this.isDone || this.isFinished()) throw "Adding assertions after the test has been already done"
            
            if (result instanceof Test.Run.Result.Assertion) result.index = ++this.assertCount
            
            this.results.push(result)
            
            this.harness.onTestUpdate(this, result)
        },
        
        
        diag : function (desc) {
            this.addResult(new Test.Run.Result.Diagnostic({
                description : desc
            }))
        },
        
        
        pass : function (desc) {
            this.addResult(new Test.Run.Result.Assertion({
                passed      : true,
                
                description : desc
            }))
        },
        
        
        fail : function (desc) {
            this.addResult(new Test.Run.Result.Assertion({
                passed      : false,
                
                description : desc
            }))
        },
        
        
        eachAssertion : function (func, scope) {
            scope       = scope || this
            
            var index   = 0
            
            Joose.A.each(this.results, function (result) {
                
                if (result instanceof Test.Run.Result.Assertion) func.call(scope, result, index++)
            })
        },
        
        
        ok : function (condition, desc) {
            if (condition) 
                this.pass(desc)
            else 
                this.fail(desc)
        },
        
        
        notOk : function (condition, desc) {
            this.ok(!condition, desc)
        },
        
        
        is : function (got, expected, desc) {
            this.ok(got == expected, desc)
        },

        
        isnt : function (got, expected, desc) {
            this.ok(got != expected, desc)
        },
        
        
        beginAsync : function (time) {
            var me = this
            
            // in NodeJS `setTimeout` returns an object and not a simple ID, so we try hard to store that object under unique index
            var timeoutId = this.topScope.setTimeout(function () {
                me.endAsync(timeoutId)
            }, time || 1e4)
            
            var index = this.timeoutsCount++
            
            this.timeoutIds[ index ] = timeoutId
            
            return index
        },
        
        
        endAsync : function (index) {
            var counter = 0
            
            if (index == null) Joose.O.each(this.timeoutIds, function (timeoutId, indx) {
                index = indx
                if (counter++) throw "Calls to endAsync without argument should only be performed if you have single beginAsync statement" 
            })
            
            var timeoutId = this.timeoutIds[ index ]
            
            this.topScope.clearTimeout(timeoutId)
            delete this.timeoutIds[ index ]
            
            var me = this
            
            if (this.finished)
                // to allow potential call to `done` after `endAsync`
                setTimeout(function (){
                    me.finalize()
                }, 1)
        },
        
        
        clearTimeouts : function () {
            var me = this
            
            Joose.O.each(this.timeoutIds, function (value, id) {
                me.topScope.clearTimeout(value)
            })
            
            this.timeoutIds = {}
        },
        
        
        skipIf : function (condition, why, code, howMany) {
            howMany = howMany || 1
            
            if (condition) {
                
                for (var i = 1; i <= howMany; i++) this.addResult(new Test.Run.Result.Assertion({
                    passed      : true,
                    isSkipped   : true,
                    
                    description : 'SKIPPED: ' + why
                }))    
                
            } else
                code()
        },
        
        
        skip : function (why, code, howMany) {
            this.skipIf(true, why, code, howMany)
        },
        
        
        todo : function (why, code) {
            var todo  = new Test.Run.Test.Todo({
                parent  : this,
                run     : function () {}
            })
            
            this.topScope.__EXCEPTION_CATCHER__(function(){
                code(todo)
            })
        },
        
        
        start : function (callback) {
            this.callback   = callback
            this.startDate  = new Date()
            
            this.harness.onTestStart(this)
            
            var me      = this
            var run     = this.run
            
            if (this.transparentEx)
                run(me)
            else 
                var e = this.topScope.__EXCEPTION_CATCHER__(function(){
                    run(me)
                })
            
            if (e) {
                this.failed             = true
                this.failedException    = e
                
                this.harness.onTestFail(this, e)
                
                this.finalize(true)
                
                if (this.passThroughEx) throw e
                
                return
            } 
            
            this.finalize()
        },
        
        
        finalize : function (force) {
            this.finished = true
            
            if (force) this.clearTimeouts()
            
            if (!Joose.O.isEmpty(this.timeoutIds)) return
            
            this.endDate = new Date()
            
            this.harness.onTestEnd(this)
            
            this.callback && this.callback()
        },
        
        
        getSummaryMessage : function (lineBreaks) {
            var res = []
            
            var passCount       = this.getPassCount()
            var failCount       = this.getFailCount()
            var assertPlanned   = this.assertPlanned
            var total           = failCount + passCount
            
            res.push('Passed: ' + passCount)
            res.push('Failed: ' + failCount)
            
            if (!this.failed) {
                if (assertPlanned != null) {
                    if (total < assertPlanned) 
                        res.push('Looks like you planned ' + assertPlanned + ' tests, but ran only ' + total)
                        
                    if (total > assertPlanned) 
                        res.push('Looks like you planned ' + assertPlanned + ' tests, but ran ' +  (total - assertPlanned) + ' extra tests, ' + total + ' total.')
                    
                    if (total == assertPlanned && !failCount) res.push('All tests successfull')
                } else 
                    if (this.isDone && !failCount) res.push('All tests successfull')
                
            } else {
                res.push('Test suite threw an exception: ' + this.failedException)
            }
            
            return res.join(lineBreaks || '')
        },
        
        
        done : function () {
            this.isDone = true
        },
        
        
        getPassCount : function () {
            var passCount = 0
            
            this.eachAssertion(function (assertion) {
                if (assertion.passed && !assertion.isTodo) passCount++
            })
            
            return passCount
        },
        
        
        getFailCount : function () {
            var failCount = 0
            
            this.eachAssertion(function (assertion) {
                if (!assertion.passed && !assertion.isTodo) failCount++
            })
            
            return failCount
        },
        
        
        isPassed : function () {
            var passCount       = this.getPassCount()
            var failCount       = this.getFailCount()
            var assertPlanned   = this.assertPlanned
            
            return !this.failed && !failCount && (
                (assertPlanned != null && passCount == assertPlanned)
                    ||
                (assertPlanned == null) && this.isDone
            )
        },
        
        
        isFinished : function () {
            return this.endDate != null
        },
        
        
        getTimeLength : function () {
            return this.endDate - this.startDate
        }
        
    }
        
})
//eof Test.Run.Test


/**

Name
====


Test.Run.Test - Class, representing the individual test file


SYNOPSIS
========

        t.ok(1 == 1, 'Indeed')
        t.is(2 * 2, '4', 'Indeed')
        
        t.pass('Some assertion is correct')
        
        t.done()


DESCRIPTION
===========

`Test.Run.Test` is a base testing class in Test.Run hierarchy. Its not supposed to be created manually, instead, 
the harness will create it for you.


USAGE
=====

Below is the list of methods, intended for usage in the individual tests.


### plan

> `void plan(Number tests)`

> This method setups test's plan. When used, it should be called before any assertions were checked. 

> **tests** - a number of planned assertions in this test file.


### done

> `void done()`

> This method indicates that you've done testing and all assertions have been ran.
It should be called after any assertions were checked.  



### diag

> `void diag(String text)`

> This method output the diagnostic message. The actual presentation logic of the message is delegated to harness.

> **text** - The text of diagnostic message


### pass

> `void pass(String text)`

> This method add the passed assertion into results queue. 

> **text** - The description of the assertion


### fail

> `void fail(String text)`

> This method add the failed assertion into results queue. 

> **text** - The description of the assertion


### ok

> `void ok(Boolean condition, String text)`

> This assertion passes when the supplied `condition` evalutes to `true` and fails otherwise. 

> **condition** - The boolean condition, indicating wheter assertions is passed or failed

> **text** - The description of the assertion


### notOk

> `void notOk(Boolean condition, String text)`

> This is a reverse of `ok` (test passes when condition is false) 

        
### is

> `void is(Object value1, Object value2, String text)`

> This assertion passes when comparison of 1st and 2nd arguments shows that they are equal.
Comparison is performed with '==' operator

> **value1** - The 1st value for comparison

> **value2** - The 2nd value for comparison

> **text** - The description of the assertion


### isnt

> `void isnt(Object value1, Object value2, String text)`

> This method is a reverse of `is` (passes when the operands are different).


### beginAsync

> `Number beginAsync(Number? maxTime)`

> This method starts the "asynchronous frame". The test will not finished, until the frame will not be finished with [endAsync] call.
[endAsync] will be automatically called after specified `maxtime`.

> **maxTime** - the maximum time (in ms) to wait until explicitly finalize this async frame. Default time is 10000 ms.

> *return* - The timeoutId, which can be used in [endAsync] call



### endAsync

> `void endAsync(Number timeoutId)`

> This method finalize the "asynchronous frame" started with [beginAsync].

> **timeoutId** - The timeoutId, returned by [beginAsync] call


### like

> `void like(String str, String|RegExp regex, String text)`

> This assertion passes when the passed `str` (1st argument) matches to a regular expression `regex` (2nd argument)

> **str** - The string to test

> **regex** - The regex against which to test the string, can be also a plain string

> **text** - The description of the assertion


### unlike

> `void unlike(String str, String|RegExp regex, String text)`

> This method is the opposite of 'like', it adds failed assertion, when the string matches the passed regex.

> **str** - The string to test

> **regex** - The regex against which to test the string, can be also a plain string

> **text** - The description of the assertion



### throwsOk

> `void throwsOk(Function func, String|RegExp expected, String text)`

> This assertion is passed, when the `func` function throws the exception during executing, and the 
stringified exception passes the 'like' assertion (with 'expected' parameter). This method has a synonym: throws_ok

> **func** - The function which supposed to throw an exception

> **expected** - The regex against which to test the *stringified* exception, can be also a plain string

> **text** - The description of the assertion


### livesOk

> `void livesOk(Function func, String text)`

> This assertion passes, when the supplied `func` function doesn't throw the exception during execution. 
This method has a synonym: lives_ok

> **func** - The function which supposed to not throw an exception

> **text** - The description of the assertion


### isaOk

> `void isaOk(Object value, Function/String class, String text)`

> This assertion passes, when the supplied `value` is the instance of the `class`. The check is performed with
`instanceof` operator. The `class` parameter can be supplied as class constructor or as string, representing the class
name. In this case the `class` will eval'ed to receive the class constructor.

This method has a synonym: isa_ok

> **value** - The value to check for 'isa' relationship

> **class** - The class to check for 'isa' relationship with `value`

> **text** - The description of the assertion


### skipIf

> `void skipIf(Boolean condition, String why, Function code, Number? howMany)`

> This methods check the supplied `condition` and if its *true* then *do not* executes the supplied code.
Instead, it adds `howMany` pseudo-passed assertions to the test suite. If the condition is *false*, then
it just run the `code` function.

This method is useful for skipping parts of the test suite, for example if the functionality being tested
is not supported on the current platform.

> **condition** - The boolean condition, indicating whether to run or skip the `code`

> **why** - The reason for the skip

> **code** - A function, wrapping the assertions which needs to be skipped

> **howMany** - Optional. A number of pseudo-passed assertions to add, when skipping real ones. Defaults to 1.


### skip

> `void skip(String why, Function code, Number? howMany)`

> Unconditional `skipIf` (always skips the code).


### todo

> `void todo(String why, Function code)`

> With this method you can mark part of the test suite as "todo", assuming it most probably will fail,
but its still worth to try run them. 

>The supplied `code` function will be run, it will receive a new test instance as the 1st argument, 
which *must* be used for assertions checks (not the primary test instance, received from `StartTest`). 

>Assertions, failed inside the `code` block will be treated by harness normally.
Assertions, passed inside the `code` block will be treated by harness as bonus ones and highlighted. 

> **why** - The reason/description for the todo

> **code** - A function, wrapping the "todo" assertions. This function will receive a special test class instance
which should be used for assertions checks.



SEE ALSO
========

General documentation for Joose: <http://openjsan.org/go/?l=Joose>


BUGS
====

All complex software has bugs lurking in it, and this module is no exception.

Please report any bugs through the web interface at <http://github.com/SamuraiJack/test.run/issues>



AUTHORS
=======

Nickolay Platonov [nplatonov@cpan.org](mailto:nplatonov@cpan.org)



COPYRIGHT AND LICENSE
=====================

Copyright (c) 2010, Nickolay Platonov

All rights reserved.

Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:

* Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
* Neither the name of Nickolay Platonov nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. 

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 

        
[Test.Run.Result]: Result.html
[Test.Run.Harness]: Harness.html

*/;
Class('Test.Run.Test.Todo', {
    
    isa         : Test.Run.Test,
    
    
    have : {
        parent              : null
    },
    
    
    methods : {
        
        addResult : function (result) {
            if (result instanceof Test.Run.Result.Assertion) result.isTodo = true
            
            this.parent.addResult(result)
        },
        
        
        beginAsync : function (time) {
            return this.parent.beginAsync(time)
        },
        
        
        endAsync : function (index) {
            return this.parent.endAsync(index)
        }
        
    }
        
})
//eof Test.Run.Test
;
Class('Test.Run.Harness', {
    
    my : {
        
        have : {
            title               : null,
            
            testClass           : Test.Run.Test,
            
            tests               : null,
            testsByURL          : null,
            descriptorsByURL    : null,
            scopesByURL         : null,
            
            startArgs       : null,
            
            passThroughEx   : false,
            transparentEx   : false,
            
            scopeProvider   : null,
            runCore         : 'parallel', // or 'sequential'
            maxThreads      : 4,
            
            preload         : null,
            
            verbosity       : 0,
            keepResults     : false
        },
        
        
        after : {
            
            initialize : function () {
                this.testsByURL         = {}
                this.descriptorsByURL   = {}
                this.scopesByURL        = {}
                this.tests              = []
            }
        },
        
        
        methods : {
            
            onTestUpdate : function (test, result) {
            },
            
            
            onTestFail : function (test, exception) {
            },
            
            
            onTestStart : function (test) {
            },
            
            
            onTestEnd : function (test) {
            },
            
            
            onTestSuiteStart : function () {
            },
            
            
            onTestSuiteEnd : function () {
            },
            
            
            configure : function (config) {
                Joose.O.copy(config, this)
            },
            
            
            start : function () {
                var me = this
                
                this.startArgs = arguments
                
                var descriptors = []
                
                Joose.A.each(arguments, function (desc, index) {
                    desc = me.normalizeDescriptor(desc, index)
                    
                    descriptors.push(desc)
                    
                    me.descriptorsByURL[ desc.url ] = desc
                })
                
                this.onTestSuiteStart(descriptors)
                
                this.runTestsForDescriptors(descriptors, function () {
                    me.onTestSuiteEnd()
                })
            },
            
            
            runTestsForDescriptors : function (descriptors, callback) {
                var runCoreMethod = 'runCore' + Joose.S.uppercaseFirst(this.runCore)
                
                if (typeof this[ runCoreMethod ] != 'function') throw "Invalid `runCore` specified: [" + this.runCore + "]"
                
                this[ runCoreMethod ](descriptors, callback)
            },
            
            
            runCoreParallel : function (descriptors, callback) {
                var me              = this
                var processedNum    = 0
                var count           = descriptors.length
                
                if (!count) callback()
                
                var launch  = function (descriptors) {
                    var desc = descriptors.shift()
                    
                    if (!desc) return
                    
                    me.processURL(desc, desc.index, function () {
                        processedNum++
                        
                        if (processedNum == count) 
                            callback()
                        else
                            launch(descriptors)
                    })
                }
                
                for (var i = 1; i <= this.maxThreads; i++) launch(descriptors)
            },
            
            
            runCoreSequential : function (descriptors, callback) {
                if (descriptors.length) {
                    var desc = descriptors.shift()
                    
                    var me = this
                    
                    this.processURL(desc, desc.index, function () {
                        
                        me.runCoreSequential(descriptors, callback)
                    })
                    
                } else
                    callback()
            },
            
            
            setupScope : function (desc, callback) {
                var scopeProvider       = desc.target
                
                var scopeProvideClass   = eval(scopeProvider)
                
                new scopeProvideClass().setup(callback)
            },
            
            
            cleanupScopeForURL : function (url) {
                var scopeProvider = this.scopesByURL[ url ]
                
                if (scopeProvider) {
                    scopeProvider.cleanup()
                    
                    delete this.scopesByURL[ url ]
                }
            },
            
            
            prepareScope : function (scopeProvider, desc, callback) {
                var me = this
                
                scopeProvider.runCode(
                    'StartTest = function () { __START_TEST__ = arguments };' +
                    '__EXCEPTION_CATCHER__ = function (func) { var ex; try { func() } catch (e) { ex = e; }; return ex; };',
                    
                    function () {
                        var preload         = desc.preload || me.preload || []
                        
                        preload             = preload.concat(desc.alsoPreload || [])
                        
                        me.preloadScripts(scopeProvider, preload, callback)
                    }
                )
            },
            
            
            preloadScripts : function (scopeProvider, scripts, callback) {
                var me  = this
                
                if (scripts.length) {
                    var script = scripts.shift()
                    
                    if (typeof script == 'object')
                        scopeProvider.runCode(script.text, function () {
                            me.preloadScripts(scopeProvider, scripts, callback)
                        })
                    else
                        scopeProvider.runScript(this.resolveURL(script), function () {
                            me.preloadScripts(scopeProvider, scripts, callback)
                        })
                } else
                    callback()
            },
            
            
            normalizeDescriptor : function (desc, index) {
                if (typeof desc == 'string') return {
                    url     : desc,
                    target  : this.scopeProvider,
                    index   : index
                }
                
                if (desc.target) {
                    var match 
                    
                    if (match = /^=(.+)/.exec(desc.target))
                        desc.target = match[ 1 ]
                    else 
                        desc.target = desc.target.replace(/^(Scope.Provder.)?/, 'Scope.Provider.')
                }
                
                desc.index = index
                
                return desc
            },
            
            
            resolveURL : function (url) {
                return url
            },
            
            
            processURL : function (desc, index, callback) {
                var me      = this
                var url     = desc.url
                
                this.cleanupScopeForURL(url)
                
                this.setupScope(desc, function (scopeProvider) {
                    
                    me.scopesByURL[ url ] = scopeProvider
                    
                    me.prepareScope(scopeProvider, desc,  function () {
                        
                        scopeProvider.runScript(me.resolveURL(url), function () {
                            
                            var scope           = scopeProvider.scope
                            var startTestArgs   = scope.__START_TEST__
                            var run             = startTestArgs[0]
                            var testClass       = startTestArgs[1]
                            
                            var test = new (testClass || me.testClass)({
                                url             : url,
                                harness         : me,
                                
                                run             : run,
                                topScope        : scope,
                                
                                passThroughEx   : me.passThroughEx,
                                transparentEx   : me.transparentEx
                            })
                            
                            me.addTest(test, index)
                            
                            test.start(function () {
                                if (!me.keepResults) me.cleanupScopeForURL(url)
                                
                                callback && callback()
                            })
                        })
                    })
                })   
            },
            
            
            addTest : function (test, index) {
                this.tests[ index ] = test
                
                this.testsByURL[ test.url ] = test
            },
            
            
            getTestByURL : function (url) {
                return this.testsByURL[url]
            },
            
            
            getTestAt : function (index) {
                return this.tests[ index ]
            },
            
            
            reRunTest : function (test, callback) {
                this.reRunTests([ test ], callback)
            },
            
            
            reRunTests : function (tests, callback) {
                var descriptors = []
                
                Joose.A.each(tests, function (test) {
                    descriptors.push( this.descriptorsByURL[ test.url ])
                }, this)
                
                this.runTestsForDescriptors(descriptors, callback)
            },
            
            
            reRunSuite : function () {
                this.start.apply(this, this.startArgs)
            },
            
            
            isPassed : function () {
                var res = true
                
                Joose.O.each(this.testsByURL, function (test) {
                    if (!test.isPassed()) res = false
                })
                
                return res
            },
            
            
            isRunning : function () {
                var res = false
                
                Joose.O.each(this.testsByURL, function (test) {
                    if (!test.isFinished()) res = true
                })
                
                return res
            }
            
        }
        
    }
    //eof my
})
//eof Test.Run.Harness



/**

Name
====


Test.Run.Harness - Abstract base class for test harness


SYNOPSIS
========

            Test.Run.Harness.Browser.Multi.configure({
                title : 'Module.Stub Test Suite',
                
                passThroughEx : true,
                
                preload : [
                    '/jsan/Task/Joose/Core.js',
                    "/jsan/JooseX/SimpleRequest.js",
                    '/jsan/Task/JooseX/Namespace/Depended/Web.js',
                    {
                        text : "JooseX.Namespace.Depended.Manager.my.INC = " + Ext.encode(INC)
                    }
                ]
            })
            
            
            Test.Run.Harness.Browser.Multi.start(
                '010_sanity.t.js',
                '020_basics.t.js'
            )
        

DESCRIPTION
===========

`Test.Run.Harness` is an abstract base harness class in Test.Run hierarchy. This class provides no UI, 
you should use one of it subclasses, for example [Test.Run.Harness.Browser.ExtJS]


USAGE
=====

Methods
-------

### configure

> `void configure(Object options)`

> This method configure the harness instance. It just copies the passed configuration option into static instance. 

> **options** - configuration options (values of attributes for this class, see below for details)


### start

> `void start(String url1, String url2, ...)`

> This method starts a whole test suite 

> **url1, url2, ...** - the variable number of test files urls


Configuration options
---------------------

### title

> `String title`

> The title of the test suite


### passThroughEx

> `Boolean passThroughEx`

> The sign whether the each tests in suite should re-throw any exceptions caught (sometimes useful 
for debugging with FireBug). Defaults to 'false'.

### transparentEx

> `Boolean transparentEx`

> The sign whether the each tests in suite shouldn't catch any exceptions at all. Exceptions will be thrown "as is".
This is also useful for debugging. Defaults to 'false'


### preload

> `Array preload`

> The array which contains the information about which files should be preloaded into each test's scope.
The folloing rules applies during processing of the array:

>1. If the string entry represent a class name (for example : `Test.Run.Test`) it is converting to the url, 
like "../lib/Test/Run/Test.js"

>2. All string entries starting with 'jsan:' are replaced with the link to corresponding JSAN module. For example:
    
                jsan:Task.Joose.Core

>3. If the entry ends with ".js", its supposed to be the url and is passing without modifications.

>4. If the entry is an Object with `text` property, then the value of that property will be evaluted in the test's 
global scope directly. In this way you can run arbitrary code for setup.


### runCore

> `String runCore`

> Either `parallel` or `sequential`. Indicates how the individual tests should be run - several at once or one-by-one.


### maxThreads

> `Number maxThreads`

> The maximum number of tests running at the same time. Only applicable for `parallel` run-core.


### testClass

> `Class testClass`

> The test class which will be used for running tests, defaults to [Test.Run.Test].



SEE ALSO
========

General documentation for Joose: <http://openjsan.org/go/?l=Joose>


BUGS
====

All complex software has bugs lurking in it, and this module is no exception.

Please report any bugs through the web interface at <http://github.com/SamuraiJack/test.run/issues>



AUTHORS
=======

Nickolay Platonov [nplatonov@cpan.org](mailto:nplatonov@cpan.org)



COPYRIGHT AND LICENSE
=====================

Copyright (c) 2010, Nickolay Platonov

All rights reserved.

Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:

* Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
* Neither the name of Nickolay Platonov nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. 

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 

        
[Test.Run.Harness.Browser.Multi]: Harness/Browser/Multi.html
[Test.Run.Test]: Test.html

*/
;
Class('Test.Run', {
    
    my : {
        
        methods : {
            
        }
        
    }
})
;
Class('Test.Run.Harness.NodeJS', {
    
    isa : Test.Run.Harness,
    
    my : {
        
        have : {
            runCore         : 'sequential',
            scopeProvider   : 'Scope.Provider.NodeJS',
            
            chdirToIndex    : true,
            
            styles          : {
                'bold'      : [1, 22],
                'italic'    : [3, 23],
                'underline' : [4, 24],
                'yellow'    : [33, 39],
                'cyan'      : [36, 39],
                'white'     : [37, 39],
                'green'     : [32, 39],
                'red'       : [31, 39],
                'grey'      : [90, 39],
                'blue'      : [34, 39],
                'magenta'   : [35, 39],
                'inverse'   : [7, 27]
            }
        },
        
        
        after : {
            
            onTestEnd : function (test) {
                this.puts( test.url + ' - ' + (test.isPassed() ? this.styled('pass', 'green') : this.styled('fail', 'red')) ) 
            },
            
            
            onTestSuiteStart : function () {
                this.runCore         = 'sequential'
                
                if (this.chdirToIndex) {
                    var indexFile = process.argv[1]
                    
                    var path = require('path')
                    
                    process.chdir(path.dirname(indexFile))
                }
            },
            
            
            onTestFail : function (test, exception) {
                var text
                
                if (exception.stack)
                    text = exception.stack
                else
                    text = exception + ''
                    
                text = this.styled(this.styled(text, 'red'), 'bold')
                
                this.puts(text)
            },
            
            
            onTestUpdate : function (test, result) {
                var text = result + ''
                
                var isAssertion = result instanceof Test.Run.Result.Assertion
                
                if (isAssertion) text = this.styled(text, result.passed ? 'green' : 'red')
                if (result instanceof Test.Run.Result.Diagnostic) text = this.styled(text, 'bold')
                
                if (this.verbosity > 0)
                    this.puts(text)
                else
                    if (isAssertion && !result.passed)
                        this.puts(text)
            }            
            
        },
        
        
        methods : {
            
            resolveURL : function (url) {
                var fs = require('fs')
                
                // ref to JSAN module
                if (/^jsan:/.test(url))
                    Joose.A.each(require.paths, function (path) {
                        
                        var libPath = path.replace(/\/?$/, '') + '/' + url.replace(/^jsan:/, '').replace(/\./g, '/') + '.js'
                        
                        try {
                            if (fs.statSync(libPath).isFile()) {
                                url = libPath
                                
                                return false
                            }
                            
                        } catch (e) {
                        }
                    })
                
                // ref to lib in current dist (no trailing `.js`) 
                if (!/\.js$/.test(url)) {
                    url = '../lib/' + url.replace(/\./g, '/') + '.js'
                }
                
                // otherwise assumed to be a raw filename, relative or absolute
                return url
            },
            
            
            styled : function (text, style) {
                var styles = this.styles
                
                return '\033[' + styles[ style ][ 0 ] + 'm' + text + '\033[' + styles[ style ][ 1 ] + 'm'
            },
            
            
            puts : function (text) {
                require('sys').puts(text)
            }
        }
        
    }
    //eof my
})
//eof Test.Run.Harness.NodeJS


;