diff --git a/README.md b/README.md
index 03d5d48..9ccec07 100644
--- a/README.md
+++ b/README.md
@@ -1,3 +1,34 @@
+SubCut Extended README
+=============
+
+SUBCUT 2.5 WILL ADD SUPPORT FOR PROPERTY INJECTION SO I'M GOING TO STOP ANY SUPPORT FOR THIS PROJECT WHEN IT WILL BE RELEASED
+
+This project is an extension of SubCut (https://github.com/dickwall/subcut) I'm keeping active while waiting for the extension
+code to be merged in the original project.
+
+I added some feature to implement injection similarly to the @value annotation in spring. For example:
+
+```(scala)
+class ToInject(implicit val bindingModule: BindingModule) extends Injectable {
+ val stringProperty = injectProperty[String]("property1")
+ val intProperty = injectProperty[Int]("property2")
+ val longProperty = injectProperty[Long]("property3")
+ val floatProperty = injectProperty[Float]("property4")
+}
+```
+
+Allowing to inject in this way:
+
+```(scala)
+{
+ implicit val bindingModule = newBindingModuleWithConfig(PropertiesConfigPropertySource.fromPath("src/test/resources/test.properties"))
+
+ val configReaderInstance = new ToInject
+}
+```
+
+It supports injection of basic types and allows generic types injection if a custom conversion function is provided.
+
SubCut README
=============
diff --git a/build.sbt b/build.sbt
index 75dfd3b..5b0147d 100644
--- a/build.sbt
+++ b/build.sbt
@@ -1,10 +1,10 @@
-name := "subcut"
+name := "subcut_ext"
-organization := "com.escalatesoft.subcut"
+organization := "com.pragmasoft"
-version := "2.0"
+version := "2.1"
-crossScalaVersions := Seq("2.10.0", "2.9.2", "2.9.1", "2.9.0-1", "2.9.0")
+crossScalaVersions := Seq("2.10.0", "2.11.1")
scalaVersion := "2.10.1"
@@ -12,7 +12,7 @@ scalacOptions += "-deprecation"
libraryDependencies += "junit" % "junit" % "4.5" % "test"
-libraryDependencies += "org.scalatest" %% "scalatest" % "1.9.1" % "test"
+libraryDependencies += "org.scalatest" %% "scalatest" % "2.1.3" % "test"
libraryDependencies <<= (scalaVersion, libraryDependencies) { (ver, deps) =>
deps :+ "org.scala-lang" % "scala-compiler" % ver
@@ -53,4 +53,14 @@ pomExtra := (
Dick Wall
http://about.me/dickwall
- )
+
+ galarragas
+ Stefano Galarraga
+
+
+
+
+ conjars.org
+ http://conjars.org/repo/
+
+ )
diff --git a/pom.xml b/pom.xml
index fc971b1..165e8ef 100644
--- a/pom.xml
+++ b/pom.xml
@@ -4,9 +4,9 @@
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
4.0.0
- com.escalatesoft.subcut
- subcut
- 2.0
+ com.pragmasoft
+ subcut_ext
+ 2.1
2.10.1
diff --git a/project/plugins.sbt b/project/plugins.sbt
index cda13ac..3bf6a38 100644
--- a/project/plugins.sbt
+++ b/project/plugins.sbt
@@ -1,7 +1,7 @@
-resolvers += "sbt-idea-repo" at "http://mpeltonen.github.com/maven/"
+resolvers += "Sonatype snapshots" at "https://oss.sonatype.org/content/repositories/snapshots/"
-addSbtPlugin("com.github.mpeltonen" % "sbt-idea" % "1.2.0")
+addSbtPlugin("com.github.mpeltonen" % "sbt-idea" % "1.6.0")
resolvers += Classpaths.typesafeResolver
-addSbtPlugin("com.typesafe.sbteclipse" % "sbteclipse-plugin" % "2.0.0")
+addSbtPlugin("com.typesafe.sbteclipse" % "sbteclipse-plugin" % "2.5.0")
diff --git a/src/main/scala/com/escalatesoft/subcut/inject/BindingModule.scala b/src/main/scala/com/escalatesoft/subcut/inject/BindingModule.scala
index 75face0..707f6d7 100644
--- a/src/main/scala/com/escalatesoft/subcut/inject/BindingModule.scala
+++ b/src/main/scala/com/escalatesoft/subcut/inject/BindingModule.scala
@@ -7,23 +7,39 @@ package com.escalatesoft.subcut.inject
* Time: 11:39 AM
*/
import scala.collection._
+import com.escalatesoft.subcut.inject.config._
+import com.escalatesoft.subcut.inject.config.Undefined
+import scala.Some
+import com.escalatesoft.subcut.inject.config.Defined
/**
* The binding key, used to uniquely identify the desired injection using the class and an optional name.
*/
private[inject] case class BindingKey[A](m: Manifest[A], name: Option[String])
+
+trait ConfigPropertySourceProvider {
+ def configPropertySource: ConfigPropertySource
+}
+
+trait WithoutConfigPropertySource extends ConfigPropertySourceProvider {
+ def configPropertySource: ConfigPropertySource = new ConfigPropertySource {
+ def getOptional(propertyName: String): ConfigProperty = throw new IllegalStateException("No ConfigPropertySource provided")
+ }
+}
+
+
/**
* 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 =>
+trait BindingModule extends ConfigPropertySourceProvider { outer : ConfigPropertySourceProvider =>
/** Abstract binding map definition */
def bindings: immutable.Map[BindingKey[_], Any]
-
+
/**
* Merge 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). If you prefer symbolic operators,
@@ -44,6 +60,8 @@ trait BindingModule { outer =>
case notLmip =>
key -> notLmip
}}).toMap
+
+ def configPropertySource: ConfigPropertySource = outer.configPropertySource
}
}
@@ -66,6 +84,8 @@ trait BindingModule { outer =>
def modifyBindings[A](fn: MutableBindingModule => A): A = {
val mutableBindings = new MutableBindingModule {
bindings = outer.bindings
+
+ def configPropertySource: ConfigPropertySource = outer.configPropertySource
}
fn(mutableBindings)
}
@@ -135,8 +155,33 @@ trait BindingModule { outer =>
for ((k, v) <- bindings) yield { k.toString + " -> " + v.toString }
}
+ /**
+ * Retrieves a mandatory binding for class T for the giving property in the ConfigPropertySource owned by this module.
+ * If there is no ConfigPropertySource the binding will fail (use NewBindingModule.newBindingModuleWithConfig to create
+ * a config with ConfigPropertySource).
+ *
+ * @param propertyName The key to use to retrieve the property from the ConfigPropertySource
+ * @return The property value converted to the required type by the implicit property converter
+ */
+ def injectPropertyMandatory[T <: Any](propertyName: String)(implicit m: scala.reflect.Manifest[T], propertyConverter: ConfigProperty => T): T =
+ propertyConverter( configPropertySource.get(propertyName) )
+
+ /**
+ * Retrieves an optional binding for class T for the giving property in the ConfigPropertySource owned by this module.
+ * If there is no ConfigPropertySource the binding will fail (use NewBindingModule.newBindingModuleWithConfig to create
+ * a config with ConfigPropertySource).
+ *
+ * @param propertyName The key to use to retrieve the property from the ConfigPropertySource
+ * @return An optional property value converted to the required type by the implicit property converter
+ */
+ def injectPropertyOptional[T <: Any](propertyName: String)(implicit m: scala.reflect.Manifest[T], propertyConverter: ConfigProperty => T): Option[T] =
+ configPropertySource.getOptional(propertyName) match {
+ case property@Defined(_, _) => Some(propertyConverter(property))
+ case Undefined(_) => None
+ }
}
+
/**
* 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
@@ -159,9 +204,21 @@ trait BindingModule { outer =>
* 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 {
+class NewBindingModule(fn: MutableBindingModule => Unit) extends BindingModule with WithoutConfigPropertySource {
+ lazy val bindings = {
+ val module = new Object with MutableBindingModule with WithoutConfigPropertySource
+ fn(module)
+ module.freeze().fixed.bindings
+ }
+}
+
+class NewBindingModuleWithConfig(fn: MutableBindingModule => Unit)(implicit configProvider: ConfigPropertySource) extends BindingModule {
+ override val configPropertySource = configProvider
+
lazy val bindings = {
- val module = new Object with MutableBindingModule
+ val module = new Object with MutableBindingModule {
+ def configPropertySource: ConfigPropertySource = configProvider
+ }
fn(module)
module.freeze().fixed.bindings
}
@@ -182,9 +239,19 @@ class NewBindingModule(fn: MutableBindingModule => Unit) extends BindingModule {
* bind [QueryService] toSingle { new SlowInitQueryService }
* }
*
+ *
+ * Use the newBindingModuleWithConfig method if you need to inject from a ConfigPropertySource. That needs a
+ * ConfigPropertySource instance to be implicitly available. A version without any binding is available if you
+ * are just injecting properties using the injectProperty method
*/
object NewBindingModule {
def newBindingModule(fn: MutableBindingModule => Unit): BindingModule = new NewBindingModule(fn)
+
+ def newBindingModuleWithConfig(fn: MutableBindingModule => Unit)(implicit configProvider: ConfigPropertySource): BindingModule =
+ new NewBindingModuleWithConfig(fn)(configProvider)
+
+ def newBindingModuleWithConfig(implicit configProvider: ConfigPropertySource): BindingModule =
+ new NewBindingModuleWithConfig( { mutableBindingModule: MutableBindingModule => } )(configProvider)
}
/**
@@ -239,6 +306,7 @@ trait MutableBindingModule extends BindingModule { outer =>
def fixed: BindingModule = {
new BindingModule {
override val bindings = outer._bindings
+ def configPropertySource: ConfigPropertySource = outer.configPropertySource
}
}
@@ -576,6 +644,22 @@ trait MutableBindingModule extends BindingModule { outer =>
name = None
}
+ /**
+ * Bind to a new instance of the provided class for each injection. The value for this instance is read from
+ * the ConfigPropertySource available in the BindingModule and converted using an implicitly available converter from
+ * ConfigPropertySource (essentially a wrapper for String) to the target type.
+ * Converters for the basic types are already available, you need to provide a new one for special types.
+ *
+ * @param name
+ * @param source
+ * @param converter
+ * @tparam I
+ * @return
+ */
+ def toProperty[I <: T](name: String)(implicit source: ConfigPropertySource, converter: ConfigProperty => I) {
+ toSingle( converter(source.get(name)) )
+ }
+
/**
* 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
@@ -622,3 +706,4 @@ trait MutableBindingModule extends BindingModule { outer =>
// and a parameterized bind method to kick it all off
def bind[T <: Any](implicit m: scala.reflect.Manifest[T]) = new Bind[T]()
}
+
diff --git a/src/main/scala/com/escalatesoft/subcut/inject/Injectable.scala b/src/main/scala/com/escalatesoft/subcut/inject/Injectable.scala
index 6322670..d86aff5 100644
--- a/src/main/scala/com/escalatesoft/subcut/inject/Injectable.scala
+++ b/src/main/scala/com/escalatesoft/subcut/inject/Injectable.scala
@@ -1,5 +1,7 @@
package com.escalatesoft.subcut.inject
+import com.escalatesoft.subcut.inject.config.{ConfigProperty, BasicPropertyConversions}
+
/**
* The trait that provides dependency injection features for a class or object. To use this trait,
* Mix it in to the class or object definition, and then define the abstract bindingModule which holds
@@ -8,7 +10,7 @@ package com.escalatesoft.subcut.inject
* or, perhaps most flexibly, an implicit constructor parameter in a curried parameter list. This last option
* can provide flexible and mostly invisible bindings all the way down an object instance creation chain.
*/
-trait Injectable {
+trait Injectable extends BasicPropertyConversions{
implicit def bindingModule: BindingModule
/**
@@ -167,6 +169,33 @@ trait Injectable {
def injectOptional[T <: Any](name: String)(implicit m: scala.reflect.Manifest[T]): Option[T] =
bindingModule.injectOptional[T](Some(name))
+ /**
+ * Inject a required instance for the given trait as read from the ConfigPropertySource provided by the BindingModule
+ * and converted using an implicit converter from ConfigProperty (a wrapper of String properties) to the required target
+ * trait.
+ *
+ * If the module contains no configuration the injection will fail. The same will happen if the property cannot be
+ * found.
+ *
+ * Instances of ConfigPropertySource trait can be developed to allow the injection to retrieve from different sources.
+ * At the moment the only implementation available allows the use of the Java Properties class.
+ *
+ * @param propertyName The key in the ConfigPropertySource to load the property to be Inject
+ * @return An instance converted to the relevant type
+ */
+ def injectProperty[T <: Any](propertyName: String)(implicit m: scala.reflect.Manifest[T], propertyConverter: ConfigProperty => T): T =
+ bindingModule.injectPropertyMandatory[T](propertyName)
+
+ /**
+ * Inject an optional for the given trait as read from the ConfigPropertySource provided by the BindingModule
+ * and converted using an implicit converter from ConfigProperty (a wrapper of String properties) to the required target
+ * trait.. If there is no matching binding, this method will return None.
+ *
+ * @param propertyName The name of the property to be read
+ * @return An option with instance converted to the relevant type if found or None if not found
+ */
+ def injectOptionalProperty[T <: Any](propertyName: String)(implicit m: scala.reflect.Manifest[T], propertyConverter: ConfigProperty => T): Option[T] =
+ bindingModule.injectPropertyOptional[T](propertyName)
}
/**
diff --git a/src/main/scala/com/escalatesoft/subcut/inject/config/BasicPropertyConversions.scala b/src/main/scala/com/escalatesoft/subcut/inject/config/BasicPropertyConversions.scala
new file mode 100644
index 0000000..b505c61
--- /dev/null
+++ b/src/main/scala/com/escalatesoft/subcut/inject/config/BasicPropertyConversions.scala
@@ -0,0 +1,27 @@
+package com.escalatesoft.subcut.inject.config
+
+object BasicPropertyConversions {
+
+ implicit def _toString(input: ConfigProperty) : String = input.value
+ implicit def _toInt(input: ConfigProperty) : Int = augmentString(input.value).toInt
+ implicit def _toLong(input: ConfigProperty) : Long = augmentString(input.value).toLong
+ implicit def _toDouble(input: ConfigProperty) : Double = augmentString(input.value).toDouble
+ implicit def _toFloat(input: ConfigProperty) : Float = augmentString(input.value).toFloat
+ implicit def _toByte(input: ConfigProperty) : Byte = augmentString(input.value).toByte
+ implicit def _toShort(input: ConfigProperty) : Short = augmentString(input.value).toShort
+}
+
+import BasicPropertyConversions._
+trait BasicPropertyConversions {
+
+ implicit def toString(input: ConfigProperty) : String = _toString(input)
+ implicit def toInt(input: ConfigProperty) : Int = _toInt(input)
+ implicit def toLong(input: ConfigProperty) : Long = _toLong(input)
+ implicit def toDouble(input: ConfigProperty) : Double = _toDouble(input)
+ implicit def toFloat(input: ConfigProperty) : Float = _toFloat(input)
+ implicit def toByte(input: ConfigProperty) : Byte = _toByte(input)
+ implicit def toShort(input: ConfigProperty) : Short = _toShort(input)
+
+}
+
+
diff --git a/src/main/scala/com/escalatesoft/subcut/inject/config/ConfigProperty.scala b/src/main/scala/com/escalatesoft/subcut/inject/config/ConfigProperty.scala
new file mode 100644
index 0000000..15be26b
--- /dev/null
+++ b/src/main/scala/com/escalatesoft/subcut/inject/config/ConfigProperty.scala
@@ -0,0 +1,18 @@
+package com.escalatesoft.subcut.inject.config
+
+sealed trait ConfigProperty {
+ def name: String
+ def value: String
+
+ def valueAs[T](implicit converter: ConfigProperty => T) : Option[T]
+}
+
+case class Defined(name: String, value: String) extends ConfigProperty {
+ def valueAs[T](implicit converter: ConfigProperty => T) : Option[T] = Some( converter(this) )
+}
+
+case class Undefined(name: String) extends ConfigProperty {
+ def value = throw new IllegalArgumentException(s"Undefined value for property $name")
+
+ def valueAs[T](implicit converter: ConfigProperty => T) : Option[T] = None
+}
diff --git a/src/main/scala/com/escalatesoft/subcut/inject/config/ConfigPropertySource.scala b/src/main/scala/com/escalatesoft/subcut/inject/config/ConfigPropertySource.scala
new file mode 100644
index 0000000..3bc3cc2
--- /dev/null
+++ b/src/main/scala/com/escalatesoft/subcut/inject/config/ConfigPropertySource.scala
@@ -0,0 +1,10 @@
+package com.escalatesoft.subcut.inject.config
+
+trait ConfigPropertySource {
+ def get(propertyName: String) : ConfigProperty = getOptional(propertyName) match {
+ case property@Defined(_, value) => property
+ case Undefined(_) => throw new IllegalArgumentException(s"Missing property $propertyName in config")
+ }
+
+ def getOptional(propertyName: String) : ConfigProperty
+}
diff --git a/src/main/scala/com/escalatesoft/subcut/inject/config/PropertiesConfigPropertySource.scala b/src/main/scala/com/escalatesoft/subcut/inject/config/PropertiesConfigPropertySource.scala
new file mode 100644
index 0000000..20c2478
--- /dev/null
+++ b/src/main/scala/com/escalatesoft/subcut/inject/config/PropertiesConfigPropertySource.scala
@@ -0,0 +1,39 @@
+package com.escalatesoft.subcut.inject.config
+
+import java.util.Properties
+import java.io.{FileInputStream, File}
+
+class PropertiesConfigPropertySource(properties: Properties) extends ConfigPropertySource {
+ def getOptional(propertyName: String) : ConfigProperty = {
+ val prop = properties.getProperty(propertyName)
+
+ if(prop != null)
+ Defined(propertyName, prop)
+ else
+ Undefined(propertyName)
+ }
+}
+
+class PropertiesConfigMapSource(properties: Map[String, String]) extends ConfigPropertySource {
+ def getOptional(propertyName: String) : ConfigProperty = properties.get(propertyName) match {
+ case Some(prop) => Defined(propertyName, prop)
+ case None => Undefined(propertyName)
+ }
+}
+
+object PropertiesConfigPropertySource {
+ def apply(properties: Properties) = new PropertiesConfigPropertySource(properties)
+ def apply(properties: Map[String, String]) = new PropertiesConfigMapSource(properties)
+
+ def fromPath(path: String) = {
+ val properties = new Properties()
+ properties.load(new FileInputStream(new File(path)))
+ new PropertiesConfigPropertySource(properties)
+ }
+
+ def fromClasspathResource(path: String) = {
+ val properties = new Properties()
+ properties.load(getClass.getClassLoader.getResourceAsStream(path))
+ new PropertiesConfigPropertySource(properties)
+ }
+}
diff --git a/src/test/resources/test.properties b/src/test/resources/test.properties
new file mode 100644
index 0000000..17d42fd
--- /dev/null
+++ b/src/test/resources/test.properties
@@ -0,0 +1,4 @@
+property1=value1
+property2=2
+property3=3
+property4=4
\ No newline at end of file
diff --git a/src/test/scala/com/escalatesoft/subcut/inject/ConfigPropertyBindingTest.scala b/src/test/scala/com/escalatesoft/subcut/inject/ConfigPropertyBindingTest.scala
new file mode 100644
index 0000000..0ddb53d
--- /dev/null
+++ b/src/test/scala/com/escalatesoft/subcut/inject/ConfigPropertyBindingTest.scala
@@ -0,0 +1,207 @@
+package com.escalatesoft.subcut.inject
+
+import org.scalatest.FunSuite
+import org.scalatest.matchers.ShouldMatchers
+
+import NewBindingModule._
+import com.escalatesoft.subcut.inject.config._
+import org.junit.runner.RunWith
+import org.scalatest.junit.JUnitRunner
+import java.util.Date
+import java.text.SimpleDateFormat
+import scala.concurrent.duration._
+
+@RunWith(classOf[JUnitRunner])
+class ConfigPropertyBindingTest extends FunSuite with ShouldMatchers {
+
+
+ test("Should inject from config") {
+ class ToInject(implicit val bindingModule: BindingModule) extends Injectable {
+ val property1 = injectProperty[String]("property1")
+ val property2 = injectProperty[String]("property2")
+
+ def properties: List[Any] = List(property1, property2)
+ }
+
+ implicit val propertyProvider = PropertiesConfigPropertySource {
+ Map( "property1" -> "value1", "property2" -> "value2")
+ }
+ implicit val bindingModule = newBindingModuleWithConfig
+
+ val configReaderInstance = new ToInject
+
+ configReaderInstance.properties should equal (List("value1", "value2"))
+ }
+
+ test("Should convert basic types") {
+ class ToInject(implicit val bindingModule: BindingModule) extends Injectable {
+ val property1 = injectProperty[String]("property1")
+ val property2 = injectProperty[Int]("property2")
+ val property3 = injectProperty[Long]("property3")
+ val property4 = injectProperty[Float]("property4")
+
+ def properties: List[Any] = List(property1, property2, property3, property4)
+ }
+
+ implicit val propertyProvider = PropertiesConfigPropertySource {
+ Map( "property1" -> "value1", "property2" -> "2", "property3" -> "3", "property4" -> "4.0")
+ }
+ implicit val bindingModule = newBindingModuleWithConfig { bindingModule => }
+
+ val configReaderInstance = new ToInject
+
+ configReaderInstance.properties should equal (List("value1", 2, 3l, 4.0))
+ }
+
+ test("Should bind properties from config source") {
+ class ToInject(implicit val bindingModule: BindingModule) extends Injectable {
+ val property1 = inject[String]("property1")
+ val property2 = inject[Int]("property2")
+ val property3 = inject[Long]("property3")
+ val property4 = inject[Float]("property4")
+
+ def properties: List[Any] = List(property1, property2, property3, property4)
+ }
+
+ implicit val propertyProvider = PropertiesConfigPropertySource {
+ Map( "property1" -> "value1", "property2" -> "2", "property3" -> "3", "property4" -> "4.0")
+ }
+
+ implicit val bindingModule = newBindingModuleWithConfig { bindingModule =>
+ import bindingModule._
+ import BasicPropertyConversions._
+
+ bind [String] idBy 'property1 toProperty "property1"
+ bind [Int] idBy 'property2 toProperty "property2"
+ bind [Long] idBy 'property3 toProperty "property3"
+ bind [Float] idBy 'property4 toProperty "property4"
+ }
+
+ val configReaderInstance = new ToInject
+
+ configReaderInstance.properties should equal (List("value1", 2, 3l, 4.0))
+ }
+
+ test("Should optionally bind properties from config source") {
+ class ToInject(implicit val bindingModule: BindingModule) extends Injectable {
+ val property1 = injectOptionalProperty[Int]("property1") getOrElse(-1)
+ val property2 = injectOptionalProperty[Int]("property2") getOrElse(-1)
+
+ def properties: List[Any] = List(property1, property2)
+ }
+
+ implicit val propertyProvider = PropertiesConfigPropertySource {
+ Map( "property1" -> "100")
+ }
+
+ implicit val bindingModule = newBindingModuleWithConfig
+
+ val configReaderInstance = new ToInject
+
+ configReaderInstance.properties should equal (List(100, -1))
+ }
+
+ test("Should bind custom type if a converter is provided") {
+ implicit def toDate(prop: ConfigProperty): Date = new SimpleDateFormat("yyyy-mm-DD").parse(prop.value)
+
+ class ToInject(implicit val bindingModule: BindingModule) extends Injectable {
+ val property1 = inject[Date]("property1")
+ val property2 = injectProperty[Date]("property2")
+ }
+
+ implicit val propertyProvider = PropertiesConfigPropertySource {
+ Map( "property1" -> "2014-01-15", "property2" -> "2014-01-15")
+ }
+
+ implicit val bindingModule = newBindingModuleWithConfig { bindingModule =>
+ import bindingModule._
+
+ bind [Date] idBy 'property1 toProperty "property1"
+ }
+
+ val configReaderInstance = new ToInject
+
+ configReaderInstance.property1 should equal (new SimpleDateFormat("yyyy-mm-DD").parse("2014-01-15"))
+ configReaderInstance.property2 should equal (new SimpleDateFormat("yyyy-mm-DD").parse("2014-01-15"))
+ }
+
+ test("Operators on binding modules are bringing around the config properties provider - modifyBindings") {
+ class ToInject(implicit val bindingModule: BindingModule) extends Injectable {
+ val property1 = inject[String]("property1")
+ val property2 = inject[Int]("property2")
+ val property3 = inject[Long]("property3")
+ val property4 = inject[Float]("property4")
+
+ def properties: List[Any] = List(property1, property2, property3, property4)
+ }
+
+ implicit val propertyProvider = PropertiesConfigPropertySource {
+ Map( "property1" -> "value1", "property2" -> "2", "property3" -> "3", "property4" -> "4.0")
+ }
+
+ implicit val bindingModule = newBindingModuleWithConfig { bindingModule =>
+ import bindingModule._
+ import BasicPropertyConversions._
+
+ bind [String] idBy 'property1 toProperty "property1"
+ bind [Int] idBy 'property2 toProperty "property2"
+ bind [Long] idBy 'property3 toProperty "property3"
+ bind [Float] idBy 'property4 toProperty "property4"
+ }
+
+ bindingModule modifyBindings { implicit bindingModule =>
+ import bindingModule._
+
+ bind [String] idBy 'property1 toSingle "value1-MODIFIED"
+
+ val configReaderInstance = new ToInject
+
+ configReaderInstance.properties should equal (List("value1-MODIFIED", 2, 3l, 4.0))
+ }
+ }
+
+ test("You can write more esoteric conversion using property name") {
+ class ToInject(implicit val bindingModule: BindingModule) extends Injectable {
+ val timeout1 = injectProperty[Duration]("timeout.millis")
+ val timeout2 = inject[Duration]("timeout2")
+ }
+
+ implicit def toDuration(prop: ConfigProperty): Duration = {
+ val amount: Long = prop.value.toLong
+ val timeQualifier: String = prop.name.split('.').last
+
+ timeQualifier match {
+ case "seconds" | "secs" => amount.seconds
+ case "hours" => amount.hours
+ case _ => amount.milliseconds
+ }
+ }
+
+ implicit val propertyProvider = PropertiesConfigPropertySource {
+ Map( "timeout.millis" -> "100", "timeout.seconds" -> "20")
+ }
+
+ implicit val bindingModule = newBindingModuleWithConfig { bindingModule =>
+ import bindingModule._
+
+ bind [Duration] idBy "timeout2" toProperty "timeout.seconds"
+ }
+
+ val configReaderInstance = new ToInject
+
+ configReaderInstance.timeout1 should equal (100.milliseconds)
+ configReaderInstance.timeout2 should equal (20.seconds)
+ }
+
+ test("you can use config source to directly access properties too") {
+ import BasicPropertyConversions._
+
+ val config = PropertiesConfigPropertySource {
+ Map( "intProp" -> "3", "doubleProp" -> "4.0")
+ }
+
+ config.getOptional("intProp").valueAs[Int] should equal (Some(3))
+ config.getOptional("doubleProp").valueAs[Double] should equal (Some(4.0))
+ config.getOptional("otherProp").valueAs[Double] should equal (None)
+ }
+}
diff --git a/src/test/scala/com/escalatesoft/subcut/inject/FrozenModuleTest.scala b/src/test/scala/com/escalatesoft/subcut/inject/FrozenModuleTest.scala
index 7aaabe0..5ab10c6 100644
--- a/src/test/scala/com/escalatesoft/subcut/inject/FrozenModuleTest.scala
+++ b/src/test/scala/com/escalatesoft/subcut/inject/FrozenModuleTest.scala
@@ -82,7 +82,7 @@ class MultFunc extends MathFunc {
override def doIt(x: Int, y: Int): Int = x * y
}
-object MathModule extends MutableBindingModule
+object MathModule extends MutableBindingModule with WithoutConfigPropertySource
trait MathInjector extends BoundToModule {
override val bindingModule = MathModule
diff --git a/src/test/scala/com/escalatesoft/subcut/inject/ModuleCompositionAndMergingTest.scala b/src/test/scala/com/escalatesoft/subcut/inject/ModuleCompositionAndMergingTest.scala
index 11f8cb6..a0a7e3e 100644
--- a/src/test/scala/com/escalatesoft/subcut/inject/ModuleCompositionAndMergingTest.scala
+++ b/src/test/scala/com/escalatesoft/subcut/inject/ModuleCompositionAndMergingTest.scala
@@ -60,7 +60,7 @@ object StandardConfiguration extends NewBindingModule({ module =>
bind [String] idBy 'version toSingle "1.3"
})
-class FromProperties extends BindingModule {
+class FromProperties extends BindingModule with WithoutConfigPropertySource {
val bindings = {
val newMod = new NewBindingModule({ module =>
import module._
diff --git a/src/test/scala/com/escalatesoft/subcut/inject/PlainScalaInjectInBindingTest.scala b/src/test/scala/com/escalatesoft/subcut/inject/PlainScalaInjectInBindingTest.scala
index e8bb99a..d43eb64 100644
--- a/src/test/scala/com/escalatesoft/subcut/inject/PlainScalaInjectInBindingTest.scala
+++ b/src/test/scala/com/escalatesoft/subcut/inject/PlainScalaInjectInBindingTest.scala
@@ -60,7 +60,7 @@ class PlainScalaInjectInBindingTest extends FunSuite with ShouldMatchers {
}
//all implementations unaware of framework, inject called during module definition
- object EchoModule extends MutableBindingModule with Injectable {
+ object EchoModule extends MutableBindingModule with WithoutConfigPropertySource with Injectable {
val bindingModule = this
diff --git a/src/test/scala/com/escalatesoft/subcut/inject/PushBindingsTest.scala b/src/test/scala/com/escalatesoft/subcut/inject/PushBindingsTest.scala
index 6f2b236..a3186d8 100644
--- a/src/test/scala/com/escalatesoft/subcut/inject/PushBindingsTest.scala
+++ b/src/test/scala/com/escalatesoft/subcut/inject/PushBindingsTest.scala
@@ -87,15 +87,15 @@ class PushBindingsTest extends FunSuite with ShouldMatchers with SeveredStackTra
}
-object PushBindingsTestModule1 extends MutableBindingModule {
+object PushBindingsTestModule1 extends MutableBindingModule with WithoutConfigPropertySource {
bind [BoundTrait1] toSingle new T1Impl1
}
-object PushBindingsTestModule2 extends MutableBindingModule {
+object PushBindingsTestModule2 extends MutableBindingModule with WithoutConfigPropertySource {
bind [BoundTrait2] toSingle new T2Impl1
}
-object PushBindingsTestModule extends MutableBindingModule {
+object PushBindingsTestModule extends MutableBindingModule with WithoutConfigPropertySource {
withBindingModules(PushBindingsTestModule1, PushBindingsTestModule2)
}
diff --git a/src/test/scala/com/escalatesoft/subcut/inject/TrialBindingModule.scala b/src/test/scala/com/escalatesoft/subcut/inject/TrialBindingModule.scala
index c9dcbdc..c99ea22 100644
--- a/src/test/scala/com/escalatesoft/subcut/inject/TrialBindingModule.scala
+++ b/src/test/scala/com/escalatesoft/subcut/inject/TrialBindingModule.scala
@@ -68,7 +68,7 @@ class InjectionTest extends FunSuite with ShouldMatchers with SeveredStackTraces
}
}
-object TrialBindingModule extends MutableBindingModule {
+object TrialBindingModule extends MutableBindingModule with WithoutConfigPropertySource {
bind [TestTrait] toProvider { new TestImpl }
bind [TestTrait] identifiedBy 'testConfig toProvider (new AlternativeImpl)
bind [TestTrait] identifiedBy "something else" toProvider { new AlternativeImpl }
diff --git a/src/test/scala/com/escalatesoft/subcut/inject/config/PropertiesConfigPropertySourceTest.scala b/src/test/scala/com/escalatesoft/subcut/inject/config/PropertiesConfigPropertySourceTest.scala
new file mode 100644
index 0000000..9684dea
--- /dev/null
+++ b/src/test/scala/com/escalatesoft/subcut/inject/config/PropertiesConfigPropertySourceTest.scala
@@ -0,0 +1,66 @@
+package com.escalatesoft.subcut.inject.config
+
+import org.scalatest.matchers.ShouldMatchers
+import org.scalatest.FunSuite
+import com.escalatesoft.subcut.inject.{Injectable, BindingModule}
+import com.escalatesoft.subcut.inject.NewBindingModule._
+import java.util.Properties
+import org.junit.runner.RunWith
+import org.scalatest.junit.JUnitRunner
+
+@RunWith(classOf[JUnitRunner])
+class PropertiesConfigPropertySourceTest extends FunSuite with ShouldMatchers {
+
+ class ToInject(implicit val bindingModule: BindingModule) extends Injectable {
+ val property1 = injectProperty[String]("property1")
+ val property2 = injectProperty[Int]("property2")
+ val property3 = injectProperty[Long]("property3")
+ val property4 = injectProperty[Float]("property4")
+
+ def properties: List[Any] = List(property1, property2, property3, property4)
+ }
+
+ test("should load from properties") {
+ val properties = new Properties()
+ properties.put("property1", "value1")
+ properties.put("property2", "2")
+ properties.put("property3", "3")
+ properties.put("property4", "4")
+ implicit val propertySource = PropertiesConfigPropertySource(properties)
+
+ implicit val bindingModule = newBindingModuleWithConfig
+
+ val configReaderInstance = new ToInject
+
+ configReaderInstance.properties should equal (List("value1", 2, 3l, 4.0))
+ }
+
+ test("should load from classpath") {
+ implicit val propertySource = PropertiesConfigPropertySource.fromClasspathResource("test.properties")
+
+ implicit val bindingModule = newBindingModuleWithConfig
+
+ val configReaderInstance = new ToInject
+
+ configReaderInstance.properties should equal (List("value1", 2, 3l, 4.0))
+ }
+
+ test("should load from path") {
+ implicit val propertySource = PropertiesConfigPropertySource.fromPath("src/test/resources/test.properties")
+
+ implicit val bindingModule = newBindingModuleWithConfig
+
+ val configReaderInstance = new ToInject
+
+ configReaderInstance.properties should equal (List("value1", 2, 3l, 4.0))
+ }
+
+ test("should work without implicit too") {
+
+ implicit val bindingModule = newBindingModuleWithConfig(PropertiesConfigPropertySource.fromPath("src/test/resources/test.properties"))
+
+ val configReaderInstance = new ToInject
+
+ configReaderInstance.properties should equal (List("value1", 2, 3l, 4.0))
+ }
+}