Fork me on GitHub

File: src\joopl.js

// (c) joopl 
// By Mat´┐Żas Fidemraizer (http://www.matiasfidemraizer.com) (http://www.linkedin.com/in/mfidemraizer/en)
// -------------------------------------------------
// Project site on GitHub: http://mfidemraizer.github.io/joopl/
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

if (typeof $namespace == "undefined") {
    // Keywords
    var $namespace = null; // Holds an object to manage namespaces
    var $global = null; // Represents the global namespace.
    var $import = null;

    (function (undefined) {
        "use strict";

        /** 
            Represents a namespace which can declare classes and enumerations, and provides metadata.

            @class Namespace
            @constructor
        */
        var Namespace = function (args) {

            /**
                Gets namespace name

                @property name
                @type string
            */
            Object.defineProperty(
                this,
                "name", {
                    value: args.name,
                    writable: false,
                    configurable: false,
                    enumerable: true
                }
            );

            /**
                Gets namespace full namespace path

                @property fullName
                @type string
            */
            Object.defineProperty(
                this,
                "fullName", {
                    value: args.fullName,
                    writable: false,
                    configurable: false,
                    enumerable: true
                }
            );

            /**
                Gets the parent namespace object

                @property parent
                @type Namespace
            */
            Object.defineProperty(
                this,
                "parent", {
                    value: args.parent,
                    writable: false,
                    configurable: false,
                    enumerable: true
                }
            );

            /**
                Declares a class inside the namespace (see <a href="define classes with joopl.html" target="_self">how to define classes</a>)

                @method declareClass
                @param {string} className A class name (f.e. "MyClass", "Person", "Order", "Product"...)
                @param {object} classDef A class definition 
            */
            Object.defineProperty(
                this,
                "declareClass", {
                    value: function (className, classDef) {
                        if (!this.hasOwnProperty(className)) {
                            var builtDef = TypeUtil.declareClass(className, this, classDef);
                            $global.__types__[this.fullName + "." + className] = builtDef;

                            Object.defineProperty(
                                this,
                                className, {
                                    value: builtDef,
                                    writable: false,
                                    configurable: false,
                                    enumerable: true
                                }
                            );

                            $namespace._classes[this.fullName + "." + className] = builtDef;
                        } else {
                            console.warn("Trying to define '" + className + "' class while it is already declared on '" + this.fullName + "' namespace");
                        }
                    },
                    writable: false,
                    enumerable: true,
                    configurable: false
                }
            );

            Object.defineProperty(
                this,
                "declareEnum", {
                    value: function (name, enumDef) {
                        if (!this.hasOwnProperty(name)) {
                            var builtDef = TypeUtil.declareEnum(name, this, enumDef);
                            $global.__types__[this.fullName + "." + name] = builtDef;

                            Object.defineProperty(
                                this,
                                name, {
                                    value: builtDef,
                                    writable: false,
                                    configurable: false,
                                    enumerable: true
                                }
                            );

                            $namespace._classes[this.fullName + "." + name] = builtDef;
                        } else {
                            console.warn("Trying to define '%s' enumeration while it is already declared on '%s' namespace", name, this.fullName);
                        }
                    },
                    writable: false,
                    enumerable: true,
                    configurable: false
                }
            );
        };

        Object.freeze(Namespace);

        var globalNamespace = new Namespace({ name: "$global", fullName: "$global", parent: null });

        $global = Object.create(globalNamespace, {
            __types__: {
                value: {},
                writable: false,
                configurable: false,
                enumerable: false
            },

            getType: {
                value: function (fullName) {
                    if ($global.__types__.hasOwnProperty(fullName)) {
                        return $global.__types__[fullName];
                    } else {
                        return null;
                    }
                },
                writable: false,
                configurable: false,
                enumerable: true
            }
        });

        var BrowserUtil = {
            get isIE() {
                if (!window) {
                    return false;
                }

                var div = document.createElement("div");
                div.innerHTML = "<!--[if IE]><i></i><![endif]-->";

                return div.getElementsByTagName("i").length == 1 || navigator.userAgent.toLowerCase().indexOf("trident") > 0;
            },

            get isWebkit() {
                var isChrome = /Chrome/.test(navigator.userAgent) && /Google Inc/.test(navigator.vendor);
                var isSafari = /Safari/.test(navigator.userAgent) && /Apple Computer/.test(navigator.vendor);

                return isChrome || isSafari;
            }
        };

        Object.freeze(BrowserUtil);

        // An object containing a set of core features used by jOOPL
        var TypeUtil = {
            declareClass: function (className, namespace, args) {
                var classDef = null;

                if (!args && this) {
                    args = this;
                }
                else if (!args && !this) {
                    args = {};
                }

                if (args.$inmutable === true && Object.freeze) {
                    classDef = function (args, callctor) {
                        TypeUtil.buildObject(this, args, typeof callctor == "undefined");

                        Object.freeze(this);
                    };

                } else if (args.dynamic === false && Object.preventExtensions) {
                    classDef = function (args, callctor) {
                        TypeUtil.buildObject(this, args, typeof callctor == "undefined");

                        Object.preventExtensions(this);
                    };

                } else {
                    classDef = function (args, callctor) {
                        TypeUtil.buildObject(this, args, typeof callctor == "undefined");
                    };
                }

                classDef.prototype = new $global.joopl.Object();

                var ctor = null;

                if (args.ctor) {
                    ctor = args.ctor;
                } else {
                    ctor = function () { };
                }

                Object.defineProperty(
                    classDef.prototype,
                    "ctor", {
                        value: ctor,
                        writable: false,
                        configurable: false,
                        enumerable: false
                    }
                );

                if (args.members) {
                    var propertyDescriptor = null;

                    for (var memberName in args.members) {
                        propertyDescriptor = Object.getOwnPropertyDescriptor(args.members, memberName);

                        if (typeof propertyDescriptor.value == "function") {
                            if (classDef.prototype.hasOwnProperty(memberName)) {
                                Object.defineProperty(
                                    classDef.prototype,
                                    memberName, {
                                        value: args.members[memberName]
                                    }
                                );
                            } else {
                                Object.defineProperty(
                                    classDef.prototype,
                                    memberName, {
                                        value: args.members[memberName],
                                        writable: false,
                                        configurable: true,
                                        enumerable: true
                                    }
                                );
                            }
                        } else if (memberName == "events" && typeof propertyDescriptor.value == "object") {
                            for (var eventIndex in propertyDescriptor.value) {
                                TypeUtil.createEvent(classDef.prototype, propertyDescriptor.value[eventIndex]);
                            }

                        } else if (propertyDescriptor.hasOwnProperty("value") || propertyDescriptor.hasOwnProperty("get") || propertyDescriptor.hasOwnProperty("set")) {
                            TypeUtil.createPropertyFromDescriptor(classDef, memberName, propertyDescriptor);
                        }
                    }
                }


                if (args.inherits) {
                    TypeUtil.buildInheritance(classDef, args.inherits);

                    Object.defineProperty(
                        classDef.prototype,
                        "__base__", {
                            value: args.inherits,
                            writable: false,
                            configurable: false,
                            enumerable: false
                        }
                    );
                }

                var hasMetadata = false;

                if (Array.isArray(args.attributes)) {
                    for (var attrIndex in args.attributes) {
                        if (!args.attributes[attrIndex] && !(args.attributes[attrIndex].isTypeOf instanceof Function) && !args.attributes[attrIndex].isTypeOf($global.joopl.Attribute)) {
                            throw new $global.joopl.ArgumentException({
                                argName: "attributes",
                                reason: "A non-attribute type given as attribute"
                            });
                        }
                    }

                    hasMetadata = true;
                }

                if (typeof $global.joopl.Type == "function") {
                    var typeDescriptor = {
                        configurable: true,
                        enumerable: true,
                        writable: false,
                        value: new $global.joopl.Type({ name: className, baseType: args.inherits ? args.inherits.type : null, namespace: namespace, attributes: hasMetadata ? args.attributes : [] })
                    };

                    Object.defineProperty(classDef, "type", typeDescriptor);
                    Object.defineProperty(classDef.prototype, "type", typeDescriptor);
                }

                return classDef;
            },

            declareEnum: function (name, namespace, enumDef) {
                if (typeof enumDef != "object") {
                    throw new $global.joopl.ArgumentException({
                        argName: "enumDef",
                        reason: "No definition for the enumeration given"
                    });
                }

                var enumerationType = function () {
                };

                enumerationType.prototype = new $global.joopl.Object();

                var enumNames = [];
                var enumValue;

                for (var propertyName in enumDef) {
                    if (typeof enumDef[propertyName] != "number") {
                        throw new $global.joopl.ArgumentException({
                            argName: "enumDef",
                            reason: "An enumeration definition must contain numeric properties only"
                        });
                    }

                    enumValue = new Number(enumDef[propertyName]);
                    enumValue.enum = new $global.joopl.EnumValue({ value: enumValue, name: propertyName });

                    Object.defineProperty(
                        enumerationType,
                        propertyName, {
                            value: enumValue,
                            configurable: false,
                            enumerable: true,
                            writable: false
                        }
                    );

                    enumNames.push(propertyName);
                }

                Object.defineProperty(
                    enumerationType,
                    "valueNames",
                    {
                        value: enumNames,
                        configurable: false,
                        enumerable: true,
                        writable: false
                    }
                );

                Object.defineProperty(
                    enumerationType,
                    "type", {
                        value: new $global.joopl.Type({ name: name, fullName: namespace.fullName + "." + name, namespace: namespace }),
                        writable: false,
                        configurable: false,
                        enumerable: true
                    }
                );

                Object.freeze(enumerationType);

                return enumerationType;
            },

            //  Creates a property on the given class definition based on a provided property descriptor.
            // @classDef: The class definition (it must be the ctor function!
            // @name: The property name
            // @descriptor: An object representing the property descriptor
            // @context: An optional context object that will work as the "this" keyword binder for the getter and/or setter when defining the property.
            createPropertyFromDescriptor: function (classDef, name, descriptor, context) {
                if (context) {
                    if (descriptor.get) {
                        descriptor.get = descriptor.get.bind(context);
                    }

                    if (descriptor.set) {
                        descriptor.set = descriptor.set.bind(context);
                    }
                }

                Object.defineProperty(
                    context ? context : classDef.prototype,
                    name,
                    descriptor
                );
            },

            // Creates a property.
            // @classDef: A class definition (it must be the class ctor function).
            // @name: The property name.
            // @getter: The getter parameterless function.
            // @setter: The setter function with a parameter representing the value to set.
            // @inmutable: A boolean flag specifying if the created property is configurable or not.
            createProperty: function (classDef, name, getter, setter, inmutable) {
                if (typeof inmutable == "undefined") {
                    inmutable = false;
                }

                if (!classDef.prototype.hasOwnProperty(name)) {
                    // This case is for a read-write property
                    if (typeof getter != "undefined" && typeof setter != "undefined") {

                        Object.defineProperty(
                            classDef.prototype,
                            name, {
                                get: getter,
                                set: setter,
                                configurable: !inmutable,
                                enumerable: true
                            }
                        );
                    } else if (typeof getter != "undefined") { // This case is for a read-only property
                        Object.defineProperty(
                            classDef.prototype,
                            name, {
                                get: getter,
                                configurable: !inmutable,
                                enumerable: true
                            }
                        );
                    } else if (typeof setter != "undefined") { // This case is for a write-only property
                        Object.defineProperty(
                            classDef.prototype,
                            name, {
                                set: setter,
                                configurable: !inmutable,
                                enumerable: true
                            }
                        );
                    }
                }
            },

            createEvent: function (source, name) {
                Object.defineProperty(
                    source,
                    name,
                    {
                        get: function () {
                            if (!this._.hasOwnProperty("__eventManager__")) {
                                Object.defineProperty(
                                    this._,
                                    "__eventManager__", {
                                        value: new $global.joopl.EventManager({ source: this }),
                                        configurable: false,
                                        writable: false,
                                        enumerable: true
                                    }
                                );
                            }

                            if (!this._.__eventManager__.hasOwnProperty(name)) {
                                this._.__eventManager__.register(name);
                            }

                            return this._.__eventManager__[name];
                        },
                        configurable: false,
                        enumerable: true
                    }
                );;
            },

            // Builds a class instance into a full jOOPL object supporting inheritance and polymoprhism, and calls the ctor of the whole class instance.
            // @instance: The class instance
            // @args: The ctor arguments.
            buildObject: function (instance, args, callctor) {
                if (instance.__base__ instanceof Function) {
                    Object.defineProperty(
                        instance,
                        "base",
                        {
                            get: function () {
                                if (instance.__base__ instanceof Function) {
                                    Object.defineProperty(
                                        instance,
                                        "__base__", {
                                            value: new instance.__base__(args, false),
                                            configurable: false,
                                            writable: false,
                                            enumerable: false
                                        }
                                    );
                                }

                                return instance.__base__;
                            },
                            configurable: false,
                            enumerable: true
                        }
                    );

                    Object.defineProperty(
                        instance,
                        "__fields__", {
                            value: instance.base._,
                            writable: false,
                            configurable: false,
                            enumerable: false
                        }
                    );
                } else {
                    Object.defineProperty(
                        instance,
                        "__fields__", {
                            value: {},
                            writable: false,
                            configurable: false,
                            enumerable: false
                        }
                    );
                }

                Object.defineProperty(
                    instance._,
                    "__derived__", {
                        value: instance,
                        writable: false,
                        enumerable: false,
                        configurable: true
                    }
                );

                // Fix for PhantomJS, which doesn't fully support
                // ES5 property definitions in prototypes...
                if ($userAgent_phantomjs) {
                    Object.defineProperty(
                        instance,
                        "derived", {
                            value: instance
                        }
                    );
                }

                if (callctor) {
                    instance.ctor.call(instance, args);
                }

                return instance;
            },

            // Builds a given class inheritance with the given parent class.
            // @derived: The child class.
            // @parent: The parent class.
            buildInheritance: function (derived, parent) {
                if (parent != null) {
                    var propertyDescriptor = null;

                    // Adding both methods and properties to the derived class...
                    for (var memberName in parent.prototype) {
                        if (!derived.prototype.hasOwnProperty(memberName) && !$global.joopl.Object.prototype.hasOwnProperty(memberName)) {
                            propertyDescriptor = Object.getOwnPropertyDescriptor(parent.prototype, memberName);

                            // If it has a property descriptor...
                            if (propertyDescriptor) {
                                // and the value of the descriptor is a function it means that it's inheriting a method.
                                if (typeof propertyDescriptor.value == "function") {
                                    Object.defineProperty(
                                        derived.prototype,
                                        memberName, {
                                            value: parent.prototype[memberName],
                                            writable: false,
                                            enumerable: true,
                                            configurable: true
                                        }
                                    );
                                    // derived.prototype[memberName] = parent.prototype[memberName];
                                } else { // If not, it is a property accessor.
                                    this.createPropertyFromDescriptor(derived, memberName, propertyDescriptor);
                                }
                            } else if (typeof parent.prototype[memberName] == "function") { // It can also happen that it's a function defined in the $global.joopl.Object prototype...
                                Object.defineProperty(
                                    derived.prototype,
                                    memberName, {
                                        value: parent.prototype[memberName],
                                        writable: false,
                                        enumerable: true,
                                        configurable: false
                                    }
                                );
                            }
                        }
                    }
                }

                return parent ? parent.prototype : null;
            },

            // Whether determines if some object reference has an associated value (object) and returns true/false.
            // @someRef: The object reference.
            hasValue: function (someRef) {
                return typeof someRef != "undefined" && someRef != null;
            }
        };

        Object.freeze(TypeUtil);

        $import = {
            _dependencyMaps: {},
            _loadedFiles: [],

            mapMany: function (dependencyMaps) {
                for (var uri in dependencyMaps) {
                    this.map(uri, dependencyMaps[uri]);
                }
            },

            map: function (uri, dependencies) {
                if (!this._dependencyMaps.hasOwnProperty(uri)) {
                    this._dependencyMaps[uri] = dependencies;
                }
            },

            modules: function () {
                var scopeModules = [];
                var that = this;
                var scopeFunc = null;
                var arg = null;

                for (var argIndex = 0; argIndex < arguments.length; argIndex++) {
                    arg = arguments[argIndex];

                    if (typeof arg == "function") {
                        scopeFunc = arg;
                    } else if (typeof arg == "string") {
                        scopeModules.push(arg);
                    } else {
                        throw new $global.joopl.ArgumentException({ argName: "paths", reason: "Some of given module names is not a string literal" });
                    }
                };

                if (scopeFunc == null) {
                    throw new $global.joopl.ArgumentException({ argName: "scopeFunc", reason: "No modules' scope function given" });
                }

                var enableHeadJS = Object.keys(this._dependencyMaps).length > 0 && typeof head != "undefined" && typeof window.head.js != "undefined";

                // If HeadJS is available, jOOPL integrates HeadJS asynchronous loading 
                // of DependencyUsageMap dependencies
                if (enableHeadJS) {
                    var dependencyMaps = this._dependencyMaps;

                    var args = [];
                    var dependencies = null;
                    var currentFile = null;

                    for (var moduleIndex in scopeModules) {
                        dependencies = dependencyMaps[scopeModules[moduleIndex]];

                        if (dependencies instanceof Array) {
                            for (var dependencyIndex in dependencies) {
                                currentFile = dependencies[dependencyIndex];

                                if (this._loadedFiles.indexOf(currentFile) == -1) {
                                    this._loadedFiles.push(currentFile);
                                    args.push(currentFile);
                                }
                            }
                        }
                    }

                    if (args.length > 0) {
                        args.push(function () {
                            scopeFunc();
                        });

                        head.js.apply(window, args);
                    } else {
                        scopeFunc();
                    }

                } else {
                    scopeFunc();
                }
            }
        };

        Object.freeze($import);

        $namespace = {
            _namespaces: {},
            _classes: {},

            __register__: function (path, scopedFunc) {

                var nsIdentifiers = typeof path == "string" ? path.split(".") : null;

                if (!this._namespaces.hasOwnProperty(path)) {
                    // The parent namespace of everything is the reserved $global object!
                    var parentNs = $global;
                    var currentNs = null;
                    var nsPath = [];

                    for (var nsIndex = 0; nsIndex < nsIdentifiers.length; nsIndex++) {
                        currentNs = nsIdentifiers[nsIndex];
                        nsPath.push(currentNs);

                        // The current namespace  is not registered (if evals true)
                        if (typeof parentNs[currentNs] == "undefined") {
                            var ns = new Namespace({ parent: parentNs, name: currentNs, fullName: nsPath.join(".") });

                            Object.defineProperty(
                                parentNs,
                                currentNs, {
                                    value: ns,
                                    writable: false,
                                    configurable: false,
                                    enumerable: true
                                }
                            );

                            this._namespaces[ns.fullName] = ns;
                        }

                        parentNs = parentNs[currentNs];
                    }
                }
            },

            using: function () {
                var scopeNamespaces = [];
                var that = this;
                var scopeFunc = null;
                var arg = null;

                for (var argIndex = 0; argIndex < arguments.length; argIndex++) {
                    arg = arguments[argIndex];

                    if (typeof arg == "function") {
                        scopeFunc = arg;
                    } else if (typeof arg == "string") {
                        if (!that._namespaces.hasOwnProperty(arg)) {
                            that.__register__(arg);
                        }

                        scopeNamespaces.push(that._namespaces[arg]);
                    } else {
                        throw new $global.joopl.ArgumentException({ argName: "paths", reason: "Some of given namespace paths is not a string literal" });
                    }
                };

                if (scopeFunc == null) {
                    throw new $global.joopl.ArgumentException({ argName: "scopeFunc", reason: "No namespace scope function given" });
                }

                scopeFunc.apply(scopeNamespaces, scopeNamespaces);
            },
        };

        Object.freeze($namespace);

        $namespace.using("joopl", function (joopl) {
            /**
                @namespace joopl
            */

            /** 
                Represents the base type of any class defined by jOOPL

                @class Object
                @constructor
            */
            joopl.Object = function () {
            };
            joopl.Object.prototype = {};
            joopl.Object.prototype = Object.defineProperties(joopl.Object.prototype, {
                joopl: {
                    get: function () {
                        return "2.5.3";
                    },
                    configurable: false,
                    enumerable: true
                },

                _: {
                    get: function () {
                        return this.__fields__;
                    },
                    configurable: false,
                    enumerable: true
                },

                derived: {
                    get: function () {
                        return this._.__derived__;
                    },
                    configurable: false,
                    enumerable: true
                },

                /**
                    Determines if a given type is of type of current object

                    @method isTypeOf
                    @param {class} type The whole type to compare with
                    @example 
                        obj.isTypeOf(this.A)
                    
                */
                isTypeOf: {
                    value: function (type) {
                        var allBases = [];
                        var lastBase = this;
                        var isMember = false;

                        if (this instanceof type) {
                            isMember = true;
                        } else {
                            while (!isMember && lastBase.base) {
                                if (!(isMember = lastBase.base instanceof type)) {
                                    lastBase = lastBase.base;
                                }
                            }
                        }

                        return isMember;
                    },
                    configurable: false,
                    enumerable: true,
                    writable: false
                }
            });

            /**
                Represents type information and provides access to types' metadata.

                @class Type
                @final
                @since 2.3.0
            */
            joopl.declareClass("Type", {
                ctor: function (args) {
                    this._.attributes = args.attributes;
                    this._.name = args.name;
                    this._.namespace = args.namespace;
                    this._.baseType = args.baseType;
                },
                members: {
                    /**
                        Gets type name (f.e. "MyClass")

                        @property name
                        @type string
                        @readonly
                    */
                    get name() {
                        return this._.name;
                    },

                    /**
                        Gets current type name including full namespace path (f.e. "joopl.test.MyClass")

                        @property fullName
                        @type string
                        @readonly
                    */
                    get fullName() {
                        return this.namespace.fullName + "." + this.name;
                    },

                    /**
                        Gets current base type (i.e. parent class) metadata.

                        @property baseType
                        @type joopl.Type
                        @readonly
                    */
                    get baseType() {
                        return this._.baseType;
                    },

                    /**
                        Gets current namespace instance

                        @property baseType
                        @type joopl.Namespace
                        @readonly
                    */
                    get namespace() {
                        return this._.namespace;
                    },

                    /**
                        Gets all type's attributes.

                        @property attributes
                        @type joopl.Attribute
                        @readonly
                    */
                    get attributes() {
                        return this._.attributes;
                    },

                    /**
                        Gets an attribute instance by giving its type, if the type has the whole attribute

                        @method getAttribute
                        @param {joopl.Attribute} An attribute class definition (rather than an instance!)
                        @return {joopl.Attribute} The attribute instance or `null` if the type does not have the given attribute type
                        @example 
                            myNamespace.MyClass.type.getAttribute(this.MyAttribute);
                    */
                    getAttribute: function (attributeType) {
                        var found = false;
                        var index = 0;

                        while (!found && index < this.attributes.length) {
                            if (this.attributes[index] instanceof attributeType) {
                                found = true;
                            } else {
                                index++;
                            }
                        }

                        if (found) {
                            return this.attributes[index];
                        } else {
                            return null;
                        }
                    },

                    /**
                        Determines whether a given type has an attribute giving its class (rather than giving an instance!)

                        @method hasAttribute
                        @param {joopl.Attribute} The whole attribute class
                        @example
                            this.SomeClass.type.hasAttribute(SomeAttribute);
                    */
                    hasAttribute: function (attributeType) {
                        return this.getAttribute(attributeType) != null;
                    }
                }
            });

            /**
                <h2 id="index">Index</h2>

                * 1.0\. [What is an attribute?](#attribute-definition)
                * 2.0\. [How to implement and consume an attribute](#attribute-howto)
                    * 2.1\. [Attributes with parameters](#attribute-params)

                <h2 id="attribute-definition">1.0 What is an attribute?</h2>
                
                Usually class definitions contain a class ctor, properties, methods and/or events, also known as *class members*. Class members define the information and behavior of a given class. 

                In some cases, classes require some descriptive information that may be useful by the consumers. 

                For example, a class may need to define that requires user authentication and the minimum security role to use its members is *administrator*. 

                How can an arbitrary class tell the environment "*I will not work if the authenticated user is not an administrator*"? **The answer is *attributes**.*

                An attribute is an inherited class of `Attribute` which defines some metadata that can be identified by other pieces and it is added to the class definition during desing-time.

                Finally, a class supports as many attributes as the code requires. The `attributes` parameters for the `$def` operator is an array of attributes.

                <h2 id="attribute-howto">2.0 How to implement and consume an attribute</h2>
                
                The so-called *I will not work if the authenticated user is not an administrator* attribute may be implemented as a class called `RequiresAuthenticationAttribute`:

                    $namespace.using("joopl", "myNamespace", function(joopl, myNamespace) {
                        myNamespace.declareClass("RequiresAuthenticationAttribute", {
                            inherits: joopl.Attribute
                        });
                    });

                Later on, some class that may require authentication to work will apply the whole `RequiresAuthenticationAttribute` as follows:

                    $namespace.using("myNamespace", function(myNamespace) {
                        myNamespace.declareClass("MyClass", {
                            attributes: [new myNamespace.RequiresAuthenticationAttribute()]
                        });
                    });

                Finally, some other code which instantiate the `MyClass` class will inspect if the class requires authentication:

                    $namespace.using("myNamespace", function(myNamespace) {
                        if(myNamespace.MyClass.type.hasAttribute(myNamespace.RequiresAuthenticationAttribute)) {
                            // Do some stuff if MyClass has the whole attribute
                        } else {
                            throw Error("Sorry, this code will not execute classes if they do not require authentication...");
                        }
                    });

                <h3 id="attribute-params">2.1 Attributes with parameters</h3>
                Sometimes using an attribute *as is* is not enough, because the attribute itself should contain data. 

                For example, some code may require some classes to define a default property. `Person` class may have `FirstName`, `Surname` and `Nickname` properties. Which one will be the one to display in some listing?

                    $namespace.using("joopl", "myNamespace", function(joopl, myNamespace) {
                        myNamespace.declareClass("DefaultPropertyAttribute", {
                            inherits: oopl.Attribute,
                            ctor: function(args) {
                                this._.defaultPropertyName = args.defaultPropertyName;
                            },
                            members: {
                                get defaultPropertyName() { return this._.defaultPropertyName; }
                            }
                        });

                        myNamespace.declareClass("Person", {
                            attributes: [new myNamespace.DefaultPropertyAttribute("nickname")],
                            ctor: function() {
                                this._.firstName = null;
                                this._.surname = null;
                                this._.nickname = null;
                            }
                            members: {
                                get firstName() { return this._.firstName; },
                                set firstName(value) { this._.firstName = value; },

                                get surname() { return this._.surname; },
                                set surname(value) { this._.surname = value; },

                                get nickname() { return this._.nickname; },
                                set nickname(value) { this._.nickname = value; }
                            }
                        });
                    });

                
                Now, some code consumes instances of `Person` and creates some HTML listing using standard DOM and the display name for the whole person will be taken from the `DefaultPropertyValueAttribute`:

                    $namespace.using("myNamespace", function(myNamespace) {
                        
                        // The first step is creating a regular instance of Person
                        var person = new myNamespace.Person();
                        person.firstName = "Matias";
                        person.surname = "Fidemraizer";
                        person.nickname = "mfidemraizer";

                        // Secondly, this is checking if the Person class has the whole attribute
                        if(Person.type.hasAttribute(myNamespace.DefaultPropertyAttribute)) {
                            // Yes, it has the attribute!
                            //
                            // Then, the attribute instance is retrieved from the type information
                            var defaultProperty = Person.type.getAttribute(myNamespace.DefaultPropertyAttribute);

                            // Once the attribute is retrieved, the code can access the "defaultPropertyName" instance property
                            // of the DefaultPropertyAttribute
                            var defaultPropertyName = defaultProperty.defaultPropertyName;
                            
                            // Since any object is also an associative array (this is plain JavaScript!), 
                            // the default property can be retrieved by using the "defaultPropertyName" variable
                            // as key of the array
                            var defaultPropertyValue = person[defaultPropertyName];

                            // Finally, this is creating a paragraph containing the defaultPropertyValue. In this case, 
                            // it will be "mfidemraizer", because the Person class has the DefaultPropertyAttribute set to "nickname"!
                            var p = document.createElement("p");
                            p.appendChild(document.createTextNode(defaultPropertyValue));
                            document.body.appendChild(p);
                        }
                    });
            
                @class Attribute
                @since 2.3.0
            */
            joopl.declareClass("Attribute", {
                members: {
                }
            });

            /**
                Represents an enumeration value and provides access to common operations for the whole enumeration value.

                See {{#crossLink "Enumerations"}}{{/crossLink}} to learn more about enumerations.

                @class EnumValue
                @final
                @since 2.3.0
            */
            var EnumValue = joopl.declareClass("EnumValue", {
                ctor: function (args) {
                    this._.value = args.value;
                    this._.name = args.name;
                },
                members: {
                    /** 
                        Gets the enumeration value.

                        @property value
                        @type Number
                    */
                    get value() { return this._.value; },

                    /**
                        Gets the enumeration value name

                        @property name
                        @type string
                        @readOnly
                    */
                    get name() { return this._.name; },

                    /** 
                        Performs a bitwise OR with the given enumeration value

                        @method or
                        @param enumValue {Number} An enumeration value
                        @return {Number} The flag of two or more enumeration values
                        @example 
                            var flag = myNamespace.State.open.enum.or(State.closed); // This is State.open | State.closed
                    */
                    or: function (enumValue) {
                        var value = this.value | enumValue;

                        var result = new Number(value);
                        result.enum = new $global.joopl.EnumValue({ value: value });

                        Object.freeze(result);

                        return result;
                    },

                    /** 
                        Performs a bitwise AND with the given enumeration value

                        @method and
                        @param enumValue {Number} An enumeration value
                        @return {Number} The flag of two or more enumeration values
                        @example 
                            var flag = myNamespace.State.open.enum.and(myNamespace.State.closed); // This is State.open & State.closed
                    */
                    and: function (enumValue) {
                        var value = this.value & enumValue;

                        var result = new Number(value);
                        result.enum = new $global.joopl.EnumValue({ value: value });

                        Object.freeze(result);

                        return result;
                    },

                    /** 
                        Determines if some enumeration value contains other enumeration value.

                        @method hasFlag
                        @param enumValue {Number} An enumeration value
                        @return {Boolean} A boolean specifying if the given enumeration value was found in the flag.
                        @example 
                            var flag = myNamespace.State.open.enum.or(myNamespace.State.closed);
                            var hasOpen = flag.enum.hasFlag(myNamespace.State.open);
                    */
                    hasFlag: function (enumValue) {
                        return (this.value & enumValue) === Number(enumValue);
                    }
                }
            });

            /**
                Represents an utility class to work with enumerations.

                @class Enum
                @static
                @since 2.3.0
            */
            joopl.Enum = new (TypeUtil.declareClass("Enum", this, {
                members: {
                    /** 
                        Parses a text into a given enumeration value

                        @method parseName
                        @param enumType {enum} The enumeration definition (i.e. *State*, *ConnectionTypes*, ...)
                        @param valueName {string} The value name to be parsed (i.e. If an enumeration called States would have an *open* and *closed* values, *open* or *closed* would be a value names)
                        @static
                        @example
                            $namespace.using("joopl", function(joopl) {
                                joopl.declareEnum("State", {
                                    open: 1,
                                    closed: 2
                                });

                                var open = joopl.Enum.parseName(State, "open")
                            });
                    */
                    parseName: function (enumType, valueName) {
                        if (enumType.valueNames.indexOf(valueName) > -1) {
                            return enumType[valueName];
                        } else {
                            throw new scope.ArgumentException({
                                argName: "valueName",
                                reason: "Given value name could not be found as value of the given enumeration type"
                            });
                        }
                    },

                    /** 
                        Parses a comma-separated list of text values as a mask of given enumeration

                        @method parseNames
                        @param enumType {enum} The enumeration definition (i.e. *State*, *ConnectionTypes*, ...)
                        @param valueNames {String} A comma-separated list of a mask of given enumeration type (i.e. "open, closed, working").
                        @static
                        @example
                            $namespace.using("joopl", function(joopl) {
                                joopl.declareEnum("State", {
                                    open: 1,
                                    closed: 2
                                });

                                joopl.Enum.parseNames(State, "open, closed")
                            });
                    */
                    parseNames: function (enumType, valueNames) {
                        if (!(valueNames && typeof valueNames == "string")) {
                            throw new scope.ArgumentException({
                                argName: "valueName",
                                reason: "Wrong value names"
                            });
                        }

                        var valueNamesArr = valueNames.replace(" ", "").split(",");
                        var value = 0;

                        for (var nameIndex = 0; nameIndex < valueNamesArr.length; nameIndex++) {
                            value += this.parseName(enumType, valueNamesArr[nameIndex])
                        }

                        return new $global.joopl.EnumValue({ value: value });
                    }
                }
            }));

            /**
                Represents a multi-cast event. 

                An event is an observable object which notifies multiple objects listening event raises.

                @class Event
                @final
                @constructor
                @param {object} source The object who will be raising this event
            */
            joopl.declareClass("Event", {
                ctor: function (args) {
                    this._.handlers = [];
                    this._.source = args.source;
                },
                members: {

                    /**
                        Gets an array of event handlers listenting for event raise

                        @property handlers
                        @return Array
                        @private
                    */
                    get handlers() { return this._.handlers; },

                    /**
                        Gets the object who raises the event

                        @property source
                        @type Object
                        @private
                    */
                    get source() { return this._.source; },

                    /**
                        Adds and binds a function to this event that will be called whenever
                        this event is raised.

                        It supports unlimited event listeners and they will be called in order *FIFO*.

                        @method addEventListener
                        @param {function} handler A function reference which handles the event
                        @return {void}
                    */
                    addEventListener: function (handler) {
                        var index = this.handlers.indexOf(handler);

                        if (index == -1) {
                            this.handlers.push(handler);
                        }
                    },

                    /**
                        Removes and unbinds a function from this event.
                        
                        Given function handler should be the one that was previously added with `addEventListener`.

                        @method addEventListener
                        @param {function} handler A function reference which handles the event
                        @return {void}
                    */
                    removeEventListener: function (handler) {
                        var index = this.handlers.indexOf(handler);

                        if (index >= 0) {
                            this.handlers.splice(index, 1);
                        }
                    },

                    raise: function (args) {
                        if (this.handlers.length > 0) {
                            for (var delegateIndex in this.handlers) {
                                this.handlers[delegateIndex].call(
                                    args ? (args.$this ? args.$this : this.source) : this.source,
                                    args ? (args.args ? args.args : null) : null
                                );
                            }
                        }
                    }
                }
            });

            /**
                Represents an event manager for some class supporting events.

                It is capable of registering events and managing their life-cycle.

                @class EventManager
                @final
                @constructor 
                @param {object} source The object who is associated with the event manager
            */
            joopl.declareClass("EventManager", {
                ctor: function (args) {
                    this._.source = args.source;
                },
                members: {
                    /**
                        Gets the object who is associated with the event manager

                        @property source
                        @type object
                        @private
                    */
                    get source() {
                        return this._.source;
                    },

                    /**
                        Registers an event in the event manager
                        
                        @method register
                        @return void
                    */
                    register: function (eventName) {
                        var delegates = [];
                        var that = this;
                        var source = this.source;

                        Object.defineProperty(
                            this,
                            eventName, {
                                value: new $global.joopl.Event({ source: this.source }),
                                writable: false,
                                configurable: false,
                                enumerable: true
                            }
                        );
                    }
                },
            });

            /**
                Represents a set of environmental values and operations

                @class Environment
                @final
            */
            joopl.declareClass("Environment", {
                members: {
                    /**
                        Occurs when any exception of any type is thrown within current application

                        @event exceptionThrown
                        @param {joopl.Exception} thrownException The exception that has been thrown
                        @example
                            // Listening exceptions...
                            $global.joopl.Environment.current.exceptionThrown.addEventListener(function(e) {
                                var exception = e.thrownException;
                            });

                            // Raising the event...
                            $global.joopl.Environment.current.notifyException(someException);
                    */
                    events: ["exceptionThrown"],

                    /**
                        Notifies a given exception to all subscribers

                        @method notifyException
                        @param {joopl.Exception} exception The exception to be notified
                        @example 
                            $global.joopl.Environment.current.notifyException(someException);
                    */
                    notifyException: function (exception) {
                        this.exceptionThrown.raise({ args: { thrownException: exception } });
                    }
                }
            });

            /**
                Gets current Environment instance

                @property current
                @type Environment
                @readonly
                @static
            */
            Object.defineProperty(
                joopl.Environment,
                "current", {
                    value: new joopl.Environment(),
                    writable: false,
                    configurable: false,
                    enumerable: true
                }
            );

            joopl.declareClass("Exception", {

                /**
                    Represents the base class for any exception 

                    <a href="Exception%20handling%20best%20practices.html" target="_self">Please read more about exception handling by following this link to "Exception handling best practices"</a>

                    @class Exception
                    @constructor
                    @param {string} message A human-readable reason text for the whole exception
                    @param {Exception} innerException An inner exception that is more specific to occured error
                    @example 
                        throw new $global.joopl.Exception({ message: "Some", innerException: otherException });
                */
                ctor: function (args) {
                    this._.message = args.message;
                    this._.innerException = args.innerException;

                    var error = Error(args.message);

                    if (BrowserUtil.isIE) {
                        try {
                            throw error;
                        } catch (e) {
                            error = e;
                        }
                    }

                    var stackTrace = error.stack.split("\n");
                    var found = false;
                    var index = BrowserUtil.isIE || BrowserUtil.isWebkit ? 0 : -1;
                    var stackRegex = BrowserUtil.isIE || BrowserUtil.isWebkit ? /(joopl[.A-Za-z0-9]+\.js[:0-9]+\)$)|(at Error)/ : /joopl[.A-Za-z0-9]+\.js[:0-9]+$/;

                    while (!found && index < stackTrace.length) {
                        if (!stackRegex.test(stackTrace[++index])) {
                            found = true;
                        }
                    }

                    if (found) {
                        stackTrace.splice(0, index);

                        this._.stackTrace = stackTrace;
                    }

                    this._.error = error;

                    $global.joopl.Environment.current.notifyException(this._.derived);
                },

                members:
                {
                    /**
                        Gets the human-readable reason text for this exception

                        @property message
                        @type string
                        @readonly
                    */
                    get message() {
                        return this._.message;
                    },

                    /**
                        Gets an inner exception (optional) which provides information about the sorrounding one

                        @property innerException
                        @type Exception
                        @readonly
                    */
                    get innerException() {
                        return this._.innerException;
                    },

                    /**
                        Gets the exception's stack trace as an array where each index is a code line  

                        @property stackTrace
                        @type Array
                        @readonly
                    */
                    get stackTrace() {
                        return this._.stackTrace;
                    },

                    /**
                        Gets underlying `Error` instance  

                        @property error
                        @type Error
                        @readonly
                    */
                    get error() {
                        return this._.error;
                    },

                    /**
                        Returns the exception message plus the stack trace as a concatenated string

                        @method toString
                        @return {String} The exception message plus the stack trace as a concatenated string
                    */
                    toString: function () {
                        var text = "An exception of type '" + this.type.fullName + "' has been thrown with message '" + this.message + "'\n\n";
                        text += "Stack trace:\n_________________________\n\n";
                        text += this.stackTrace.join("\n");

                        return text;
                    }
                }
            });

            joopl.declareClass("ArgumentException", {
                inherits: joopl.Exception,

                /**
                    Represents an exception that occurs when some method argument is missing or invalid

                    <a href="Exception%20handling%20best%20practices.html" target="_self">Please read more about exception handling by following this link to "Exception handling best practices"</a>

                    @class ArgumentException
                    @extends joopl.Exception
                    @constructor
                    @param {string} argName The affected argument name
                    @param {string} reason (optional) A reason text explaining what was wrong with the affected argument
                    @example
                        throw new $global.joopl.ArgumentException({ argName: "someArgument"});
                */
                ctor: function (args) {
                    this._.argName = args.argName;

                    var message = "The given argument '" + args.argName + "' is not valid";

                    if (args.reason) {
                        message += " (Reason: " + args.reason + ")";
                    }

                    this.base.ctor({
                        message: message
                    });
                },
                members: {
                    /**
                        Gets the affected argument name

                        @property argName
                        @type string
                        @readonly
                    */
                    get argName() {
                        return this._.argName;
                    }
                }
            });

            joopl.declareClass("NotImplementedException", {
                inherits: joopl.Exception,

                /**
                    Represents an exception that occurs when a class member is not implemented.

                    <a href="Exception%20handling%20best%20practices.html" target="_self">Please read more about exception handling by following this link to "Exception handling best practices"</a>

                    @class NotImplementedException
                    @extends joopl.Exception
                    @constructor
                    @param {string} memberName The affected member name which does not implement something
                    @example
                        throw new $global.joopl.NotImplementedException({ memberName: "someMethod"});
                */
                ctor: function (args) {
                    this.base.ctor(
                        {
                            message: !TypeUtil.hasValue(args) || !TypeUtil.hasValue(args.memberName) ?
                                            "A method or property is not implemented"
                                            :
                                            "Method or property '" + args.memberName + "' is not implemented"
                        }
                    );

                    this._.memberName = args.memberName;
                },
                members: {
                    /**
                        Gets the not implemented member name

                        @property memberName
                        @type string
                        @readonly
                    */
                    get memberName() {
                        return this._.memberName;
                    }
                }
            });
        });
    })(undefined);
}