All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.scala_tools.subcut.inject.BindingModule.scala Maven / Gradle / Ivy

package org.scala_tools.subcut.inject

/*
 * Created by IntelliJ IDEA.
 * User: dick
 * Date: 2/17/11
 * Time: 11:39 AM
 */
import scala.collection._

/**
 * The binding key, used to uniquely identify the desired injection using the class and an optional name.
 */
private[inject] case class BindingKey(val clazz: Class[Any], val name: Option[String])

/**
 * The main BindingModule trait.
 * Not intended to be used directly, instead extend NewBindingModule with a function to create the bindings
 * (recommended - the result will be immutable) or a MutableBindingModule (not recommended unless you know what
 * you are doing and take on the thread safety responsibility yourself).
 */
trait BindingModule { outer =>

  /** Abstract binding map definition */
  def bindings: immutable.Map[BindingKey, Any]
  
  /**
   * Cons this module with another. The resulting module will include all bindings from both modules, with this
   * module winning if there are common bindings (binding override).
   * @param other another BindingModule to cons with this one. Any duplicates will favor the bindings from this
   * rather than other.
   */
  def ::(other: BindingModule): BindingModule = {
    new BindingModule {
      override val bindings = outer.bindings ++ other.bindings
    }
  }

  /**
   * Provide a mutable copy of these bindings to a passed in function so that it can override the bindings
   * for just the scope of that function. Useful for testing.
   * @param fn a function that takes the new mutable binding copy and may use it within scope. Can
   * return any type, and the any return from the function will be returned from this function.
   * @return the value returned from the provided function.
   */
  def modifyBindings[A](fn: MutableBindingModule => A): A = {
    val mutableBindings = new MutableBindingModule {
      bindings = outer.bindings
    }
    fn(mutableBindings)
  }

  /**
   * Inject for a given class with optional name. Whatever is bound to the class will be provided.
   * If the name is given, only a matching class/name pair will be used, and it will not fall back
   * to just providing an implementation based only on the class.
   * @param clazz the class to match for the binding match
   * @param name an optional name to use for the binding match
   * @return the instance the binding was configured to return
   */
  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
    }
  }

  /**
   * Retrieve an optional binding for class T with the optional name provided. If no
   * binding is available with the given class and optional name, a None will be returned,
   * otherwise the binding will be evaluated and an instance of a subclass of T will be
   * returned.
   * @param clazz the class to match a binding for
   * @param name Option[String] name, if present will be matched, if None only class will
   * be used for lookup (note, this also means any named bindings of the same type will
   * not be matched)
   * @return Option[T] containing either an instance subtype of T, or None if no matching
   * binding is found.
   */
  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)
        }
    }
  }
}

/**
 * A class to create a new, immutable, binding module. 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.
 * 

* To use this class: *

*

 * 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 function that takes a mutable binding module and initializes it with whatever bindings * you want. The module will be frozen after creation of the bindings, but is mutable for the * time you are defining it with the DSL. */ class NewBindingModule(fn: MutableBindingModule => Unit) extends BindingModule { lazy val bindings = { val module = new Object with MutableBindingModule fn(module) module.freeze().fixed.bindings } } /** * A mutable binding module. This module is used during construction of bindings configuration * with the DSL, but may also be used as a mutable binding module in its own right. Anyone wishing * to use the mutable binding module to inject a real system should be aware that they take on all * responsibility for threading issues with such usage. 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 } } // an inner builder class to give us a nice little DSL for doing the bindings /** * Inner builder class providing a convenient DSL for configuring the bindings in this mutable binding module. */ class Bind[T <: Any](implicit m: scala.reflect.Manifest[T]) { var name: Option[String] = None /** * Bind to a single instance of I where I is an instance of any subtype of T. This will effectively provide * a singleton binding for any injected instance. As such, any instances used here should be thread safe if * they are to be used in threaded execution. * @param instance a single instance of type I <: T. The same instance will be returned for any matching binding. */ def toInstance[I <: T](instance: I) = { name match { case Some(n) => outer.bindInstance[T](n, instance) case None => outer.bindInstance[T](instance) } name = None } /** * Bind to a provider of type I where I is any subtype of T. The provider is a by-name function that returns * an instance of type I, and may perform any necessary operation in order to provide I, for example if * the current web session is to be injected, the provider may use whatever mechanism is required to obtain * the correct current web session for the current user, etc. and provide that back to the injection site. * The function will be evaluated for each injection of the matching binding and may return a unique instance * each time, or the same one, or anything in between. * @param function a by-name function returning type I where I is a subtype of the bound type T. */ 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]() }





© 2015 - 2025 Weber Informatics LLC | Privacy Policy