Permalink
Browse files

Allow element creation via delegate provider

After considering a few different names for the delegate provider (`new`
/ `factory` / `builder` / `creator` / `creating`) I settled on
`creating`:

 * it shares the same prefix with the element creation methods
   (`create`) so it's arguably more discoverable

 * it reads more like prose, specially when coupled with the type

    tasks {
        val deploy by creating(Copy::class) {
            // ...
        }
    }

Resolves #35
  • Loading branch information...
1 parent 68484cd commit 2e398590b2639ab3f2239585f207a8dae71185ae @bamboo bamboo committed Jan 12, 2017
@@ -1,18 +1,22 @@
-val helloTask = task("hello") {
- doLast { println("Hello!") }
-}
-task("goodbye") {
- dependsOn(helloTask) // dependsOn task reference
- doLast { println("Goodbye!") }
-}
+tasks {
-task("chat") {
- dependsOn("goodbye") // dependsOn task name
-}
+ val hello by creating { // refactor friendly task definition
+ doLast { println("Hello!") }
+ }
+
+ "goodbye" {
+ dependsOn(hello) // dependsOn task reference
+ doLast { println("Goodbye!") }
+ }
+
+ "chat" {
+ dependsOn("goodbye") // dependsOn task name
+ }
-task("mixItUp") {
- dependsOn(helloTask, "goodbye")
+ "mixItUp" {
+ dependsOn(hello, "goodbye")
+ }
}
defaultTasks("chat")
@@ -35,6 +35,13 @@ operator fun <T : Any> NamedDomainObjectCollection<T>.get(name: String): T =
getByName(name)
+/**
+ * Allows a [NamedDomainObjectCollection] to be used as a property delegate.
+ *
+ * @throws UnknownDomainObjectException upon property access when there is no such object in the given collection.
+ *
+ * @see NamedDomainObjectCollection.getByName
+ */
inline operator fun <T : Any, reified U : T> NamedDomainObjectCollection<T>.getValue(thisRef: Any?, property: KProperty<*>): U =
getByName(property.name).let {
it as? U
@@ -20,6 +20,7 @@ import org.gradle.api.NamedDomainObjectContainer
import org.gradle.api.PolymorphicDomainObjectContainer
import kotlin.reflect.KClass
+import kotlin.reflect.KProperty
/**
@@ -37,20 +38,44 @@ inline operator fun <T : Any, C : NamedDomainObjectContainer<T>> C.invoke(
class NamedDomainObjectContainerConfiguration<T : Any>(
- private val container: NamedDomainObjectContainer<T>) {
+ private val container: NamedDomainObjectContainer<T>) : NamedDomainObjectContainer<T> by container {
+ /**
+ * @see NamedDomainObjectContainer.maybeCreate
+ */
inline operator fun String.invoke(configuration: T.() -> Unit): T =
this().apply(configuration)
+ /**
+ * @see NamedDomainObjectContainer.maybeCreate
+ */
operator fun String.invoke(): T =
container.maybeCreate(this)
+ /**
+ * @see PolymorphicDomainObjectContainer.maybeCreate
+ */
inline operator fun <U : T> String.invoke(type: KClass<U>, configuration: U.() -> Unit): U =
this(type).apply(configuration)
+ /**
+ * @see PolymorphicDomainObjectContainer.maybeCreate
+ */
operator fun <U : T> String.invoke(type: KClass<U>): U =
polymorphicDomainObjectContainer().maybeCreate(this, type.java)
+ /**
+ * Provides a property delegate that creates elements of the given [type].
+ */
+ fun <U : T> creating(type: KClass<U>) =
+ polymorphicDomainObjectContainer().creating(type)
+
+ /**
+ * Provides a property delegate that creates elements of the given [type] with the given [configuration].
+ */
+ fun <U : T> creating(type: KClass<U>, configuration: U.() -> Unit) =
+ polymorphicDomainObjectContainer().creating(type, configuration)
+
private fun polymorphicDomainObjectContainer() =
// We must rely on the dynamic cast and possible runtime failure here
// due to a Kotlin extension member limitation.
@@ -60,3 +85,59 @@ class NamedDomainObjectContainerConfiguration<T : Any>(
?: throw IllegalArgumentException("Container '$container' is not polymorphic.")
}
+
+/**
+ * Provides a property delegate that creates elements of the default collection type.
+ */
+val <T : Any> NamedDomainObjectContainer<T>.creating
+ get() = NamedDomainObjectContainerDelegateProvider(this, {})
+
+
+/**
+ * Provides a property delegate that creates elements of the default collection type with the given [configuration].
+ */
+fun <T : Any> NamedDomainObjectContainer<T>.creating(configuration: T.() -> Unit) =
+ NamedDomainObjectContainerDelegateProvider(this, configuration)
+
+
+class NamedDomainObjectContainerDelegateProvider<T : Any>(
+ val container: NamedDomainObjectContainer<T>, val configuration: T.() -> Unit) {
+
+ operator fun provideDelegate(thisRef: Any?, property: KProperty<*>) =
+ container.apply {
+ create(property.name).apply(configuration)
+ }
+}
+
+
+/**
+ * Provides a property delegate that creates elements of the given [type].
+ */
+fun <T : Any, U : T> PolymorphicDomainObjectContainer<T>.creating(
+ type: KClass<U>) = creating(type.java, {})
+
+
+/**
+ * Provides a property delegate that creates elements of the given [type] with the given [configuration].
+ */
+fun <T : Any, U : T> PolymorphicDomainObjectContainer<T>.creating(
+ type: KClass<U>, configuration: U.() -> Unit) = creating(type.java, configuration)
+
+
+/**
+ * Provides a property delegate that creates elements of the given [type] expressed as a [java.lang.Class]
+ * with the given [configuration].
+ */
+fun <T : Any, U : T> PolymorphicDomainObjectContainer<T>.creating(type: Class<U>, configuration: U.() -> Unit) =
+ PolymorphicDomainObjectContainerDelegateProvider(this, type, configuration)
+
+
+class PolymorphicDomainObjectContainerDelegateProvider<T : Any, U : T>(
+ val container: PolymorphicDomainObjectContainer<T>, val type: Class<U>, val configuration: U.() -> Unit) {
+
+ @Suppress("unchecked_cast")
+ operator fun provideDelegate(thisRef: Any?, property: KProperty<*>) =
+ container.apply {
+ create(property.name, type).apply(configuration)
+ } as PolymorphicDomainObjectContainer<U>
+}
@@ -6,10 +6,12 @@ import com.nhaarman.mockito_kotlin.verify
import org.gradle.api.NamedDomainObjectContainer
import org.gradle.api.PolymorphicDomainObjectContainer
+import org.gradle.api.Task
import org.gradle.api.tasks.Delete
import org.gradle.api.tasks.TaskContainer
-import org.hamcrest.CoreMatchers.*
+import org.hamcrest.CoreMatchers.equalTo
+import org.hamcrest.CoreMatchers.sameInstance
import org.hamcrest.MatcherAssert.assertThat
import org.junit.Test
@@ -109,4 +111,120 @@ class NamedDomainObjectContainerExtensionsTest {
verify(clean).delete("build")
}
+
+ @Test
+ fun `can create element in monomorphic container via delegated property`() {
+
+ val container = mock<NamedDomainObjectContainer<DomainObject>> {
+ on { create("domainObject") } doReturn DomainObject()
+ }
+
+ @Suppress("unused_variable")
+ val domainObject by container.creating
+
+ verify(container).create("domainObject")
+ }
+
+ @Test
+ fun `can create and configure element in monomorphic container via delegated property`() {
+
+ val element = DomainObject()
+ val container = mock<NamedDomainObjectContainer<DomainObject>> {
+ on { create("domainObject") } doReturn element
+ on { getByName("domainObject") } doReturn element
+ }
+
+ val domainObject by container.creating {
+ foo = "domain-foo"
+ bar = true
+ }
+
+ verify(container).create("domainObject")
+ assertThat(
+ domainObject,
+ equalTo(DomainObject("domain-foo", true)))
+ }
+
+ @Test
+ fun `can create and configure element in polymorphic container via delegated property`() {
+
+ val element = DomainObjectBase.Foo()
+ val container = mock<PolymorphicDomainObjectContainer<DomainObjectBase>> {
+ on { create("domainObject", DomainObjectBase.Foo::class.java) } doReturn element
+ on { getByName("domainObject") } doReturn element
+ }
+
+ val domainObject by container.creating(type = DomainObjectBase.Foo::class) {
+ foo = "domain-foo"
+ }
+
+ verify(container).create("domainObject", DomainObjectBase.Foo::class.java)
+ assertThat(
+ domainObject.foo,
+ equalTo("domain-foo"))
+ }
+
+ @Test
+ fun `can create element in polymorphic container via delegated property`() {
+
+ val container = mock<PolymorphicDomainObjectContainer<DomainObjectBase>> {
+ on { create("domainObject", DomainObjectBase.Foo::class.java) } doReturn DomainObjectBase.Foo()
+ }
+
+ @Suppress("unused_variable")
+ val domainObject by container.creating(DomainObjectBase.Foo::class)
+
+ verify(container).create("domainObject", DomainObjectBase.Foo::class.java)
+ }
+
+ @Test
+ fun `can create element within configuration block via delegated property`() {
+ val tasks = mock<TaskContainer> {
+ on { create("hello") } doReturn mock<Task>()
+ }
+
+ tasks {
+ @Suppress("unused_variable")
+ val hello by creating
+ }
+ verify(tasks).create("hello")
+ }
+
+ @Test
+ fun `can create element of specific type within configuration block via delegated property`() {
+
+ val container = mock<PolymorphicDomainObjectContainer<DomainObjectBase>> {
+ on { create("domainObject", DomainObjectBase.Foo::class.java) } doReturn DomainObjectBase.Foo()
+ }
+
+ container {
+
+ @Suppress("unused_variable")
+ val domainObject by creating(type = DomainObjectBase.Foo::class)
+ }
+
+ verify(container).create("domainObject", DomainObjectBase.Foo::class.java)
+ }
+
+ @Test
+ fun `can create and configure element of specific type within configuration block via delegated property`() {
+
+ val element = DomainObjectBase.Foo()
+ val container = mock<PolymorphicDomainObjectContainer<DomainObjectBase>> {
+ on { create("domainObject", DomainObjectBase.Foo::class.java) } doReturn element
+ }
+
+ container {
+
+ @Suppress("unused_variable")
+ val domainObject by creating(DomainObjectBase.Foo::class) {
+ foo = "domain-foo"
+ }
+ }
+
+ verify(container).create("domainObject", DomainObjectBase.Foo::class.java)
+ assertThat(
+ element.foo,
+ equalTo("domain-foo"))
+ }
}

0 comments on commit 2e39859

Please sign in to comment.