= BindingModule.scala = 2011/7/2時点のソースのコメントを日本語訳(予定) {{{ package org.scala_tools.subcut.inject /* * Created by IntelliJ IDEA. * User: dick * Date: 2/17/11 * Time: 11:39 AM */ import scala.collection._ /** * バインディングキー。クラスとオプションの名称によって所望のインジェクションを一意に識別するためのもの。 */ private[inject] case class BindingKey(val clazz: Class[Any], val name: Option[String]) /** * メインのBindingModuleトレイト * 直接使用は想定していない。代わりに、バインディング生成ファンクションを伴うNewBindingModuleを使う * (推奨される方法である。結果はイミュータブルになる)か、あるいはMultiableBindingModule * (何をしているのか理解していない限りは推奨されない。そしてスレッドセーフティに責任を持つこと)。 */ trait BindingModule { outer => /** 抽象的なバインディングマップ定義 */ def bindings: immutable.Map[BindingKey, Any] /** * このモジュールを他とconsする(つまり、マージした結果を返す)。 * 結果のモジュールは両者のすべてのバインディングを保持するが、ただし、共通のバインディングがある場合は、 * こちらのモジュールのものが勝つ(バインディングオーバライド)。 * @param other このモジュールとconstされるもう一つのBindingModule。重複があれば、こちらのものが * otherのものよりも優先される。 */ def ::(other: BindingModule): BindingModule = { new BindingModule { override val bindings = outer.bindings ++ other.bindings } } /** * このバインディングのミュータブルなコピーを渡された関数に与える。これにより、その関数のスコープ内でのみ * バインディングをオーバライドすることができる。テスト時に有効である。 * @param fn 新たなミュータブルなコピーを取得し、そのスコープ内でそれを使用する。いかなる型を返すことも * 可能で、この関数から返された型が、この関数(メソッド)から返される型となる。 * @return 供給された関数から返された値 */ def modifyBindings[A](fn: MutableBindingModule => A): A = { val mutableBindings = new MutableBindingModule { bindings = outer.bindings } fn(mutableBindings) } /** * 指定されたクラスと名称オプションについてインジェクトする。クラスに何がバインドされていても供給される。 * 名称が指定されている場合は、クラス/名称ペアが使用される。そのクラスのみに基づいた実装にフォールバック * することはない。 * * @param clazz バインディングマッチに使用されるクラス * @param name バインディングマッチに使用されるオプションの名称 * @return the instance the binding was configured to return * * (訳注:何も無いときはBindingExceptionが発生する)。 */ def inject[T <: Any](clazz: Class[T], name: Option[String]): T = { val key = BindingKey(clazz.asInstanceOf[Class[Any]], name) injectOptional[T](key) match { case None => throw new BindingException("No binding for key " + key) case Some(instance) => instance } } /** * 指定されたオプション名を伴う、クラスTのオプショナルバインディングを取得する。 * 指定されたクラス、指定されたオプション名でのバインディングが無ければNoneが返される。 * さもなければ、バインディングが評価され、Tのサブクラスのインスタンスが返される。 * @param clazz バインディングマッチに使用されるクラス * @param name Option[String] 指定されていればマッチに利用されるが、Noneであればクラスのみがルックアップに * 使用される(いかなる名前付バインディングにも一致しないことに注意)。 * name, if present will be matched, if None only class will * @return Option[T] Tのサブタイプのインスタンスか、あるいはマッチしないときはNone */ def injectOptional[T <: Any](clazz: Class[T], name: Option[String]): Option[T] = injectOptional(BindingKey(clazz.asInstanceOf[Class[Any]], name)) /** * Retrieve an optional binding for class T with the given BindingKey, if no * binding is available for the binding key, a None will be returned, * otherwise the binding will be evaluated and an instance of a subclass of T will be * returned. * @param key a BindingKey to use for the lookup * @return Option[T] containing either an instance subtype of T, or None if no matching * binding is found. */ def injectOptional[T <: Any](key: BindingKey): Option[T] = { // common sense check - many binding maps are empty, we can short circuit all lookup if it is // and just return None if (bindings.isEmpty) None else { val bindingOption = bindings.get(key) if (bindingOption == None) None else bindingOption.get match { case ip: ClassInstanceProvider[T] => Some(ip.newInstance()) case lip: LazyInstanceProvider[T] => Some(lip.instance) case nip: NewInstanceProvider[T] => Some(nip.instance) case i: T => Some(i) case _ => throw new BindingException("Illegal binding for key " + key) } } } } /** * 新規のイミュータブルなバインディングモジュールを作成する。 * In order to work, the constructor of this class * takes a function to evaluate, and passes this on to a bindings method which can be used to resolve * the bindingModule using the function on demand. The binding module loaned to the passed in function * is mutable, allowing the convenient DSL to be used, then the bindings are copied to an immutable * class upon exit of the bindings evaluation. *
* 使い方は以下の通り * ** class ProductionBindings extends NewBindingModule({ module => * module.bind[DBLookup] toInstance new MySQLLookup * module.bind[WebService].toClass[RealWebService] * module.bind[Int] identifiedBy 'maxPoolSize toInstance 10 * module.bind[QueryService] toLazyInstance { new SlowInitQueryService } * }) ** @param fn a * ミュータブルなバインディングモジュールを受け取り、所望のバインディングをそこに設定する関数。 * バインディングの生成後にこれは凍結されるのだが、DSLを使っての定義中はミュータブルである。 */ class NewBindingModule(fn: MutableBindingModule => Unit) extends BindingModule { lazy val bindings = { val module = new Object with MutableBindingModule fn(module) module.freeze().fixed.bindings } } /** * ミュータブルなバインディングモジュール。このモジュールはDSLによるバインディング設定のときに使用されるものだが、しかし * ミュータブルなバインディングモジュールそのものとしても使用可能である。しかし、リアルシステムでのインジェクションに * これを使う場合には、スレディング問題を解決しておく必要がある。 * For example, tests running in parallel could * reconfigure the same binding and cause a race condition which will cause the tests to fail. * As such, direct usage of the mutable binding module, particularly in a production environment, is * strongly discouraged. Use NewBindingModule instead to ensure immutability and thread safety. * * The MutableBindingModule is also provided on a per-function usage by the modifyBindings method on * BindingModule. This is the recommended way to have rebindings on a test-by-test basis and is * thread safe, as each test gets a new copy of the binding module and will not interfere with others. * * An example usage will look like this: *
* class SomeBindings extends NewBindingModule ({ module => * module.bind[Trait1] toInstance new Class1Impl * }) * * // in a test... * SomeBindings.modifyBindings { testBindings => // holds mutable copy of SomeBindings * module.bind[Trait1] toInstance mockClass1Impl // where the mock has been set up already * // run tests using the mockClass1Impl * } // coming out of scope destroys the temporary mutable binding module **/ trait MutableBindingModule extends BindingModule { outer => @volatile private[this] var _bindings = immutable.Map.empty[BindingKey, Any] @volatile private[this] var _frozen = false private[inject] def bindings_=(newBindings: BindingModule) { ensureNotFrozen() this._bindings = newBindings.bindings } private[inject] def bindings_=(newBindings: immutable.Map[BindingKey, Any]) { ensureNotFrozen() this._bindings = newBindings } def bindings = this._bindings /** * return an immutable copy of these bindings by creating a new binding module * with the bindings taken as an immutable snapshot of the current bindings in * this module. * @return a new immutable BindingModule */ def fixed(): BindingModule = { new BindingModule { override val bindings = outer._bindings } } /** * freeze the current state of this mutable binding module so that it may not be * changed further. This is done by checking the frozen property in the bindings * property modifier and is not as safe as using fixed() to obtain a completely * immutable copy of the bindings configuration, so fixed() is recommended. However * there may be times this approach is preferable. Calling freeze() on a mutable * binding module cannot be reversed. */ def freeze() = { this._frozen = true; this } /** * return whether the current state of these bindings is frozen. */ def frozen: Boolean = _frozen def ensureNotFrozen() = { if (_frozen) throw new BindingException("Module is frozen, no further bindings allowed") } private def bindingKey[T](m: Manifest[T], name: Option[String]) = BindingKey(m.erasure.asInstanceOf[Class[Any]], name) /** * Merge in bindings from another binding module, replacing any conflicts with the new bindings from the * other module supplied. May be used to bulk-apply some test configuration onto a mutable copy of the * regular bindings. * @param other A BindingModules with bindings to merge and/or replace the bindings in this module. */ def mergeWithReplace(other: BindingModule) = { this.bindings = this.bindings ++ other.bindings } /** * Replace the current bindings configuration module completely with the bindings from the other module * supplied. This will effectively unbind anything currently bound that is not bound in the new module. * @param other the other binding module with which to replace the current bindings. */ def replaceBindings(other: BindingModule) = { this.bindings = other.bindings } /** * A convenient way to combine multiple binding modules into one module. Just use withBindingModules and * supply a repeated parameter list of BindingModules to merge. The order for conflict resolution is * last in wins, so if you have
withBindingModule(ModuleA, ModuleB)
and both ModuleA and ModuleB
* bind the same class (and optional name), ModuleB will win.
*/
def withBindingModules(modules: BindingModule*) = {
if (!this.bindings.isEmpty) throw new BindingException("withBindingModules may only be used on an empty module for initialization")
for (module <- modules) mergeWithReplace(module)
}
private def bindInstance[T <: Any](instance: T)(implicit m: scala.reflect.Manifest[T]) {
val key = bindingKey(m, None)
bindings += key -> instance
}
private def bindLazyInstance[T <: Any](func: () => T)(implicit m: scala.reflect.Manifest[T]) {
val key = bindingKey(m, None)
bindings += key -> new LazyInstanceProvider(func)
}
private def bindProvider[T <: Any](func: () => T)(implicit m: scala.reflect.Manifest[T]) {
val key = bindingKey(m, None)
bindings += key -> new NewInstanceProvider(func)
}
private def bindInstance[T <: Any](name: String, instance: T)(implicit m: scala.reflect.Manifest[T]) {
val key = bindingKey(m, Some(name))
bindings += key -> instance
}
private def bindLazyInstance[T <: Any](name: String, func: () => T)(implicit m: scala.reflect.Manifest[T]) {
val key = bindingKey(m, Some(name))
bindings += key -> new LazyInstanceProvider(func)
}
private def bindProvider[T <: Any](name: String, func: () => T)(implicit m: scala.reflect.Manifest[T]) {
val key = bindingKey(m, Some(name))
bindings += key -> new NewInstanceProvider(func)
}
private def bindClass[T <: Any](instanceProvider: ClassInstanceProvider[T])(implicit m: scala.reflect.Manifest[T]) {
val key = bindingKey(m, None)
bindings += key -> instanceProvider
}
private def bindClass[T <: Any](name: String, instanceProvider: ClassInstanceProvider[T])(implicit m: scala.reflect.Manifest[T]) {
val key = bindingKey(m, Some(name))
bindings += key -> instanceProvider
}
/**
* Unbind a given trait (without name) from the list of bindings.
*/
def unbind[T <: Any]()(implicit m: scala.reflect.Manifest[T]): Option[T] = {
val key = bindingKey(m, None)
val existing = bindings.get(key)
bindings -= key
existing.asInstanceOf[Option[T]]
}
/**
* Unbind a given trait with the provided name from the list of bindings.
* @param name a String name that together with the trait type, identifies the binding to remove.
*/
def unbind[T <: Any](name: String)(implicit m: scala.reflect.Manifest[T]): Option[T] = {
val key = bindingKey(m, Some(name))
val existing = bindings.get(key)
bindings -= key
existing.asInstanceOf[Option[T]]
}
/**
* Unbind a given trait with the provided symbol from the list of bindings.
* @param symbol A symbol that together with the trait type, identifies the binding to remove.
*/
def unbind[T <: Any](symbol: Symbol)(implicit m: scala.reflect.Manifest[T]): Option[T] = unbind[T](symbol.name)
/**
* A convenient way to list the current bindings in the binding module. Useful for debugging purposes.
* Prints to standard out.
*/
def showMap() = {
println(mapString.mkString("\n"))
}
/**
* A convenient way to obtain a string representation of the current bindings in this module.
*/
def mapString = {
for ((k, v) <- bindings) yield { k.toString + " -> " + v.toString }
}
/**
* Temporarily push the bindings (as though on a stack) and let the current bindings be overridden for
* the scope of a provided by-name function. The binding changes will be popped after execution of the
* function, restoring the state of the bindings prior to the push.
* @param fn by-name function that can use and modify the bindings for this module without altering the original.
*/
def pushBindings[A](fn: => A): A = {
val currentBindings = bindings
try {
fn
}
finally {
bindings = currentBindings
}
}
// バインディングを行う小さなDSLを実現するための内部ビルダークラス
/**
* ミュータブルなバインディングモジュールにおける、バインディング設定用の便利なDSLを実現する内部ビルダクラス
*/
class Bind[T <: Any](implicit m: scala.reflect.Manifest[T]) {
var name: Option[String] = None
/**
* TのサブタイプのIの一つのインスタンスをバインドする。すべての注入時に対するシングルトンとして機能する。
* したがって、もしスレッド化実行するのであれば、このインスタンスはスレッドセーフでなければならない。
* @param instance I <: T型である一つのインスタンス。同じインスタンスがすべてのマッチするバインディングについて
* 返される
*/
def toInstance[I <: T](instance: I) = {
name match {
case Some(n) => outer.bindInstance[T](n, instance)
case None => outer.bindInstance[T](instance)
}
name = None
}
/**
* TのサブタイプであるIのプロバイダをバインドする。このプロバイダは名前渡し関数であり、Iのインスタンスを返すために
* 必要な操作を行うかもしれない。例えば、現在のウェブセッションに注入したい場合、プロバイダは現在のユーザ用の
* セッションを正しく取得する必要がある。
* この関数はバインディングがマッチしたそれぞれの注入において評価されるのだが、別々のインスタンスを返しても良いし、
* 同じものを返してもよい。
* @param function TのサブタイプであるI(のインスタンス)を返す名前渡し関数
*/
def toProvider[I <: T](function: => I) = {
name match {
case Some(n) => outer.bindProvider[T](n, function _)
case None => outer.bindProvider[T](function _)
}
name = None
}
/**
* Bind to a single instance of I where I is a subtype of the bound type T. The single instance will not be
* decided until the first time the matching binding is injected, but from then on will always be the same
* instance. This is to provide a way to cope with injection of items that may not have been configured at
* the time of application startup, but will be configured before the first usage. It can also be used for
* object with a slow initialization or ones that may never be used in a run. Since the same instance is
* always provided after the first evaluation, care should be taken that the threading capabilities of the
* object bound should match the execution environment, in other words, thread safety of the returned instance
* is your responsibility.
* @param function a by-name function that is evaluated on the first injection of this binding, and after that
* will always return the same instance I, where I is any subtype of T.
*/
def toLazyInstance[I <: T](function: => I) = {
name match {
case Some(n) => outer.bindLazyInstance[T](n, function _)
case None => outer.bindLazyInstance[T](function _)
}
}
/**
* A convenient operator to bind to an instance of None (in this definition). Can be used instead of
* unbind. For example module.bind[Int] identified by 'timeLimit to None
* @param must be None (for this form of the method).
*/
def to(none: None.type) = {
name match {
case Some(n) => outer.unbind[T](n)
case None => outer.unbind[T]()
}
name = None
}
/**
* Bind to a class instance provider of class. Intended to be used with instanceOfClass like this:
* module.bind[DBLookup] to instanceOfClass[MySQLDBLookup]
. Will provide a new
* instance of the class configured for each injection site. Any instance provided in this way
* must provide a zero parameter default constructor since reflection is used to create the instance
* and it will fail if there is no default constructor. Note that this is true even for implicit
* parameters, so you cannot use this form if you wish to provide the implicit binding chain to the
* target instance. Use a toProvider instead.
* @param instOfClass the class instance provder to use for the binding. Use instanceOfClass method
* to conveniently obtain the right thing.
*/
def to(instOfClass: ClassInstanceProvider[T]) = {
name match {
case Some(n) => outer.bindClass[T](n, instanceOfClass)
case None => outer.bindClass[T](instanceOfClass)
}
name = None
}
/**
* Create a class instance provider for the given class parameter. Intended for use with to(instOfClass).
* Note that this will only work to provide instances of classes that have a zero arg default constructor.
* reflection is used to create the class instance.
*/
def instanceOfClass[I <: T](implicit m: scala.reflect.Manifest[I], t: scala.reflect.Manifest[T]) = {
new ClassInstanceProvider[T](m.erasure.asInstanceOf[Class[Any]])
}
/**
* Bind to a new instance of the provided class for each injection. The class provided, I, must be
* a subtype of the binding class T. Because this form takes no parameters other than the type parameter
* it can screw up the semicolon inference in Scala if not used with the explicit . form, e.g.
* module.bind[DBLookup].toClass[MySQLDBLookup]
is safe, but
* module.bind[DBLookup] toClass[MySQLDBLookup]
can cause issues with semicolon inference.
* Note that the provided type I must provide a zero args default constructor for this binding to work.
* It uses reflection to instantiate the class and will fail at injection time if no such default constructor is
* available.
*/
def toClass[I <: T](implicit m: scala.reflect.Manifest[I], t: scala.reflect.Manifest[T]) = {
name match {
case Some(n) => outer.bindClass[T](n, new ClassInstanceProvider[T](m.erasure.asInstanceOf[Class[Any]]))
case None => outer.bindClass[T](new ClassInstanceProvider[T](m.erasure.asInstanceOf[Class[Any]]))
}
name = None
}
/**
* Part of the fluent interface in the DSL, identified by provides a name to attach to the binding key so that,
* in combination with the trait type being bound, a unique key is formed. This form takes a string name, but
* there is an overloaded version that allows a symbol to be used instead. The symbol and string names are
* interchangeable, i.e. 'maxPoolSize and "maxPoolSize" are equivalent both in definition and in usage.
*
* Typical usage:
* module.bind[Int] identifiedBy "maxPoolSize" toInstance 30
* @param n the string name to identify this binding when used in combination with the type parameter.
*/
def identifiedBy(n: String) = {
this.name = Some(n)
this
}
/**
* Part of the fluent interface in the DSL, identified by provides a name to attach to the binding key so that,
* in combination with the trait type being bound, a unique key is formed. This form takes a symbol name, but
* there is an overloaded version that allows a string to be used instead. The symbol and string names are
* interchangeable, i.e. 'maxPoolSize and "maxPoolSize" are equivalent both in definition and in usage.
*
* Typical usage:
* module.bind[Int] identifiedBy 'maxPoolSize toInstance 30
* @param symbol the symbol name to identify this binding when used in combination with the type parameter.
*/
def identifiedBy(symbol: Symbol) = {
this.name = Some(symbol.name)
this
}
}
// and a parameterized bind method to kick it all off
def bind[T <: Any](implicit m: scala.reflect.Manifest[T]) = new Bind[T]()
}
}}}