// ネームスペース if ( typeof container == "undefined" ) { container = {}; } container.VERSION = "0.4.0"; /** * コンテナ * @param {Function} module コンポーネント定義を行う関数。 */ container.Container = function ( module ) { var binder = new container.Binder( {} ); module( binder ); this.defs = binder.defs; // 循環生成防止用のフィールド。作成中のモジュールの一覧が入る。 this.creating = []; // 名前でキャッシュを作っておく。 // EagerSingletonなオブジェクトがあれば生成。 this.typeCache = {}; this.nameCache = {}; var thiz = this; this.eachComponentDef( function( def ) { if ( def && def[container.Annotation.Container] && def[container.Annotation.Container][container.Annotation.Scope] == container.Scope.EagerSingleton ) { thiz.create( def ); } // 名前でキャッシュ if ( def && def[container.Annotation.Container] && def[container.Annotation.Container][container.Annotation.Name] ) { var name = def[container.Annotation.Container][container.Annotation.Name]; if ( !thiz.nameCache[name] ) { thiz.nameCache[name] = [] } thiz.nameCache[name].push( def ); } }); } container.Container.prototype = { /** * コンポーネント名またはcontainer.Typeに対応するオブジェクトを取得します。 * * @param {String or container.Type} nameOrType コンポーネント名またはcontainer.Type * @return 対応するオブジェクト。複数のコンポーネントがマッチした場合、最初の1つを返します。 */ get: function( nameOrType ){ if ( nameOrType instanceof container.Type ) { // キャッシュがなければスキャン if ( !this.typeCache[nameOrType] ) { this._createTypeCahce( nameOrType ); } if ( this.typeCache[nameOrType].length > 0 ) { return this.create( this.typeCache[nameOrType][0]); } else { throw container.createError( new Error(), container.ErrorCode.ComponentNotFound, "component not found.name=" + nameOrType, {"nameOrType":nameOrType} ); } } else { if ( this.nameCache[nameOrType] && this.nameCache[nameOrType].length > 0 ) { return this.create( this.nameCache[nameOrType][0]); } else { throw container.createError( new Error(), container.ErrorCode.ComponentNotFound, "component not found.name=" + nameOrType, {"nameOrType":nameOrType} ); } } }, /** * コンポーネント名またはcontainer.Typeに対応するオブジェクトをすべて取得します。 * * @param {String or container.Type} nameOrType コンポーネント名またはcontainer.Type * @return 対応するオブジェクトの配列 */ gets: function( nameOrType ){ var objects = []; if ( nameOrType instanceof container.Type ) { // キャッシュがなければスキャン if ( !this.typeCache[nameOrType] ) { this._createTypeCahce( nameOrType ); } var defs = this.typeCache[nameOrType] for ( var i=0; i < defs.length; i++ ) { objects.push( this.create( defs[i]) ); } } else { if ( this.nameCache[nameOrType] ) { var defs = this.nameCache[nameOrType]; for ( var i=0; i < defs.length; i++ ) { objects.push( this.create( defs[i]) ); } } } return objects; }, /** * コンテナを破棄します。 */ destroy: function( ) { var thiz = this; this.eachComponentDef( function( def ) { if ( !def.instance ){ return; } var obj = def.instance; var config = def[container.Annotation.Container]; if ( !config || !config[container.Annotation.Destroy] ) { return; } var destroy = config[container.Annotation.Destroy]; if ( typeof destroy == "string" ) { obj[destroy].apply( obj, [this] ); } else if ( typeof destroy == "function" ) { destroy( obj, thiz ); } else { throw container.createError( new Error(), container.ErrorCode.IllegalDefinition, "illegal destroy method. string or function is supported.", {"def":def} ); } def.instance = null; }); }, /** * コンポーネント定義からコンポーネントを生成する。 * @param {Hash} def コンポーネント定義 */ create: function( def ) { // 既に作成済みのインスタンスがあればそれを返す。 if ( def.instance ) { return def.instance; } // 循環チェック if ( this._isCreating(def) ) { throw container.createError( new Error(), container.ErrorCode.CircularReference, "circulative component creation.", {"def":def} ); } try { this.creating.push( def ); var obj = def.constractor( this ); // 生成 def.instance = obj; // キャッシュ // アノーテョンで指定した設定とコンテナのコンポーネント設定をマージ var config = def[container.Annotation.Container] || {}; // 自動インジェクション if ( config[container.Annotation.AutoInjection] != false ) { for ( var property in obj ) { if ( obj[property] instanceof container.inner.Component ) { obj[property] = this.get( obj[property].name ); } else if ( obj[ property] instanceof container.inner.Components ) { obj[property] = this.gets( obj[property].name ); } else if ( obj[ property] === container.Inject ) { obj[property] = this.get( property ); } else if ( obj[ property] === container.Injects ) { obj[property] = this.gets( property ); } } } // プロパティインジェクション if ( config[container.Annotation.Inject] ) { var inject = config[container.Annotation.Inject]; for ( var f in inject ) { if ( inject[f] instanceof container.inner.Component ) { obj[f] = this.get( inject[f].name ); } else if ( inject[f] instanceof container.inner.Components ) { obj[f] = this.gets( inject[f].name ); } else if ( inject[f] instanceof container.inner.Provider ) { obj[f] = inject[f].func( obj, this ); } else { obj[f] = inject[f]; } } } // 初期化関数の実行 if ( config[container.Annotation.Initialize] ) { var initialize = config[container.Annotation.Initialize]; if ( typeof initialize == "string" ) { obj[initialize].apply( obj, [this] ); } else if ( typeof initialize == "function" ) { initialize( obj, this ); } else { throw container.createError( new Error(), container.ErrorCode.IllegalDefinition, "illegal initialize method. string or function is supported.", {"def":def} ); } } // インターセプタの設定 if ( config[container.Annotation.Intercept] ) { var interceptors = config[container.Annotation.Intercept]; for ( var i=0; i < interceptors.length; i++ ) { this.applyInterceptor( obj, interceptors[i][0], interceptors[i][1] ); } } // グローバルインターセプタの設定 if ( this.defs.interceptors && def.componentType != "function" ) { // バインドメソッドには適用しない。 for ( var i=0; i < this.defs.interceptors.length; i++ ) { var insterceptor = this.defs.interceptors[i]; if ( !insterceptor.nameMatcher) { continue; } if ( insterceptor.nameMatcher instanceof container.Type && insterceptor.nameMatcher.isImplementor(obj) ) { this.applyInterceptor( obj, insterceptor.interceptor, insterceptor.methodMatcher ); } else if ( insterceptor.nameMatcher instanceof container.Matcher && config[container.Annotation.Name] && insterceptor.nameMatcher.match( config[container.Annotation.Name] )) { this.applyInterceptor( obj, insterceptor.interceptor, insterceptor.methodMatcher ); } } } // シングルトンの場合、次回同じインスタンスを返すのでコンポーネント定義にキャッシュしたままにしておく。 // プロトタイプの場合破棄 if ( config[container.Annotation.Scope] == container.Scope.Prototype ) { def.instance = undefined; } return obj; } catch ( error ) { def.instance = undefined; // エラーの場合破棄 throw error; } finally { this.creating.pop(); } }, /** * インターセプターを適用する。 * @param {Object} target 適用対象のオブジェクト */ applyInterceptor: function( target, interceptor, matcher ) { if ( !interceptor || !matcher ) { return; } for ( var f in target ) { if ( typeof target[f] == "function" && matcher.match( f ) ) { (function() { // f をローカル化するため関数で。 var x = f; var original = target[x]; target[x] = function( ) { // インターセプターを実行する関数に置き換える。 var mi = new container.MethodInvocation( x, original, target, arguments ); return interceptor( mi ); } })(); } } }, /** * コンポーネント定義を列挙する。 * @param {Function} block 列挙先。第1引数でコンポーネント定義が渡される。 */ eachComponentDef : function( block ) { for ( var i = 0; i < this.defs.objects.length; i++ ) { if ( block ) { block.apply( null, [this.defs.objects[i]] ); } } }, _createTypeCahce: function( nameOrType ) { var list = []; var self = this; this.eachComponentDef( function( def ){ // 循環する場合は対象に含めない。 if ( self._isCreating(def) && !def.instance ) { return } var obj = self.create( def ); if ( nameOrType.isImplementor( obj ) ) { list.push( def ); } }); this.typeCache[nameOrType] = list; }, _isCreating: function(def) { for ( var i=0; i < this.creating.length; i++ ) { if ( def === this.creating[i] ) { return true; } } return false; } } /** * バインダー * @param {Hash} defs コンポーネント定義 * @param {String} namespace ネームスペース */ container.Binder = function( defs, namespace ){ this.defs = defs; this.namespace = namespace; } container.Binder.prototype = { /** * コンポーネントを登録する * @param {Function} clazz コンポーネントクラス * @return コンポーネント定義オブジェクト */ bind: function( clazz ) { return this._bind( "object", clazz.prototype.meta, function() { return new clazz(); }); }, /** * 条件にマッチするコンポーネントメソッドを登録する。 * * - Typeまたは名前にマッチするコンポーネントの指定したメソッドをコンポーネントとして登録する。 * - 複数のコンポーネントがある場合、最初に見つかったTypeまたは名前にマッチするコンポーネントのメソッドが登録される。 * * @param {String or container.Type} nameOrType コンポーネント名またはcontainer.Type * @param {String} methodName メソッド名 * @return コンポーネント定義オブジェクト */ bindMethod: function( nameOrType, methodName ) { var self = this; return this._bind( "function", null, function( container ) { var obj = container.get( nameOrType ); if (!obj) { throw container.createError( new Error(), container.ErrorCode.ComponentNotFound, "component not found.name=" + nameOrType, {"nameOrType":nameOrType} ); } return self._createBindMethod( obj, methodName ); }); }, /** * 条件にマッチするコンポーネントメソッドの配列を登録する。 * * - Typeまたは名前にマッチするコンポーネントの指定したメソッドをコンポーネントとして登録する。 * - 複数のコンポーネントがある場合、Typeまたは名前にマッチするコンポーネントメソッドの配列が登録される。 * * @param {String or container.Type} nameOrType コンポーネント名またはcontainer.Type * @param {String} methodName メソッド名 * @return コンポーネント定義オブジェクト */ bindMethods: function( nameOrType, methodName ) { var self = this; return this._bind( "function", null, function( container ) { var objs = container.gets( nameOrType ); var list = []; for ( var i=0; i} or {Function} includes * マッチ条件。 * 正規表現、正規表現の配列、または関数で指定する。 * -正規表現 .. 値が正規表現にマッチするか * -正規表現の配列 .. 値が正規表現のいずれかにマッチするか * -関数 .. 関数の実行結果がtrueであるか。(引数として評価対象の値が渡される。) * @param {RegExp} or {Array} or {Function} excludes * マッチ対象外を指定する条件。includeに含まれていてもexcludeにマッチする場合、マッチしないと見なされる。 * 正規表現、正規表現の配列、または関数で指定する。 * -正規表現 .. 値が正規表現にマッチするか * -正規表現の配列 .. 値が正規表現のいずれかにマッチするか * -関数 .. 関数の実行結果がtrueであるか。(引数として評価対象の値が渡される。) */ container.Matcher = function ( includes, excludes ){ if ( includes && !(includes instanceof Array || includes instanceof RegExp || includes instanceof Function )){ throw container.createError( new Error(), container.ErrorCode.IllegalArgument, "Illegal includes.", {} ); } if ( excludes && !(excludes instanceof Array || excludes instanceof RegExp || excludes instanceof Function )){ throw container.createError( new Error(), container.ErrorCode.IllegalArgument, "Illegal excludes.", {} ); } this.excludes = excludes; this.includes = includes; } container.Matcher.prototype = { /** * 評価値が条件にマッチするか評価する。 * @param {String} value 評価値 * @return マッチする場合true */ match: function( value ){ if ( this.excludes && this.getEvaluator( this.excludes)(value) ) { return false; } if ( this.includes && this.getEvaluator( this.includes)(value) ) { return true; } return false }, getEvaluator: function( includes ) { if ( includes instanceof Array ){ return function( value ) { for (var i=0; i