Skip to content

Commit 7b88a15

Browse files
authored
Merge pull request #1 from JLLeitschuh/feature/better_module_api
Better module api
2 parents d13b6f8 + d0daefe commit 7b88a15

File tree

16 files changed

+431
-36
lines changed

16 files changed

+431
-36
lines changed

.gitignore

+2-1
Original file line numberDiff line numberDiff line change
@@ -80,11 +80,12 @@ local.properties
8080

8181
## File-based project format:
8282
*.iws
83+
*.iml
8384

8485
## Plugin-specific files:
8586

8687
# IntelliJ
87-
/out/
88+
*/out/
8889

8990
# mpeltonen/sbt-idea plugin
9091
.idea_modules/

.travis.yml

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
language: java
22

3-
script: ./gradlew build --no-daemon
3+
script: ./gradlew build jacocoRootReport --no-daemon
44

55
before_cache:
66
- rm -f $HOME/.gradle/caches/modules-2/modules-2.lock

README.md

+39-4
Original file line numberDiff line numberDiff line change
@@ -20,20 +20,55 @@ Many of these problems have been solved by Kotlin using inline functions with `r
2020

2121
In java you can declare a type literal with:
2222
```java
23-
new TypeLiteral<Map<Integer, String>>() {}
23+
final TypeLiteral<Map<Integer, String>> someLiteral = new TypeLiteral<Map<Integer, String>>() {}
2424
```
2525
In Kotlin this syntax becomes even more verbose requiring more characters to write.
2626
```kotlin
27-
object : TypeLiteral<Map<Integer, String>>() {}
27+
val someLiteral = object : TypeLiteral<Map<Integer, String>>() {}
2828
```
2929
This library provides helpers like the one below that is much cleaner to read.
3030
```kotlin
31-
typeLiteral<Map<Int, String>>()
31+
val someLiteral = typeLiteral<Map<Int, String>>()
3232
```
3333

3434
### Guice Modules
3535

36-
TODO
36+
Creating a module in Java requires quite a bit of extra boilerplate.
37+
```java
38+
public class MyModule extends AbstractModule {
39+
@Override
40+
void configure() {
41+
bind(SomeService.class).to(SomeServiceImpl.class);
42+
}
43+
}
44+
45+
class Main {
46+
public static void main(String... args) {
47+
final Injector injector = Guice.createInjector(new MyModule());
48+
}
49+
}
50+
```
51+
This is the equivalent in Kotlin:
52+
```kotlin
53+
fun main(vararg args: String) {
54+
val myModule = module {
55+
bind(SomeService::class).to(SomeServiceImpl::class)
56+
}
57+
val injector = Guice.createInjector(myModule)
58+
}
59+
```
60+
61+
The library also defines a simple way of declaring private modules:
62+
```kotlin
63+
fun main(vararg args: String) {
64+
val privateModule = privateModule {
65+
bind(SomeService::class).to(SomeServiceImpl::class)
66+
expose(SomeService::class)
67+
}
68+
val injector = Guice.createInjector(privateModule)
69+
}
70+
```
71+
3772

3873
## Project Structure
3974
The intention is to structure this project such that Guice Core and each of it's respective extensions will
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
package org.jlleitschuh.guice
2+
3+
import com.google.inject.Binder
4+
import com.google.inject.Key
5+
import com.google.inject.TypeLiteral
6+
import org.jlleitschuh.guice.binder.AnnotatedBindingBuilderScope
7+
import org.jlleitschuh.guice.binder.LinkedBindingBuilderScope
8+
import kotlin.reflect.KClass
9+
10+
open class BinderScope
11+
internal constructor(private val binder: Binder) : Binder by binder {
12+
13+
/**
14+
* Convenience.
15+
*/
16+
fun binder() = this
17+
18+
/**
19+
* See the EDSL examples at [Binder].
20+
*/
21+
fun <T : Any> bind(type: KClass<T>): AnnotatedBindingBuilderScope<T> =
22+
bind(type.java)
23+
24+
/**
25+
* See the EDSL examples at [Binder].
26+
*/
27+
override fun <T : Any> bind(key: Key<T>): LinkedBindingBuilderScope<T> =
28+
LinkedBindingBuilderScope(binder.bind(key))
29+
30+
/**
31+
* See the EDSL examples at [Binder].
32+
*/
33+
override fun <T : Any> bind(type: Class<T>): AnnotatedBindingBuilderScope<T> =
34+
AnnotatedBindingBuilderScope(binder.bind(type))
35+
36+
/**
37+
* See the EDSL examples at [Binder].
38+
*/
39+
override fun <T : Any> bind(typeLiteral: TypeLiteral<T>): AnnotatedBindingBuilderScope<T> =
40+
AnnotatedBindingBuilderScope(binder.bind(typeLiteral))
41+
42+
43+
override fun newPrivateBinder(): PrivateBinderScope =
44+
PrivateBinderScope(binder.newPrivateBinder())
45+
46+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
package org.jlleitschuh.guice
2+
3+
import com.google.inject.Module
4+
import com.google.inject.PrivateModule
5+
6+
/**
7+
* Creates a [Module] with the [BinderScope] being configured when [Module.configure]
8+
* is called by Guice.
9+
*/
10+
fun module(configure: BinderScope.() -> Unit) =
11+
Module { binder -> BinderScope(binder).configure() }
12+
13+
/**
14+
* Creates a [PrivateModule] with the [PrivateBinderScope] being configured when [PrivateModule.configure]
15+
* is called by Guice.
16+
*/
17+
fun privateModule(configure: PrivateBinderScope.() -> Unit) =
18+
// The PrivateModule data type has some reflection checks to get the right type passed in. Easier to just use it.
19+
object : PrivateModule() {
20+
override fun configure() =
21+
configure.invoke(PrivateBinderScope(binder()))
22+
23+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
package org.jlleitschuh.guice
2+
3+
import com.google.inject.Key
4+
import com.google.inject.PrivateBinder
5+
import com.google.inject.TypeLiteral
6+
import com.google.inject.binder.AnnotatedElementBuilder
7+
import kotlin.reflect.KClass
8+
9+
class PrivateBinderScope
10+
internal constructor(private val privateBinder: PrivateBinder): BinderScope(privateBinder), PrivateBinder {
11+
12+
override fun skipSources(vararg classesToSkip: Class<*>): PrivateBinderScope {
13+
return PrivateBinderScope(privateBinder.skipSources(*classesToSkip))
14+
}
15+
16+
fun skipSources(vararg classesToSkip: KClass<*>): PrivateBinderScope=
17+
skipSources(*classesToSkip.map { it.java }.toTypedArray())
18+
19+
override fun withSource(source: Any): PrivateBinder {
20+
return PrivateBinderScope(privateBinder.withSource(source))
21+
}
22+
23+
override fun expose(key: Key<*>) {
24+
privateBinder.expose(key)
25+
}
26+
27+
override fun expose(type: Class<*>): AnnotatedElementBuilder {
28+
return privateBinder.expose(type)
29+
}
30+
31+
fun expose(type: KClass<*>): AnnotatedElementBuilder =
32+
expose(type.java)
33+
34+
override fun expose(type: TypeLiteral<*>): AnnotatedElementBuilder {
35+
return privateBinder.expose(type)
36+
}
37+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
package org.jlleitschuh.guice.binder
2+
3+
import com.google.inject.binder.AnnotatedBindingBuilder
4+
import com.google.inject.binder.LinkedBindingBuilder
5+
import kotlin.reflect.KClass
6+
7+
@Suppress("MemberVisibilityCanBePrivate")
8+
class AnnotatedBindingBuilderScope<T : Any>(
9+
private val annotatedBindingBuilder: AnnotatedBindingBuilder<T>
10+
) : LinkedBindingBuilderScope<T>(annotatedBindingBuilder), AnnotatedBindingBuilder<T> {
11+
12+
/**
13+
* See the EDSL examples at [com.google.inject.Binder].
14+
*/
15+
fun annotatedWith(annotationType: KClass<out Annotation>): LinkedBindingBuilderScope<T> =
16+
annotatedWith(annotationType.java)
17+
18+
/**
19+
* See the EDSL examples at [com.google.inject.Binder].
20+
*/
21+
override fun annotatedWith(annotationType: Class<out Annotation>): LinkedBindingBuilderScope<T> =
22+
LinkedBindingBuilderScope(annotatedBindingBuilder.annotatedWith(annotationType))
23+
24+
/**
25+
* See the EDSL examples at [com.google.inject.Binder].
26+
*/
27+
override fun annotatedWith(annotation: Annotation): LinkedBindingBuilderScope<T> =
28+
LinkedBindingBuilderScope(annotatedBindingBuilder.annotatedWith(annotation))
29+
30+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
package org.jlleitschuh.guice.binder
2+
3+
import com.google.inject.Key
4+
import com.google.inject.TypeLiteral
5+
import com.google.inject.binder.LinkedBindingBuilder
6+
import kotlin.reflect.KClass
7+
8+
open class LinkedBindingBuilderScope<T : Any>(
9+
private val linkedBindingBuilder: LinkedBindingBuilder<T>) :
10+
LinkedBindingBuilder<T> by linkedBindingBuilder {
11+
/*
12+
* I've thought about providing `to` as an infix operation, but realized it's a terrible idea.
13+
* The possible confusion of meaning between this `to` and the the one that creates a `Pair` is way too easy
14+
* a mistake to make.
15+
*/
16+
17+
/**
18+
* See the EDSL examples at [com.google.inject.Binder].
19+
*/
20+
fun to(implementation: KClass<out T>): ScopedBindingBuilderScope =
21+
to(implementation.java)
22+
23+
/**
24+
* See the EDSL examples at [com.google.inject.Binder].
25+
*/
26+
override fun to(implementation: Class<out T>): ScopedBindingBuilderScope =
27+
ScopedBindingBuilderScope(linkedBindingBuilder.to(implementation))
28+
29+
/**
30+
* See the EDSL examples at [com.google.inject.Binder].
31+
*/
32+
override fun to(implementation: TypeLiteral<out T>): ScopedBindingBuilderScope =
33+
ScopedBindingBuilderScope(linkedBindingBuilder.to(implementation))
34+
35+
/**
36+
* See the EDSL examples at [com.google.inject.Binder].
37+
*/
38+
override fun to(targetKey: Key<out T>): ScopedBindingBuilderScope =
39+
ScopedBindingBuilderScope(linkedBindingBuilder.to(targetKey))
40+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
package org.jlleitschuh.guice.binder
2+
3+
import com.google.inject.binder.ScopedBindingBuilder
4+
import kotlin.reflect.KClass
5+
6+
class ScopedBindingBuilderScope(
7+
private val scopedBuilder: ScopedBindingBuilder
8+
) : ScopedBindingBuilder by scopedBuilder {
9+
10+
/**
11+
* See the EDSL examples at [com.google.inject.Binder].
12+
*/
13+
fun `in`(scopeAnnotation: KClass<out Annotation>) =
14+
scopedBuilder.`in`(scopeAnnotation.java)
15+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
package org.jlleitschuh.guice
2+
3+
import com.google.inject.AbstractModule
4+
import com.google.inject.Guice
5+
import org.junit.jupiter.api.Assertions.assertSame
6+
import org.junit.jupiter.api.Assertions.assertTrue
7+
import org.junit.jupiter.api.Test
8+
9+
class ModuleTest {
10+
interface Interface
11+
12+
class Implementation : Interface
13+
14+
@Test
15+
fun `simple module`() {
16+
val simpleModule = module {
17+
bind(key<Interface>()).to(key<Implementation>())
18+
}
19+
val injector = Guice.createInjector(simpleModule)
20+
val theInterface = injector.getInstance(key<Interface>())
21+
assertTrue(theInterface is Implementation)
22+
}
23+
24+
@Test
25+
fun `module not using key`() {
26+
val simpleModule = module {
27+
bind(Interface::class).to(Implementation::class)
28+
}
29+
val injector = Guice.createInjector(simpleModule)
30+
val theInterface = injector.getInstance(key<Interface>())
31+
assertTrue(theInterface is Implementation)
32+
}
33+
34+
@Test
35+
fun `module using instance`() {
36+
val instance = Implementation()
37+
val simpleModule = module {
38+
bind(Interface::class).toInstance(instance)
39+
}
40+
val injector = Guice.createInjector(simpleModule)
41+
val theInterface = injector.getInstance(key<Interface>())
42+
assertSame(instance, theInterface)
43+
}
44+
45+
@Test
46+
fun `complicated module`() {
47+
val complicated = object : AbstractModule() {
48+
override fun configure() {
49+
bind(key<Interface>()).to(key<Implementation>())
50+
}
51+
}
52+
val injector = Guice.createInjector(complicated)
53+
injector.getInstance(key<Interface>())
54+
}
55+
56+
}
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,14 @@
11
package org.jlleitschuh.guice
22

3-
import io.kotlintest.matchers.shouldBe
4-
import io.kotlintest.specs.StringSpec
3+
import org.junit.jupiter.api.Assertions.assertEquals
4+
import org.junit.jupiter.api.Test
55

66

7-
class TypeLiteralTest : StringSpec() {
8-
init {
9-
"should keep type data" {
10-
val keySetType = typeLiteral<Map<Int, String>>().getReturnType(Map::class.java.getMethod("keySet"))
11-
keySetType.toString() shouldBe "java.util.Set<java.lang.Integer>"
12-
}
7+
class TypeLiteralTest {
8+
9+
@Test
10+
fun `should keep type data`() {
11+
val keySetType = typeLiteral<Map<Int, String>>().getReturnType(Map::class.java.getMethod("keySet"))
12+
assertEquals("java.util.Set<java.lang.Integer>", keySetType.toString())
1313
}
1414
}

gradle.properties

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
kotlin.version=1.1.4-3

gradle/wrapper/gradle-wrapper.jar

-375 Bytes
Binary file not shown.

gradle/wrapper/gradle-wrapper.properties

+1-1
Original file line numberDiff line numberDiff line change
@@ -2,4 +2,4 @@ distributionBase=GRADLE_USER_HOME
22
distributionPath=wrapper/dists
33
zipStoreBase=GRADLE_USER_HOME
44
zipStorePath=wrapper/dists
5-
distributionUrl=https\://services.gradle.org/distributions/gradle-4.1-all.zip
5+
distributionUrl=https\://services.gradle.org/distributions/gradle-4.4.1-all.zip

0 commit comments

Comments
 (0)