Skip to content

Commit

Permalink
Merge pull request #1 from JLLeitschuh/feature/better_module_api
Browse files Browse the repository at this point in the history
Better module api
  • Loading branch information
JLLeitschuh committed Feb 27, 2018
2 parents d13b6f8 + d0daefe commit 7b88a15
Show file tree
Hide file tree
Showing 16 changed files with 431 additions and 36 deletions.
3 changes: 2 additions & 1 deletion .gitignore
Expand Up @@ -80,11 +80,12 @@ local.properties

## File-based project format:
*.iws
*.iml

## Plugin-specific files:

# IntelliJ
/out/
*/out/

# mpeltonen/sbt-idea plugin
.idea_modules/
Expand Down
2 changes: 1 addition & 1 deletion .travis.yml
@@ -1,6 +1,6 @@
language: java

script: ./gradlew build --no-daemon
script: ./gradlew build jacocoRootReport --no-daemon

before_cache:
- rm -f $HOME/.gradle/caches/modules-2/modules-2.lock
Expand Down
43 changes: 39 additions & 4 deletions README.md
Expand Up @@ -20,20 +20,55 @@ Many of these problems have been solved by Kotlin using inline functions with `r

In java you can declare a type literal with:
```java
new TypeLiteral<Map<Integer, String>>() {}
final TypeLiteral<Map<Integer, String>> someLiteral = new TypeLiteral<Map<Integer, String>>() {}
```
In Kotlin this syntax becomes even more verbose requiring more characters to write.
```kotlin
object : TypeLiteral<Map<Integer, String>>() {}
val someLiteral = object : TypeLiteral<Map<Integer, String>>() {}
```
This library provides helpers like the one below that is much cleaner to read.
```kotlin
typeLiteral<Map<Int, String>>()
val someLiteral = typeLiteral<Map<Int, String>>()
```

### Guice Modules

TODO
Creating a module in Java requires quite a bit of extra boilerplate.
```java
public class MyModule extends AbstractModule {
@Override
void configure() {
bind(SomeService.class).to(SomeServiceImpl.class);
}
}

class Main {
public static void main(String... args) {
final Injector injector = Guice.createInjector(new MyModule());
}
}
```
This is the equivalent in Kotlin:
```kotlin
fun main(vararg args: String) {
val myModule = module {
bind(SomeService::class).to(SomeServiceImpl::class)
}
val injector = Guice.createInjector(myModule)
}
```

The library also defines a simple way of declaring private modules:
```kotlin
fun main(vararg args: String) {
val privateModule = privateModule {
bind(SomeService::class).to(SomeServiceImpl::class)
expose(SomeService::class)
}
val injector = Guice.createInjector(privateModule)
}
```


## Project Structure
The intention is to structure this project such that Guice Core and each of it's respective extensions will
Expand Down
46 changes: 46 additions & 0 deletions core/src/main/kotlin/org/jlleitschuh/guice/BinderScope.kt
@@ -0,0 +1,46 @@
package org.jlleitschuh.guice

import com.google.inject.Binder
import com.google.inject.Key
import com.google.inject.TypeLiteral
import org.jlleitschuh.guice.binder.AnnotatedBindingBuilderScope
import org.jlleitschuh.guice.binder.LinkedBindingBuilderScope
import kotlin.reflect.KClass

open class BinderScope
internal constructor(private val binder: Binder) : Binder by binder {

/**
* Convenience.
*/
fun binder() = this

/**
* See the EDSL examples at [Binder].
*/
fun <T : Any> bind(type: KClass<T>): AnnotatedBindingBuilderScope<T> =
bind(type.java)

/**
* See the EDSL examples at [Binder].
*/
override fun <T : Any> bind(key: Key<T>): LinkedBindingBuilderScope<T> =
LinkedBindingBuilderScope(binder.bind(key))

/**
* See the EDSL examples at [Binder].
*/
override fun <T : Any> bind(type: Class<T>): AnnotatedBindingBuilderScope<T> =
AnnotatedBindingBuilderScope(binder.bind(type))

/**
* See the EDSL examples at [Binder].
*/
override fun <T : Any> bind(typeLiteral: TypeLiteral<T>): AnnotatedBindingBuilderScope<T> =
AnnotatedBindingBuilderScope(binder.bind(typeLiteral))


override fun newPrivateBinder(): PrivateBinderScope =
PrivateBinderScope(binder.newPrivateBinder())

}
23 changes: 23 additions & 0 deletions core/src/main/kotlin/org/jlleitschuh/guice/Module.kt
@@ -0,0 +1,23 @@
package org.jlleitschuh.guice

import com.google.inject.Module
import com.google.inject.PrivateModule

/**
* Creates a [Module] with the [BinderScope] being configured when [Module.configure]
* is called by Guice.
*/
fun module(configure: BinderScope.() -> Unit) =
Module { binder -> BinderScope(binder).configure() }

/**
* Creates a [PrivateModule] with the [PrivateBinderScope] being configured when [PrivateModule.configure]
* is called by Guice.
*/
fun privateModule(configure: PrivateBinderScope.() -> Unit) =
// The PrivateModule data type has some reflection checks to get the right type passed in. Easier to just use it.
object : PrivateModule() {
override fun configure() =
configure.invoke(PrivateBinderScope(binder()))

}
37 changes: 37 additions & 0 deletions core/src/main/kotlin/org/jlleitschuh/guice/PrivateBinderScope.kt
@@ -0,0 +1,37 @@
package org.jlleitschuh.guice

import com.google.inject.Key
import com.google.inject.PrivateBinder
import com.google.inject.TypeLiteral
import com.google.inject.binder.AnnotatedElementBuilder
import kotlin.reflect.KClass

class PrivateBinderScope
internal constructor(private val privateBinder: PrivateBinder): BinderScope(privateBinder), PrivateBinder {

override fun skipSources(vararg classesToSkip: Class<*>): PrivateBinderScope {
return PrivateBinderScope(privateBinder.skipSources(*classesToSkip))
}

fun skipSources(vararg classesToSkip: KClass<*>): PrivateBinderScope=
skipSources(*classesToSkip.map { it.java }.toTypedArray())

override fun withSource(source: Any): PrivateBinder {
return PrivateBinderScope(privateBinder.withSource(source))
}

override fun expose(key: Key<*>) {
privateBinder.expose(key)
}

override fun expose(type: Class<*>): AnnotatedElementBuilder {
return privateBinder.expose(type)
}

fun expose(type: KClass<*>): AnnotatedElementBuilder =
expose(type.java)

override fun expose(type: TypeLiteral<*>): AnnotatedElementBuilder {
return privateBinder.expose(type)
}
}
@@ -0,0 +1,30 @@
package org.jlleitschuh.guice.binder

import com.google.inject.binder.AnnotatedBindingBuilder
import com.google.inject.binder.LinkedBindingBuilder
import kotlin.reflect.KClass

@Suppress("MemberVisibilityCanBePrivate")
class AnnotatedBindingBuilderScope<T : Any>(
private val annotatedBindingBuilder: AnnotatedBindingBuilder<T>
) : LinkedBindingBuilderScope<T>(annotatedBindingBuilder), AnnotatedBindingBuilder<T> {

/**
* See the EDSL examples at [com.google.inject.Binder].
*/
fun annotatedWith(annotationType: KClass<out Annotation>): LinkedBindingBuilderScope<T> =
annotatedWith(annotationType.java)

/**
* See the EDSL examples at [com.google.inject.Binder].
*/
override fun annotatedWith(annotationType: Class<out Annotation>): LinkedBindingBuilderScope<T> =
LinkedBindingBuilderScope(annotatedBindingBuilder.annotatedWith(annotationType))

/**
* See the EDSL examples at [com.google.inject.Binder].
*/
override fun annotatedWith(annotation: Annotation): LinkedBindingBuilderScope<T> =
LinkedBindingBuilderScope(annotatedBindingBuilder.annotatedWith(annotation))

}
@@ -0,0 +1,40 @@
package org.jlleitschuh.guice.binder

import com.google.inject.Key
import com.google.inject.TypeLiteral
import com.google.inject.binder.LinkedBindingBuilder
import kotlin.reflect.KClass

open class LinkedBindingBuilderScope<T : Any>(
private val linkedBindingBuilder: LinkedBindingBuilder<T>) :
LinkedBindingBuilder<T> by linkedBindingBuilder {
/*
* I've thought about providing `to` as an infix operation, but realized it's a terrible idea.
* The possible confusion of meaning between this `to` and the the one that creates a `Pair` is way too easy
* a mistake to make.
*/

/**
* See the EDSL examples at [com.google.inject.Binder].
*/
fun to(implementation: KClass<out T>): ScopedBindingBuilderScope =
to(implementation.java)

/**
* See the EDSL examples at [com.google.inject.Binder].
*/
override fun to(implementation: Class<out T>): ScopedBindingBuilderScope =
ScopedBindingBuilderScope(linkedBindingBuilder.to(implementation))

/**
* See the EDSL examples at [com.google.inject.Binder].
*/
override fun to(implementation: TypeLiteral<out T>): ScopedBindingBuilderScope =
ScopedBindingBuilderScope(linkedBindingBuilder.to(implementation))

/**
* See the EDSL examples at [com.google.inject.Binder].
*/
override fun to(targetKey: Key<out T>): ScopedBindingBuilderScope =
ScopedBindingBuilderScope(linkedBindingBuilder.to(targetKey))
}
@@ -0,0 +1,15 @@
package org.jlleitschuh.guice.binder

import com.google.inject.binder.ScopedBindingBuilder
import kotlin.reflect.KClass

class ScopedBindingBuilderScope(
private val scopedBuilder: ScopedBindingBuilder
) : ScopedBindingBuilder by scopedBuilder {

/**
* See the EDSL examples at [com.google.inject.Binder].
*/
fun `in`(scopeAnnotation: KClass<out Annotation>) =
scopedBuilder.`in`(scopeAnnotation.java)
}
56 changes: 56 additions & 0 deletions core/src/test/kotlin/org/jlleitschuh/guice/ModuleTest.kt
@@ -0,0 +1,56 @@
package org.jlleitschuh.guice

import com.google.inject.AbstractModule
import com.google.inject.Guice
import org.junit.jupiter.api.Assertions.assertSame
import org.junit.jupiter.api.Assertions.assertTrue
import org.junit.jupiter.api.Test

class ModuleTest {
interface Interface

class Implementation : Interface

@Test
fun `simple module`() {
val simpleModule = module {
bind(key<Interface>()).to(key<Implementation>())
}
val injector = Guice.createInjector(simpleModule)
val theInterface = injector.getInstance(key<Interface>())
assertTrue(theInterface is Implementation)
}

@Test
fun `module not using key`() {
val simpleModule = module {
bind(Interface::class).to(Implementation::class)
}
val injector = Guice.createInjector(simpleModule)
val theInterface = injector.getInstance(key<Interface>())
assertTrue(theInterface is Implementation)
}

@Test
fun `module using instance`() {
val instance = Implementation()
val simpleModule = module {
bind(Interface::class).toInstance(instance)
}
val injector = Guice.createInjector(simpleModule)
val theInterface = injector.getInstance(key<Interface>())
assertSame(instance, theInterface)
}

@Test
fun `complicated module`() {
val complicated = object : AbstractModule() {
override fun configure() {
bind(key<Interface>()).to(key<Implementation>())
}
}
val injector = Guice.createInjector(complicated)
injector.getInstance(key<Interface>())
}

}
16 changes: 8 additions & 8 deletions core/src/test/kotlin/org/jlleitschuh/guice/TypeLiteralTest.kt
@@ -1,14 +1,14 @@
package org.jlleitschuh.guice

import io.kotlintest.matchers.shouldBe
import io.kotlintest.specs.StringSpec
import org.junit.jupiter.api.Assertions.assertEquals
import org.junit.jupiter.api.Test


class TypeLiteralTest : StringSpec() {
init {
"should keep type data" {
val keySetType = typeLiteral<Map<Int, String>>().getReturnType(Map::class.java.getMethod("keySet"))
keySetType.toString() shouldBe "java.util.Set<java.lang.Integer>"
}
class TypeLiteralTest {

@Test
fun `should keep type data`() {
val keySetType = typeLiteral<Map<Int, String>>().getReturnType(Map::class.java.getMethod("keySet"))
assertEquals("java.util.Set<java.lang.Integer>", keySetType.toString())
}
}
1 change: 1 addition & 0 deletions gradle.properties
@@ -0,0 +1 @@
kotlin.version=1.1.4-3
Binary file modified gradle/wrapper/gradle-wrapper.jar
Binary file not shown.
2 changes: 1 addition & 1 deletion gradle/wrapper/gradle-wrapper.properties
Expand Up @@ -2,4 +2,4 @@ distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-4.1-all.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-4.4.1-all.zip

0 comments on commit 7b88a15

Please sign in to comment.