Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 31 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -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
=============

Expand Down
22 changes: 16 additions & 6 deletions build.sbt
Original file line number Diff line number Diff line change
@@ -1,18 +1,18 @@
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"

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
Expand Down Expand Up @@ -53,4 +53,14 @@ pomExtra := (
<name>Dick Wall</name>
<url>http://about.me/dickwall</url>
</developer>
</developers>)
<developer>
<id>galarragas</id>
<name>Stefano Galarraga</name>
</developer>
</developers>
<repositories>
<repository>
<id>conjars.org</id>
<url>http://conjars.org/repo/</url>
</repository>
</repositories>)
6 changes: 3 additions & 3 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,9 @@
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>

<groupId>com.escalatesoft.subcut</groupId>
<artifactId>subcut</artifactId>
<version>2.0</version>
<groupId>com.pragmasoft</groupId>
<artifactId>subcut_ext</artifactId>
<version>2.1</version>

<properties>
<scala.version>2.10.1</scala.version>
Expand Down
6 changes: 3 additions & 3 deletions project/plugins.sbt
Original file line number Diff line number Diff line change
@@ -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")
93 changes: 89 additions & 4 deletions src/main/scala/com/escalatesoft/subcut/inject/BindingModule.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is the "import .....config.Undefined" really needed ? (as you imported the entire package before)

import scala.Some
import com.escalatesoft.subcut.inject.config.Defined

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same comment as before.


/**
* 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")
}
}


Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Avoid empty lines change.

/**
* 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,
Expand All @@ -44,6 +60,8 @@ trait BindingModule { outer =>
case notLmip =>
key -> notLmip
}}).toMap

def configPropertySource: ConfigPropertySource = outer.configPropertySource
}
}

Expand All @@ -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)
}
Expand Down Expand Up @@ -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
Expand All @@ -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
}
Expand All @@ -182,9 +239,19 @@ class NewBindingModule(fn: MutableBindingModule => Unit) extends BindingModule {
* bind [QueryService] toSingle { new SlowInitQueryService }
* }
* </pre>
*
* 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)
}

/**
Expand Down Expand Up @@ -239,6 +306,7 @@ trait MutableBindingModule extends BindingModule { outer =>
def fixed: BindingModule = {
new BindingModule {
override val bindings = outer._bindings
def configPropertySource: ConfigPropertySource = outer.configPropertySource
}
}

Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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]()
}

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Avoid empty lines changes.

31 changes: 30 additions & 1 deletion src/main/scala/com/escalatesoft/subcut/inject/Injectable.scala
Original file line number Diff line number Diff line change
@@ -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
Expand All @@ -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

/**
Expand Down Expand Up @@ -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)
}

/**
Expand Down
Original file line number Diff line number Diff line change
@@ -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)

}


Original file line number Diff line number Diff line change
@@ -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
}
Original file line number Diff line number Diff line change
@@ -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
}
Loading