
function toJSON(o) {
    var str = "";
    if(o instanceof Array) {
        str += "[";
        for(var i = 0; i < o.length; i++) {
            str += toJSON(o[i]) + (i+1<o.length?",":"");
        }
        str += "]";
    } else if(o instanceof String) {
        str += "\"" + o.toString().replace(/\"/g, "\\\"") + "\"";
    } else if(o instanceof Number) {
        str += "" + o.valueOf();
    } else if(o instanceof Date) {
        str += "new Date(" + o.getTime() + ")";
    } else if(o instanceof Object) {
        str += "{";
        var c = 0;
        for(var i in o) {
            str += (c++?",":"") + "\"" + i + "\":" + toJSON(o[i]);
        }
        str += "}";
    } else if(typeof(o) == "string"){
        str += "\"" + o.replace(/\"/g, "\\\"") + "\"";
    } else {
        str += o;
    }
    return str;
}

/**
 * Get a string representation of the objects type
 * @type String
 * @param {object} object
 */
function typeOf(object) {
    var student = object && object.__class__;
    if(!student) {
        if(typeof(object) == "object") {
            return object.constructor.name;
        } else {
            return typeof(object);
        }
    } else {
        return student.className;
    }
}

/**
 * Check if object is an instance of a specific class
 * @type boolean
 * @param {Object} object
 * @param {Class<T>} teacher
 * @return True if object inherits from teacher
 */
function instanceOf(object, teacher) {
    if(typeof(object) == "undefined") return false;
    if(typeof(teacher) == "string") {
        if(object === null) return teacher.toLowerCase() == "string";
        return typeof(object) == teacher.toLowerCase();
    } else {
        if(object === null || teacher == Object) return true;
        if(!object.__class__) {
            return object.constructor == teacher;
        } else {
            return inheritsFrom(object.__class__, teacher);
        }
    }
}

/**
 * Check if a class inherits from a specific class
 * @type boolean
 * @param {Class<T>} student
 * @param {Class<T>} teacher
 * @return True if student inherits from teacher
 */
function inheritsFrom(student, teacher) {
    if(!teacher) return false;
    return (student.__inheritance__.indexOf(teacher.className + "|") != -1);
}

/**
 * Clone a object
 * @type Object
 * @param {Object} o The object to clone
 * @return A new Object that equals the object passed in the first parameter
 */
function clone(o, d) {
    if(typeof(o) == "object" && (typeof(d) == "undefined" || d-- > 0)) {
        var n = {};
        for(var i in o) {
            n[i] = clone(o[i], d);
        }
        return n;
    }
    return o;
}

/**
 * Check if two objects are equal
 * @type boolean
 * @param {Object} o1
 * @param {Object} o2
 * @return True if o1 is equal to o2
 * @deprecated
 */
function compare(o1, o2) {
    if(o1 && o2  && o1 != o2
       && typeof(o1) == "object"
       && typeof(o2) == "object") {
        var notempty = false;
        for(var i in o1) {
            if(!compare(o1[i], o2[i])) return false;
            var notempty = true;
        }
        for(var i in o2) {
            return notempty;
        }
        return true;
    }
    return (o1 == o2);
}

/**
 * Check if two objects are equal
 * @type boolean
 * @param {Object} o1
 * @param {Object} o2
 * @return True if o1 is equal to o2
 */
function equals(o1, o2) {
    if(o1 && o2) {
        if(o1.equals) return o1.equals(o2);
        if(o1 != o2
           && typeof(o1) == "object"
           && typeof(o2) == "object") {
            var notempty = false;
            for(var i in o1) {
                if(!equals(o1[i], o2[i])) return false;
                var notempty = true;
            }
            for(var i in o2) {
                return notempty;
            }
            return true;
        }
    }
    return (o1 == o2);
}

function stringValue(v, d) {
    if(typeof(d) == "undefined") var d = null;
    return typeof(v) == "undefined" ? d : (typeof(v) == "string" ? v : "" + v || d);
}

function intValue(v, d) {
    if(typeof(d) == "undefined") var d = null;
    return typeof(v) == "number" ? parseInt(v) : v ? parseInt(v) : d;
}

function floatValue(v, d) {
    if(typeof(d) == "undefined") var d = null;
    return typeof(v) == "number" ? parseFloat(v) : v ? parseFloat(v) : d;
}

function boolValue(v, d) {
    if(typeof(d) == "undefined") var d = null;
    return typeof(v) == "undefined" ? d : v ? true : false;
}

function create(type, args) {
    str = "new " + (type.className || type) + "(";

    if (args.constructor == Array) {
        for (var i = 0; i < args.length; i++) {
            str += "args["+i+"],";
        }
    } else {
        if(typeof(type) == "string") {
            c = window[type];
        } else {
            c = type;
        }
        var p = parameters(c);
        for (var i = 0; i < p.length; i++) {
            str += "args."+p[i]+",";
        }
    }
    return eval(str.replace(/,?$/, ")"));
}

function parameters(func) {
    var m = func.toString().match(/\((.+?)\)/);
    if (m && m.length > 1) {
        return m[1].split(", ");
    } else {
        return [];
    }
}

function property(pName, type, funcs) {
    if(type == Boolean || (typeof(type) == "string" && type.toLowerCase() == "boolean")) {
        var getName = "is" + pName;
    } else {
        var getName = "get" + pName;
    }
    
    if(!this[pName]) this[pName] = null;
    
    var set, get, setFunc, getFunc;
    
    if(funcs) {
        if(funcs.set) {
           setFunc = funcs.set;
        }
        if(funcs.get) {
           getFunc = funcs.get;
        }
    }
    
    if(!setFunc) {
        set = function set(value) {
            if(typeof(value) == "undefined") throw "UndefinedValueException: " + this.__class__.className + ".property." + pName + " takes a '" + (type.className || type.name) + "' value but got a '" + typeOf(value) + "'.";
            if(instanceOf(value, type)) {
                this[pName] = value;
            } else if(value.constructor == Array && typeof(type) != "string") {
                this[pName] = create(type, value);
            } else {
                throw "TypeMismatchException: " + this.__class__.className + ".property." + pName + " takes a '" + (type.className || type.name) + "' value but got a '" + typeOf(value) + "'.";
            }
        }
    } else {
        set = function set(value) {
            if(typeof(value) == "undefined") throw "UndefinedValueException: " + this.__class__.className + ".property." + pName + " takes a '" + (type.className || type.name) + "' value but got a '" + typeOf(value) + "'.";
            if(instanceOf(value, type)) {
                var n = setFunc.call(this, value);
                if(typeof(n) != "undefined") this[pName] = n;
            } else if(value.constructor == Array && typeof(type) != "string") {
                var n = setFunc.call(this, create(type, value));
                if(typeof(n) != "undefined") this[pName] = n;
            } else {
                throw "TypeMismatchException: " + this.__class__.className + ".property." + pName + " takes a '" + (type.className || type.name) + "' value but got a '" + typeOf(n) + "'.";
            }
        }
    }
    
    if(!getFunc) {
        get = function get() {return this[pName];}
    } else {
        get = function get() {
            var n = getFunc.call(this);
            if(instanceOf(n, type)) {
                return n;
            } else {
                throw "TypeMismatchException: " + this.__class__.className + ".property." + pName + " should return a '" + (type.className || type.name) + "' value but returned a '" + typeOf(n) + "'.";
            }
        }
    }
    this.prototype["set" + pName] = set;
    this.prototype[getName] = get;
}

/*
function event(eName, func) {
    if(!this.prototype.__event) this.prototype.__event = {};
    this.prototype.__event[eName] = function event() {
        var a = this.__event[eName].listeners;
        for(var i = 0; i < a.length; i++) {
            a[i].apply((a[i].scope || this), arguments);
        }
    }
    this.prototype.__event[eName].handler = func;
    this.prototype.__event[eName].listeners = new Array();
    this.prototype[eName] = function() {
        var handler = this.__event[eName].handler;
        if(!handler || handler.apply(this, arguments) !== false) {
            this.__event[eName].apply(this, arguments);
        }
    }
}
*/

function arrayCompare(a1, a2) {
    if(a1.length != a2.length) return false;
    for(var i = 0; i < a1.length; i++) {
        if(a1[i] != a2[i]) return false;
    }
    return true;
}

function printf(str) {
	var string = new String(str);
	for(var i = 1; i<arguments.length; i++) {
			string = string.replace("%" + i, arguments[i], "g");
	}
	return string;
}

/**
 * Hook event to an object and unhook after event triggered
 * @param {Object} obj
 * @param {String} e
 * @param {Object} own
 * @param {function} f
 */
function fuseOnce(obj, e, own, f) {
    var df = function() {
        f.apply(own, arguments);
        defuse(obj, e, own, df);
    }
    fuse(obj, e, own, df);
}

/**
 * Hook event to an object
 * @param {Object} obj
 * @param {String} e
 * @param {Object} own
 * @param {function} f
 */
function fuse(obj, e, own, f) {
    var fe = "__fuse__" + e;
	if(!obj[e] || !obj[e].__fuse__) {
	    if(!obj[fe]) {
    		if(typeof(obj[e]) == "function") {
        		obj[fe] = new Array({o:obj,f:obj[e]});
    		} else {
        		obj[fe] = new Array();
    		}
	    }
    	obj[e] = function() {
    	    var a = obj[fe];
	      	for(var i=0; i<a.length; i++ ) {
				a[i].f.apply(a[i].o, arguments);
			}
		}
		obj[e].__fuse__ = true;
	}
	obj[fe].push({o:own,f:f});
}

/**
 * Unhook event from an object
 * @param {Object} obj
 * @param {String} e
 * @param {Object} own
 * @param {function} f
 */
function defuse(obj, e, own, f) {
    var fe = "__fuse__" + e;
    if(obj[fe]) {
	    var a = obj[fe];
      	for(var i=0; i<a.length; i++ ) {
    		if(a[i].f == f
    		&& a[i].o == own)
    		    a.splice(i,1);
    	}
    }
}

function syncronize(obj, e , f, c) {
    if(c) {
        f();
    } else {
        hookOnce(obj, e, f);
    }
}

/**
 * Hook event to an object and unhook after event triggered
 * @param {Object} obj
 * @param {String} e
 * @param {function} f
 */
function hookOnce(obj, e, f) {
    var uh = function() {
        f.apply(obj, arguments);
        unhook(obj, e, uh);
    }
    hook(obj, e, uh);
}

/**
 * Hook event to an object
 * @param {Object} obj
 * @param {String} e
 * @param {function} f
 */
function hook(obj, e, f) {
    var he = "__hook__" + e;
	if(!obj[he]) {
		if(typeof(obj[e]) == "function") {
    		obj[he] = new Array(obj[e]);
		} else {
    		obj[he] = new Array();
		}
    	obj[e] = function() {
    	    var a = obj[he];
	      	for(var i = 0; i < a.length; i++) {
				a[i].apply(obj, arguments);
			}
		}
	}
	obj[he].push(f);
}

/**
 * Unhook event from an object
 * @param {Object} obj
 * @param {String} e
 * @param {function} f
 */
function unhook(obj, e, f) {
    var he = "__hook__" + e;
    if(obj[he]) {
    	var a = obj[he];
      	for(var i = 0; i < a.length; i++) {
    		if(a[i] == f) {
    		    a.splice(i,1);
    		}
    	}
    }
}

/**
 * Listen to a declared event
 * @param {Object} obj The event owner
 * @param {String} eName The GJSF event name
 * @param {function} f The function to execute on the event trigger
 * @param {Object} s The scope to execute the given function in
 */
/*
function listen(obj, eName, f, s) {
    var event = obj.__event[eName];
    event.listeners.push(f);
    if(s) f.scope = s;
}
*/

/**
 * Remove listen from a declared event
 * @param {Object} obj The event owner
 * @param {String} eName The GJSF event name
 * @param {function} f The function to execute on the event trigger
 * @param {Object} s The scope to execute the given function in
 */
/*
function unlisten(obj, eName, f, s) {
    var l = obj.__event[eName].listeners;
    for(var i = 0; i < l.length; i++) {
        if(l[i] == f && (!s || l[i].scope == s)) {
            l.splice(i,1);
        }
    }
}
*/

function staticConstructor(clazz, func) {
    gjsf.myStaticConstructors.push(function() {func.call(clazz);});
}

/**
 * Declare a new class
 * @param {String} className
 * @param {Class} superClass
 */
function declareClass(className, superClass) {
    if(!window[className] || typeof(window[className]) != "function") {
        if(superClass) {
            window[className] = function() {
                this.construct.apply(this, arguments);
            }
        } else {
            window[className] = function() {}
        }
    } 

    if(gjsf.myDebug) window[className] = gjsf.getDebugFunction(window[className], className);
    window[className].prototype.toString = function(){return className};
    window[className].prototype.__class__ = window[className];
    window[className].className = className;
    window[className].property = property;
    //window[className].event = event;

    if(superClass) {
        window[className].__inheritance__ = className + "|";
        if(superClass.className) superClass = superClass.className;
        gjsf.xtend(window[className], superClass);
    } else {
        window[className].__inheritance__ =  "Object||" + className + "|";
    }
    
    gjsf.classLoaded(className);
}

/**
 * Declare a new static class
 * @param {String} className
 */
function declareStaticClass(className) {
    if(typeof(window[className]) == "function") {
        gjsf.myStaticConstructors.push(window[className]);
    }

    window[className] = {};
    window[className].className = className;
    
    gjsf.classLoaded(className);
}

/**
 * Declare a new abstract class
 * @param {String} className
 * @param {Class} superClass
 */
function declareAbstractClass(className, superClass) {
    if(typeof(window[className]) == "function") {
        var classConstructor = window[className];
        window[className] = function() {
            if(this.__class__.className == className) {
                throw(className + " is abstract.");
            }
            classConstructor.apply(this, arguments);
        }
    } else {
        window[className] = function() {
            if(this.__class__.className == className) {
                throw(className + " is abstract.");
            }
            if(this.construct) this.construct.apply(this, arguments);
        }
    }
    declareClass(className, superClass);
}

/**
 * Declare a new final class
 * @param {String} className
 * @param {function} classConstructor
 * @param {Class} superClass
 */
function declareFinalClass(className, superClass) {
    declareClass(className, superClass);
    window[className].__final__ = true;
}

function declareContract(contractName) {
    declareClass(contractName);
    gjsf.myContracts[contractName] = true;
}

function finalize(f) {
    f.__final__ = true;
    return f;
}

function strapCss(path) {
    var link = document.createElement("link");
    link.type = "text/css";
    link.rel = "stylesheet";
    
    var url = "";
    for(var i in gjsf.myClassPaths) {
        if(path.indexOf(i.replace(/\./g, "/") + "/") == 0) {
            url = gjsf.myClassPaths[i] + path;
            break;
        }
    }
    if(!url) url = path;
    link.href = url;
    gjsf.getHeadElement().appendChild(link);
}

function strap(path) {
    if(/\.js$/.test(path) && path.indexOf("/") != -1) {
        var url = "";
        for(var i in gjsf.myClassPaths) {
            if(path.indexOf(i.replace(/\./g, "/") + "/") == 0) {
                url = gjsf.myClassPaths[i] + path;
                break;
            }
        }
        if(!url) url = path;
        gjsf.openScript(url);
    } else {
        var url = "";
        for(var i in gjsf.myClassPaths) {
            if(path.indexOf(i + ".") == 0) {
                url = gjsf.myClassPaths[i] + path.replace(/\./g,"/");
                break;
            }
        }
        if(!url) {
           for(var i in gjsf.myClassPaths) {
               url = gjsf.myClassPaths[i] + (i + "." + path).replace(/\./g,"/");
               break;
           }
        }
        var className = url.substring(url.lastIndexOf("/")+1, url.length);
        url += ".js";
        if(!window[className]) {
            window[className] = {className:className};
            gjsf.myPendingClasses[className] = true;
            if(gjsf.openScript(url)) {
                gjsf.myLoadCount++;
            }
        }
    }
}

function contract(url) {
    var className = url.substring(url.lastIndexOf("/")+1, url.length);
    var lastDot = className.indexOf(".");
    className = className.substring(0, (lastDot != -1 ? lastDot : className.length));
    if(!window[className]) {
        window[className] = className;
        gjsf.myPendingClasses[className] = true;
        if(gjsf.openScript(url)) {
            gjsf.myLoadCount++;
        }
    }
}

function isClassLoaded(className) {
    return (gjsf.myPendingClasses[className] === false || typeof(window[className]) == "function");
}

function isClassStrapped(className) {
    return (typeof(gjsf.myPendingClasses[className]) != "undefined");
}

function addClassPath(packagePrefix, classPath) {
    if(classPath) {
        gjsf.myClassPaths[packagePrefix] = classPath.replace(new RegExp("\/*$"), "/");
    }
}

function getDefaultCharset() {
    if(typeof(document.characterSet) == "string") {
        return document.characterSet;
    }
    var charset = null;
    var metaTags = document.getElementsByTagName("meta");
    for(var i = 0; i < metaTags.length; i++) {
        charset = metaTags[i].content.match(/^.*charset=([\w\d\-]+);?.*$/i);
        if(charset && charset.length > 1) {
            return charset[1];
        }
    }
    return "";
}

gjsf = {};

gjsf.myScripts = {};
gjsf.myPendingClasses = {};
gjsf.myLoadCount = 0;
gjsf.myLoadTimer = null;
gjsf.myScriptsToStrap = new Array();
gjsf.myIsStrapInitiated = false;
gjsf.myHeadElement = null;
gjsf.myStackTrace = new Array();
gjsf.myXtends = new Array();
gjsf.myIsXtendInitiated = false;
gjsf.myStaticConstructors = new Array();
gjsf.myContracts = {};

gjsf.myClassPaths = {};

/**
 * Extend a class by another
 * @param {String} student The class to inherit from the teacher
 * @param {String} teacher The class to inherit to the student
 */
gjsf.xtend = function(student, teacher) {
    gjsf.myXtends.push({"student":student, "teacher":teacher});
}

gjsf.buildTree = function(student, teacher) {
    if(!teacher.__final__) {
        student.__teacher__ = teacher;
        if(!teacher.__tree__) {
            teacher.__tree__ = new Array();
        }
        teacher.__tree__.push(student);
    } else {
        throw new Exception(teacher + " is declared as final.");
    }
}

gjsf.buildClass = function(student) {
    var teacher = student.__teacher__;

    if(teacher && !student.__built__) {
        for(var i in teacher.prototype) {
            var s = student.prototype[i];
            var t = teacher.prototype[i];
            
		    if(t && t.__final__ && s) {
		        throw new Exception(teacher.className + "." + i + " is declared as final.");
		    }
		    if(typeof(s) == "undefined") {
			    student.prototype[i] = t;
		    } else if(typeof(s) == "function" && typeof(t) == "function") {
		        //if(teacher.prototype.__event && teacher.prototype.__event[i]) {
		        //TODO: implement 'event' method override
		        //}
		        s.__base__ = teacher;
            }
        }
        
        try {
            if(teacher.prototype.toString() != teacher.className && student.prototype.toString() == student.className)  {
                student.prototype.toString = teacher.prototype.toString;
            }
        } catch(ex) {}
        
        student.__inheritance__ = teacher.__inheritance__ + "|" + student.__inheritance__;
        student.prototype.base = gjsf.base;
        student.prototype.construct = gjsf.construct;
        student.prototype.getConstruct = gjsf.getConstruct;
        student.prototype.getClass = gjsf.getClass;
    }

    if(student.__tree__) {
        for(var i = 0; i < student.__tree__.length; i++) {
            gjsf.buildClass(student.__tree__[i]);
        }
        delete(student.__tree__);
    }
    
    student.__built__ = true;
}

gjsf.initXtend = function() {
    for(var i = 0; i < gjsf.myXtends.length; i++) {
        //if(typeof(gjsf.myXtends[i].student) == "string") 
            gjsf.myXtends[i].student = eval(gjsf.myXtends[i].student);
        //if(typeof(gjsf.myXtends[i].teacher) == "string")
            gjsf.myXtends[i].teacher = eval(gjsf.myXtends[i].teacher);
        gjsf.buildTree(gjsf.myXtends[i].student, gjsf.myXtends[i].teacher);
    }
    for(var i = 0; i < gjsf.myXtends.length; i++) {
        var teacher = gjsf.myXtends[i].teacher;
        if((teacher.__tree__ && teacher.__built__) || !teacher.__teacher__)
            gjsf.buildClass(teacher);
    }
    gjsf.myXtends = new Array();
    gjsf.myIsXtendInitiated = true;
}

/**
 * @private
 * @param {String} f
 * @param {Class} specific
 */
 /*
gjsf.base = function(f, specific) {
	var stud = this;
	if(stud[f].__stack__) {
  		var teach = stud[f].__stack__;
		stud[f].__stack__ = teach.prototype[f].__base__;
	} else {
		var teach = null;
		if(specific) stud[f].__stack__ = specific;
		else stud[f].__stack__ = stud.__class__.prototype[f].__base__;
	}
	return function() {
		var value = stud[f].__stack__.prototype[f].apply(stud, arguments);
		stud[f].__stack__ = teach;
		return value;
	}
}*/
gjsf.base = function(f, specific) {
	var stud = this;
	if(!stud.__stack__) stud.__stack__ = {};
	if(stud.__stack__[f]) {
  		var teach = stud.__stack__[f];
		stud.__stack__[f] = teach.prototype[f].__base__;
	} else {
		var teach = null;
		if(specific) stud.__stack__[f] = specific;
		else stud.__stack__[f] = stud.__class__.prototype[f].__base__;
	}
	return function() {
	    try {
    		return stud.__stack__[f].prototype[f].apply(stud, arguments);
	    //} catch(ex) {
	    } finally {
		    stud.__stack__[f] = teach;
	    }
	}
}

/**
 * @private
 */
gjsf.construct = function() {
	if(!this.__construct__) this.__construct__ = this.__class__.__teacher__;
	else this.__construct__ = this.__construct__.__teacher__;
	this.__construct__.apply(this, arguments);
	delete(this.__construct__);
}

/**
 * @private
 * @param {Class} c
 */
gjsf.getConstruct = function(c) {
    var stud = this;
    stud.__construct__ = c;
    return function() {
        stud.__construct__.apply(stud, arguments);
        delete(stud.__construct__);
    }
}

gjsf.getClass = finalize(function() {
    return this.__class__;
});

gjsf.setDebug = function(debug) {
    gjsf.myDebug = debug;
}

gjsf.isDebug = function(debug) {
    return gjsf.myDebug;
}

gjsf.getHeadElement = function() {
    if(!gjsf.myHeadElement) {
        gjsf.myHeadElement = document.getElementsByTagName("head")[0];
    }
    return gjsf.myHeadElement;
}

gjsf.getPendingClasses = function() {
    return gjsf.myPendingClasses;
}

gjsf.getStackTrace = function() {
    return gjsf.myStackTrace.concat().reverse();
}

gjsf.isInitiated = function() {
    return gjsf.myIsXtendInitiated;
}

gjsf.initStrap = function() {
    gjsf.myIsStrapInitiated = true;
    if(gjsf.myScriptsToStrap.length > 0) {
        for(var i=0; i<gjsf.myScriptsToStrap.length; i++) {
            gjsf.openScript(gjsf.myScriptsToStrap[i]);
        }
    } else {
        clearTimeout(gjsf.myLoadTimer);
        gjsf.myLoadTimer = setTimeout(
                function() {
                	gjsf.loaded();
                }, 1);
    }
}

gjsf.openScript = function(url) {
    if(!gjsf.myScripts[url]) {
        if(gjsf.myIsStrapInitiated) {
            gjsf.myScripts[url] = true;
        	var script = document.createElement("script");
            script.type = "text/javascript";

            script.onerror = gjsf.onerror;

            script.src = url;

        	gjsf.getHeadElement().appendChild(script);
        } else {
            gjsf.myScriptsToStrap.push(url);
        }
        return true;
    }
    return false;
}

gjsf.classLoaded = function(className) {
    if(gjsf.myPendingClasses[className]) {
        
        gjsf.myPendingClasses[className] = false;
        gjsf.myLoadCount--;
        setTimeout(function(){gjsf.onclassload(className);}, 0);
        clearTimeout(gjsf.myLoadTimer);
        gjsf.myLoadTimer = setTimeout(
                function() {
                    if(gjsf.myLoadCount == 0) {
                        gjsf.loaded();
                    }
                }, 1);
    }
}

gjsf.execStaticConstructors = function() {
   for(var i = 0; i < gjsf.myStaticConstructors.length; i++) {
       gjsf.myStaticConstructors[i]();
   }
   gjsf.myStaticConstructors = new Array();
}

gjsf.loaded = function() {
    var b = gjsf.myIsXtendInitiated;

    if(gjsf.myDebug) {
        gjsf.initDebug();
    }
    
    gjsf.initXtend();
    gjsf.execStaticConstructors();
    
    if(!b) {
        gjsf.triggerOnLoad();
    } else {
        clearTimeout(gjsf.myLoadTimer);
        gjsf.myLoadTimer = setTimeout(gjsf.onlateload, 1);
    }
}


gjsf.initDebug = function() {
    for(var className in gjsf.myPendingClasses) {
        var c = window[className];
        for(var i in c.prototype) {
            if(typeof(c.prototype[i]) == "function" && i != "__class__" && i != "__original__") {
                c.prototype[i] = gjsf.getDebugFunction(c.prototype[i], className + ".prototype." + i + "()");
            }
        }
        for(var i in c) {
            if(typeof(c[i]) == "function" && i != "__class__" && i != "__original__") {
                c[i] = gjsf.getDebugFunction(c[i], className + "." + i + "()");
            }
        }
    }
}

gjsf.getDebugFunction = function(f, fName) {
    var debugFunction = function() {
        gjsf.myStackTrace.push(arguments.callee.functionName);
        try {
            var r = arguments.callee.__original__.apply(this, arguments);
        } catch(ex) {
            if(typeof(ex) == "string") ex = new Exception(ex);
            throw ex;
        } finally {
            gjsf.myStackTrace.pop();
        }
        return r;
    }
    debugFunction.functionName = fName;
    debugFunction.__original__ = f;
    return debugFunction;
}


gjsf.triggerOnLoad = function() {
    if(typeof(gjsf.onload) == "function") {
        gjsf.onload();
    } else if(typeof(gjsf.kojaMain) == "function") {
        kojaMain();
    } else {
        setTimeout(function() {
            gjsf.triggerOnLoad();
        }, 100);
    }
}

gjsf.onlateload = function() {}
gjsf.onclassload = function(className) {}

gjsf.onerror = function(err) {
    throw(err + " - " + this.src);
}

try{
    if(window.attachEvent){
    	window.attachEvent("onload", gjsf.initStrap);
    }else{
    	window.addEventListener("load", gjsf.initStrap,false);
    } 
} catch (ex) {
    if(typeof(window.onload) != "undefined") {
    	var tmpOnLoad = window.onload;
        window.onload = function() {
            gjsf.initStrap();
    		window.onload = tmpOnLoad;
    		tmpOnLoad = null;
    		try{
    		    window.onload();
    		} catch(ex) {}
        }
    } else {
        window.onload = function() {
            gjsf.initStrap();
        }
    }
}

