Skip to content
This repository has been archived by the owner on Jul 13, 2020. It is now read-only.

Commit

Permalink
Merge branch 'develop'
Browse files Browse the repository at this point in the history
  • Loading branch information
apatrida committed Aug 9, 2015
2 parents a4a4d60 + d73f897 commit a9c3b25
Show file tree
Hide file tree
Showing 20 changed files with 390 additions and 327 deletions.
29 changes: 29 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
2015-08-10 v1.1.0

Seperate API from core JAR. Core depends on API. a lot of small changes to structure for having independent scopes for injection, and cleaning.
Sorry for the breaks, but they will be tiny little changes to clean up.

API Changes:
* new property delegates that require scope to be passed in.

Core Changes:
* delegates by default point a the top level scope unless you use the scope-specific version.
* changed Injekt from object singleton that contained implementation to an var of type `InjektScope` (no API change, code should compile as-is but does need recompile)
* [BREAKING CHANGE] changed default registry/factory package to `uy.kohesive.injekt.registry.default` although unlikely referenced from user code.
* [BREAKING CHANGE] renamed `InjektInstanceFactory` to `InjektFactory`
* [BREAKING CHANGE] renamed `Injekt.Property` with delegates removed, use other Delegates.*
* [BREAKING CHANGE] `InkektRegistrar` is changed to only be a combination of two interfaces, `InjektRegistry` and `InjektFactory` and nothing else.
* [BREAKING CHANGE] changed `InjektMain` / `InjektScopedMain` to also be a module with same `registerInjectables` method
* [BREAKING CHANGE] changed `exportInjektables` to `registerInjectables`
* [BREAKING CHANGE] changed other words in method names that used "injekt" to "inject" to be more natural, old versions deprecated to be removed in 1 release
* Introduced `InjektScope` which allows different parts of the app to have different injection registries
* Introduced `InjektScopedMain` which is like `InjektMain` but for a specified scope instead of the global scope.



=====

2015-08-04 v1.0.0 released to Maven Central

First version:
uy.kohesive.injekt:injekt-core:1.0.0
48 changes: 23 additions & 25 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,15 +12,15 @@ First, include the dependency in your Gradle / Maven projects, ones that have Ko

**Gradle:**
```
compile "uy.kohesive.injekt:injekt-core:1.0.+"
compile "uy.kohesive.injekt:injekt-core:1.1.+"
```

**Maven:**
```
<dependency>
<groupId>uy.kohesive.injekt</groupId>
<artifactId>injekt-core</artifactId>
<version>[1.0.0,1.1.0)</version>
<version>[1.1.0,1.2.0)</version>
</dependency>
```

Expand All @@ -29,7 +29,7 @@ compile "uy.kohesive.injekt:injekt-core:1.0.+"

## Injektor "Main"

At the earliest point in your application startup, you register singletons, factories and your logging factories. For the simplest version of this process, you can use the InjektModule on an object or companion object (from [Injekt Examples](https://github.com/kohesive/injekt/blob/master/core/src/example/kotlin/uy/kohesive/injekt/example/MyApp.kt))
At the earliest point in your application startup, you register singletons, factories and your logging factories. For the simplest version of this process, you can use the `InjektModule` on an object or companion object (from [Injekt Examples](https://github.com/kohesive/injekt/blob/master/core/src/example/kotlin/uy/kohesive/injekt/example/MyApp.kt))

```kotlin
class MyApp {
Expand All @@ -39,9 +39,9 @@ class MyApp {
MyApp().run()
}

// the InjektModule() will call me back here on a method I override. And all my functions for registration are
// the Injekt system will call me back here on a method I override. And all my functions for registration are
// easy to find on the receiver class
override fun InjektRegistrar.registerInjektables() {
override fun InjektRegistrar.registerInjectables() {
// let's setup my logger first
addLoggerFactory<Logger>({ byName -> LoggerFactory.getLogger(byName) }, { byClass -> LoggerFactory.getLogger(byClass) })

Expand All @@ -53,9 +53,9 @@ class MyApp {
addSingletonFactory { DontCreateUntilWeNeedYa() }

// or lets only have one database connection per thread, basically a singleton per thread
addPerThreadFactory { JdbcDatabaseConnection(Injekt.get()) } // wow, nested injektions!!!
addPerThreadFactory { JdbcDatabaseConnection(Injekt.get()) } // wow, nested injections!!!

// or give me a new one each time it is injekted
// or give me a new one each time it is injected
addFactory { LazyDazy() }

// or be weird and use extension functions on classes that are visible while in this lambda
Expand All @@ -66,7 +66,7 @@ class MyApp {
val pets = listOf(NamedPet("Bongo"), NamedPet("Dancer"), NamedPet("Cheetah")).map { it.name to it}.toMap()
addPerKeyFactory { petName: String -> pets.get(petName) }

// use prebuilt Injektable packages
// import prebuilt Injekt modules
importModule(AmazonS3InjektModule)
}
}
Expand All @@ -75,12 +75,12 @@ class MyApp {
}
```

And once they are registered, anything else in the system can access them, for example as class properties they can be injekted using delegates (you should `import uy.kohesion.injekt.*` to get all delegates):
And once they are registered, anything else in the system can access them, for example as class properties they can be injected using delegates:

```kotlin
val log: Logger by Delegates.injektLogger()
val laziest: LazyDazy by Delegates.injektLazy()
val lessLazy: LazyDazy by Delegates.injektValue()
val log: Logger by Delegates.injectLogger()
val laziest: LazyDazy by Delegates.injectLazy()
val lessLazy: LazyDazy by Delegates.injectValue()
```

or directly as assignments both as property declarations and local assignemtns:
Expand All @@ -96,11 +96,11 @@ And they can be used in constructors and methods as default parameters:
public fun foo(dbConnectParms: DatabaseConfig = Injekt.get()) { ... }
```

And since we have registered in the first example a mix of types, including thread specific injektions and key/parameter based, here they are in action:
And since we have registered in the first example a mix of types, including thread specific injections and key/parameter based, here they are in action:

```kotlin
public fun run() {
// even local variables can be injekted, or rather "got"
// even local variables can be injected, or rather "got"
val something = Injekt.get<DontCreateUntilWeNeedYa>()
startHttpServer()
}
Expand All @@ -112,7 +112,7 @@ And since we have registered in the first example a mix of types, including thre
val db: JdbcDatabaseConnection = Injekt.get() // we have a connection per thread now!

if (context.params.containsKey("pet")) {
// injekt from a factory that requires a key / parameter
// inject from a factory that requires a key / parameter
val pet: NamedPet = Injekt.get(context.params.get("pet")!!)
// or other form without reified parameters
val pet2 = Injekt.get<NamedPet>(context.params.get("pet")!!)
Expand All @@ -123,36 +123,34 @@ And since we have registered in the first example a mix of types, including thre

## Packaged Injektables

Now that you have mastered Injektions, let's make modules of our application provide their own injektables. Say our Amazon AWS helper module has a properly configured credential provider chain, and can make clients for us nicely. It is best to have that module decide the construction and make it available to other modules. And it's easy. Create an object that extends `InjektModule` and then it is pretty much the same as before:
Now that you have mastered injections, let's make modules of our application provide their own injectable items. Say our Amazon AWS helper module has a properly configured credential provider chain, and can make clients for us nicely. It is best to have that module decide the construction and make it available to other modules. And it's easy. Create an object that extends `InjektModule` and then it is pretty much the same as before:

```kotlin
public object AmazonS3InjektModule : InjektModule {
override fun InjektRegistrar.exportInjektables() {
override fun InjektRegistrar.registerInjectables() {
addSingletonFactory { AmazonS3Client(defaultCredentialsProviderChain()) }
}
}
```

The only difference between an `InjektMain` and `InjektModule` object is that a `InjektMain` is automatically called to initalize, whereas an `InjektModule` does not do anything until it is imported by another `InjektMain` or `InjektModule`. Using `InjektModule` is simple, go back to the first example at the top of this page and you will see it imported with a simple
The only difference between an `InjektMain` and `InjektModule` object is that a `InjektMain` is automatically called to initialize, whereas an `InjektModule` does not do anything until it is imported by another `InjektMain` or `InjektModule`. Using `InjektModule` is simple, go back to the first example at the top of this page and you will see it imported with a simple

```kotlin
// use prebuilt Injektable packages
// use prebuilt package
importModule(AmazonS3InjektModule)
```

Note: if you extend `InjektMain` you can also implement `InjektModule` interface and be both at the same time. When doing this, put common Injektables into the module `exportInjektables()` method and import it during the main `registerInjektables()` method with simple:

```kotlin
importInjektables(this)
```
Note: if you extend `InjektMain` you are also a module that can be imported. But beware that if you use scopes (see `InjektScope` and `InjektScopeMain` then you should not use `InjektMain` which will always import into the global scope). More on scopes in a future release, not intended to be used yet.

## One Instance Per-Thread Factories -- a tip

When using a factory that is per-thread (one instance of each object is generated per thread), it is important that you consider how the instance is used. If you generate it near the start of processing on a thread avoid passing the object to be used on a different thread if you truly want to isolate the instances by thread. Currently, the default registry has lock contention across threads for these per-thread factories, so asking too often will cause possible thread contention. But, when [issue #2](https://github.com/kohesive/injekt/issues/2) is resolved, thread local storage will be used making it very fast to grab the instance any time you need it rather than holding onto the instance for a long duration. Until then, watch how you uses these.

## Coming soon... (RoadMap)

* Konfiguration loading, binding and injektion as a separate module.
* More about scopes
* Materializing object graphs without explicit calls to Injekt
* Configuration loading, binding and injektion as a separate module.
* Tell me what you would like to see, add Issues here in Github with requests.

## Recommneded libraries:
Expand Down
5 changes: 5 additions & 0 deletions api/src/main/kotlin/uy/kohesive/injekt/Exceptions.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
package uy.kohesive.injekt

class InjektionException(msg: String) : RuntimeException(msg)


19 changes: 19 additions & 0 deletions api/src/main/kotlin/uy/kohesive/injekt/Factory.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package uy.kohesive.injekt

public interface InjektFactory {
public fun <R> getInstance(forClass: Class<R>): R
public fun <R, K> getKeyedInstance(forClass: Class<R>, key: K): R
public fun <R> getLogger(expectedLoggerClass: Class<R>, name: String): R
public fun <R> getLogger(expectedLoggerClass: Class<R>, forClass: Class<*>): R

public final inline fun <reified T> get(): T = getInstance(javaClass<T>())
public final inline fun <reified T> get(key: Any): T = getKeyedInstance(javaClass<T>(), key)

public final fun <T> get(forClass: Class<T>): T = getInstance(forClass)
public final fun <T> get(forClass: Class<T>, key: Any): T = getKeyedInstance(forClass, key)

public final inline fun <reified T> logger(byClass: Class<*>): T = getLogger(javaClass<T>(), byClass)
public final inline fun <reified T> logger(byName: String): T = getLogger(javaClass<T>(), byName)
public final inline fun <reified T> logger(byObject: Any): T = getLogger(javaClass<T>(), byObject.javaClass)
}

24 changes: 24 additions & 0 deletions api/src/main/kotlin/uy/kohesive/injekt/Modules.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package uy.kohesive.injekt


/**
* A startup module that registers and uses singletons/object factories from a specific scope
*/
public abstract class InjektScopedMain(public val scope: InjektScope) : InjektModule {
init {
scope.registrar.registerInjectables()
}
}

/**
* A package of injectable items that can be included into a scope of someone else
*/
public interface InjektModule {
internal fun registerWith(intoModule: InjektRegistrar) {
intoModule.registerInjectables()
}

fun InjektRegistrar.registerInjectables()
}


7 changes: 7 additions & 0 deletions api/src/main/kotlin/uy/kohesive/injekt/Registrar.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package uy.kohesive.injekt

interface InjektRegistrar: InjektRegistry, InjektFactory {
public fun importModule(submodule: InjektModule) {
submodule.registerWith(this)
}
}
76 changes: 76 additions & 0 deletions api/src/main/kotlin/uy/kohesive/injekt/Registry.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
package uy.kohesive.injekt

import kotlin.reflect.KClass

public interface InjektRegistry {
public fun <T : Any> addSingleton(forClass: Class<T>, singleInstance: T)
public fun <R> addSingletonFactory(forClass: Class<R>, factoryCalledOnce: () -> R)
public fun <R> addFactory(forClass: Class<R>, factoryCalledEveryTime: () -> R)
public fun <R> addPerThreadFactory(forClass: Class<R>, factoryCalledOncePerThread: () -> R)
public fun <R, K> addPerKeyFactory(forClass: Class<R>, forKeyClass: Class<K>, factoryCalledPerKey: (K) -> R)
public fun <R, K> addPerThreadPerKeyFactory(forClass: Class<R>, forKeyClass: Class<K>, factoryCalledPerKeyPerThread: (K) -> R)
public fun <R : Any> addLoggerFactory(forLoggerClass: Class<R>, factoryByName: (String) -> R, factoryByClass: (Class<*>) -> R)
public fun <T> alias(existingRegisteredClass: Class<T>, otherClassesThatAreSame: List<Class<*>>)
public fun <T> hasFactory(forClass: Class<T>): Boolean


public final inline fun <reified T : Any> T.registerAsSingleton() {
addSingleton(this)
}

public final inline fun <reified R> KClass<R>.registerSingletonFactory(@noinline factoryCalledOnce: () -> R) {
addSingletonFactory(factoryCalledOnce)
}

public final inline fun <reified R> KClass<R>.registerFactory(@noinline factoryCalledEveryTime: () -> R) {
addFactory(factoryCalledEveryTime)
}

public final inline fun <reified R> KClass<R>.registerPerThreadFactory(@noinline factoryCalledOncePerThread: () -> R) {
addPerThreadFactory(factoryCalledOncePerThread)
}

public final inline fun <reified R, reified K> KClass<R>.registerPerKeyFactory(@noinline factoryCalledPerKey: (K) -> R) {
addPerKeyFactory(factoryCalledPerKey)
}

public final inline fun <reified R, reified K> KClass<R>.registerPerThreadPerKeyFactory(@noinline factoryCalledPerKeyPerThread: (K) -> R) {
addPerThreadPerKeyFactory(factoryCalledPerKeyPerThread)
}

public final inline fun <reified R> KClass<R>.registerLoggerFactory(@noinline factoryByName: (String) -> R, @noinline factoryByClass: (Class<*>) -> R) {
addLoggerFactory(factoryByName, factoryByClass)
}

public final inline fun <reified T : Any> T.aliasOthersToMe(classes: List<Class<*>>) {
alias(javaClass<T>(), classes)
}

public final inline fun <reified T : Any> addSingleton(singleInstance: T) {
addSingleton(javaClass<T>(), singleInstance)
}

public final inline fun <reified R> addSingletonFactory(@noinline factoryCalledOnce: () -> R) {
addSingletonFactory(javaClass<R>(), factoryCalledOnce)
}

public final inline fun <reified R> addFactory(@noinline factoryCalledEveryTime: () -> R) {
addFactory(javaClass<R>(), factoryCalledEveryTime)
}

public final inline fun <reified R> addPerThreadFactory(@noinline factoryCalledOncePerThread: () -> R) {
addPerThreadFactory(javaClass<R>(), factoryCalledOncePerThread)
}

public final inline fun <reified R, reified K> addPerKeyFactory(@noinline factoryCalledPerKey: (K) -> R) {
addPerKeyFactory(javaClass<R>(), javaClass<K>(), factoryCalledPerKey)
}

public final inline fun <reified R, reified K> addPerThreadPerKeyFactory(@noinline factoryCalledPerKeyPerThread: (K) -> R) {
addPerThreadPerKeyFactory(javaClass<R>(), javaClass<K>(), factoryCalledPerKeyPerThread)
}

public final inline fun <reified R> addLoggerFactory(@noinline factoryByName: (String) -> R, @noinline factoryByClass: (Class<*>) -> R) {
addLoggerFactory(javaClass<R>(), factoryByName, factoryByClass)
}
}
78 changes: 78 additions & 0 deletions api/src/main/kotlin/uy/kohesive/injekt/Scope.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
package uy.kohesive.injekt

import kotlin.properties.Delegates
import kotlin.properties.ReadOnlyProperty


/**
* Not much difference than a InjektRegistrar for now...
*/
public open class InjektScope(val registrar: InjektRegistrar) : InjektRegistrar by registrar{
override fun <T> alias(existingRegisteredClass: Class<T>, otherClassesThatAreSame: List<Class<*>>) {
if (!hasFactory(existingRegisteredClass)) {
throw InjektionException("Cannot alias anything to ${existingRegisteredClass.getName()}, it does not have a registered factory")
}
for (oneOther in otherClassesThatAreSame) {
if (!oneOther.isAssignableFrom(existingRegisteredClass)) {
throw InjektionException("Cannot alias ${oneOther.getName()} to ${existingRegisteredClass.getName()}, not compatible types")
}
}
registrar.alias(existingRegisteredClass, otherClassesThatAreSame)
}

}

public inline fun <reified T> Delegates.injectLazy(scope: InjektScope): ReadOnlyProperty<Any?, T> {
return kotlin.properties.Delegates.lazy { scope.getInstance(javaClass<T>()) }
}

public inline fun <reified T> Delegates.injectValue(scope: InjektScope): ReadOnlyProperty<Any?, T> {
val value: T = scope.getInstance(javaClass<T>())
return object : ReadOnlyProperty<Any?, T> {
public override fun get(thisRef: Any?, desc: PropertyMetadata): T {
return value
}
}
}

public inline fun <reified T> Delegates.injectLazy(scope: InjektScope, key: Any): ReadOnlyProperty<Any?, T> {
return kotlin.properties.Delegates.lazy {
scope.getKeyedInstance(javaClass<T>(), key)
}
}

public inline fun <reified T> Delegates.injectValue(scope: InjektScope, key: Any): ReadOnlyProperty<Any?, T> {
val value: T = scope.getKeyedInstance(javaClass<T>(), key)
return object : ReadOnlyProperty<Any?, T> {
public override fun get(thisRef: Any?, desc: PropertyMetadata): T {
return value
}
}
}

public inline fun <reified R, reified T> Delegates.injectLogger(scope: InjektScope): ReadOnlyProperty<R, T> {
val value: T = scope.getLogger(javaClass<T>(), javaClass<R>())
return object : ReadOnlyProperty<R, T> {
public override fun get(thisRef: R, desc: PropertyMetadata): T {
return value
}
}
}

public inline fun <reified R, reified T> Delegates.injectLogger(scope: InjektScope, byClass: Class<*>): ReadOnlyProperty<R, T> {
val value: T = scope.getLogger(javaClass<T>(), byClass)
return object : ReadOnlyProperty<R, T> {
public override fun get(thisRef: R, desc: PropertyMetadata): T {
return value
}
}
}

public inline fun <reified R, reified T> Delegates.injectLogger(scope: InjektScope, byName: String): ReadOnlyProperty<R, T> {
val value: T = scope.getLogger(javaClass<T>(), byName)
return object : ReadOnlyProperty<R, T> {
public override fun get(thisRef: R, desc: PropertyMetadata): T {
return value
}
}
}
3 changes: 3 additions & 0 deletions core/build.gradle
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
dependencies {
compile relativeProject(":injekt-api")
}
Loading

0 comments on commit a9c3b25

Please sign in to comment.