遅延ロード可能バージョン

たとえばこんなふうに


Namespace("org.example.net").define(function(ns){
    ns.provide({
        HTTPRequest : function(){ console.log("ok");},
        HTTPResponse : function(){ console.log("ok");},
    });
});


Namespace("org.example.system").define(function(ns){
    setTimeout(function(){
    ns.provide({
        Console : {
            log : function(item){console.log(item)}
        },
    });
    },1000);
});


Namespace("org.example.application")
.use('org.example.net *')
.apply(function(ns){with(ns){
    var req = new HTTPRequest;
    var res = new HTTPResponse;
}});

Namespace("org.example.application")
.use('org.example.net *')
.use('org.example.system Console')
.apply(function(ns){with(ns){
    var req = new HTTPRequest;
    var res = new HTTPResponse;
    Console.log("ok");
}});


Namespace("org.example.application")
.use('org.example.net *')
.use('org.example.system Console')
.apply(function(ns){with(ns){
    var req = new HTTPRequest;
    var res = new HTTPResponse;
    Console.log("ok");
}});

defineで予約されたものはns.provideを継続として保持している。そのため非同期にns.provideを呼び出しても
use and apply時に順序だって遅延評価される。

たとえば、xhr/createElement('script')で外部のリソースを取得するような場合にも使用できる。
あるいは、DOMContentLoadedまでまた無いとprovideできないようなリソースが存在する場合などにもok。

var Namespace = (function(){
    /* utility */
    var merge = function(aObj,bObj){
        for( var p in bObj ){
            if( bObj.hasOwnProperty( p ) ){
                aObj[p] = bObj[p];
            }
        }
        return aObj;
    };
    var _assertValidFQN = function(fqn){
        if( !fqn.match(/^[a-z][a-z0-9.]*[a-z0-9]?$/) ){
            throw('Invalid namespace');
        }
    };
    var Namespace = (function(){
        var nsCache = {};
        var nsList  = [];


        /* constructor*/
        var Klass = function _Private_Class_Of_Namespace(fqn){
            this.stash   = {
                CURRENT_NAMESPACE : fqn
            };
            this.name = fqn;
            this.contexts = [];
            this.isLoading = false;
            this.callbacks = [];
        };
        
        (function(){
            var justThrow = function(){
                throw('do not call provide twice');
            };
            this.merge = function(obj){
                merge( this.stash,obj);
                return this;
            };
            this.registerContext = function(context,callback){
                this.contexts.push({
                    context :context , callback : callback
                });
            };
            this.load = function(finishedCallback){
                this.callbacks.push(finishedCallback);
                if(!this.isLoading){ 
                    this.isLoading = true;
                    this._load();
                }
            };
            this._load = function(){
                var _self = this;

                var unused = this.contexts[0];
                if( !unused ){
                    this.isLoading = false;
                    for(var i = 0,l= this.callbacks.length;i<l;i++){
                        this.callbacks[i]();
                    }
              
                    this.callbacks=[];
                    return;
                }
                var callback = unused.callback;
                var context  = unused.context;
                context.load(function(ns){
                    ns.provide = function(obj){
                        ns.provide = justThrow;
                        _self.contexts.shift();
                        _self.merge(obj)._load();
                    };
                    callback(ns);
                });
            };
        }).apply(Klass.prototype); 
         
        var _loadStash = function(ns){
            if(!nsCache[ns]) throw('undefined namespace:' + ns);
            return nsCache[ns].stash;
        };
        var _loadStashItem = function(ns,itemName){
            var nsStash = _loadStash(ns);
            if( typeof(nsStash[itemName]) === 'undefined' ) throw('undefined item:' + ns + '#' + itemName);
            return nsStash[itemName];
        }
        return {
            loadStash     : _loadStash,
            loadStashItem : _loadStashItem,
            create :function(fqn){
                _assertValidFQN(fqn);
                if( nsCache[fqn] )
                    return nsCache[fqn];
                var ns = nsCache[fqn] = new Klass(fqn);
                return ns;
            }
        };
    })();
    
    var NamespaceContext = (function(){
        var Klass = function _Private_Class_Of_NamespaceContext(namespaceObject){

            this.namespaceObject = namespaceObject;
            this.callbacks = [];
            this.requires  = [];
            this.useList   = [];
        };
        var _mergeUseData = (function(){
            var _loadImport = function(ns,imports){
                var retStash = {};
                for(var i = 0,l=imports.length;i<l;i++){
                    var importSyntax = imports[i];
                    if( importSyntax === '*' ) return Namespace.loadStash(ns);
                    retStash[importSyntax] = Namespace.loadStashItem(ns,importSyntax);
                }
                return retStash;
            };
            var _mergeWithNS = function(stash,ns){
                var nsList = ns.split(/\./);
                var current = stash;
                for(var i = 0,l=nsList.length;i<l-1;i++){
                    if( !current[nsList[i]] ) current[nsList[i]] = {};
                    current = current[nsList[i]];
                }
                var lastLeaf = nsList[nsList.length-1];
                if( current[lastLeaf] )
                   return merge( current[lastLeaf] , Namespace.loadStash(ns) );
                return current[lastLeaf] = Namespace.loadStash(ns);
            };
            return function(stash,useData){
                if( useData.imports ){
                    merge( stash , _loadImport(useData.ns,useData.imports));
                }else{
                    _mergeWithNS(stash,useData.ns);
                }
            };
        })();
        (function(){
            this.use = function(syntax){
                var splittedUseSyntax = syntax.split(/\s/);
                var fqn = splittedUseSyntax[0];
                var imp = splittedUseSyntax[1];
                var importNames = (imp) ? imp.split(/,/): null;
                _assertValidFQN(fqn);
                this.requires.push(Namespace.create(fqn));
                this.useList.push({ ns: fqn,imports: importNames });
                return this;
            };
            this.createContextualObject = function(){
                var useList = this.useList;
                var retObj  = {};
                for(var i=0,l=useList.length;i<l;i++) {
                    _mergeUseData(retObj,useList[i])
                }
                return retObj;
            };
            this.define = function(callback){
                this.namespaceObject.registerContext(this,callback);
                return this.namespaceObject;
            };
            this.load = function(callback){
                var _self    = this;
                var require  = this.requires.shift();
                if( !require ){
                    return callback( _self.createContextualObject() )
                }
                require.load(function(){
                    _self.load(callback);
                });
            };
            this.apply = function(callback){
                var namespaceObject = this.namespaceObject;
                this.use( namespaceObject.name + ' *');
                this.load(function(ns){
                    callback(ns);
                });
            };
        }).apply(Klass.prototype);
        return Klass;
    
    })();
    return function(nsString){
        return new NamespaceContext(Namespace.create(nsString));
    };
})();