From f12d68dbdb0bc2b080363444dcca75851c2a7e81 Mon Sep 17 00:00:00 2001 From: Robin Friedli Date: Sun, 17 Apr 2022 01:31:58 +0200 Subject: [PATCH] enable forking of Modes and add ImmutableMode - make sure the default empty Mode is immutable --- README.md | 4 +- build.gradle | 2 +- .../exec/AbstractNestedModeWrapper.kt | 13 +++-- src/main/kotlin/net/robinfriedli/exec/Mode.kt | 52 +++++++++++++++---- .../net/robinfriedli/exec/ModeWrapper.kt | 10 +++- .../net/robinfriedli/exec/modes/ModesTest.kt | 37 +++++++++++++ 6 files changed, 101 insertions(+), 17 deletions(-) diff --git a/README.md b/README.md index b402542..82c3ac8 100644 --- a/README.md +++ b/README.md @@ -13,7 +13,7 @@ repositories { } dependencies { - implementation "com.github.robinfriedli:exec:1.3" + implementation "com.github.robinfriedli:exec:1.4" } ``` @@ -22,7 +22,7 @@ dependencies { com.github.robinfriedli exec - 1.3 + 1.4 pom diff --git a/build.gradle b/build.gradle index 6125449..4306777 100644 --- a/build.gradle +++ b/build.gradle @@ -5,7 +5,7 @@ plugins { } group = "net.robinfriedli" -version = "1.3" +version = "1.4" sourceCompatibility = "8" targetCompatibility = "8" diff --git a/src/main/kotlin/net/robinfriedli/exec/AbstractNestedModeWrapper.kt b/src/main/kotlin/net/robinfriedli/exec/AbstractNestedModeWrapper.kt index b4509a6..9af0f68 100644 --- a/src/main/kotlin/net/robinfriedli/exec/AbstractNestedModeWrapper.kt +++ b/src/main/kotlin/net/robinfriedli/exec/AbstractNestedModeWrapper.kt @@ -9,17 +9,22 @@ abstract class AbstractNestedModeWrapper : ModeWrapper { private var outer: ModeWrapper? = null - override fun combine(mode: ModeWrapper): ModeWrapper { + final override fun combine(mode: ModeWrapper): ModeWrapper { mode.setOuter(this) return mode } - override fun getOuter(): ModeWrapper? { + final override fun getOuter(): ModeWrapper? { return outer } - override fun setOuter(mode: ModeWrapper?) { + final override fun setOuter(mode: ModeWrapper?) { this.outer = mode } -} \ No newline at end of file + override fun fork(): ModeWrapper { + // since this combine implementation does not modify this ModeWrapper instance it is inherently immutable + return this + } + +} diff --git a/src/main/kotlin/net/robinfriedli/exec/Mode.kt b/src/main/kotlin/net/robinfriedli/exec/Mode.kt index 1f5d52c..3510fb9 100644 --- a/src/main/kotlin/net/robinfriedli/exec/Mode.kt +++ b/src/main/kotlin/net/robinfriedli/exec/Mode.kt @@ -3,13 +3,17 @@ package net.robinfriedli.exec /** * Helper class to build a mode with the given [ModeWrapper] applied */ -class Mode { +open class Mode() { + + private constructor(currentWrapper: ModeWrapper?) : this() { + this.currentWrapper = currentWrapper + } private var currentWrapper: ModeWrapper? = null companion object { @JvmStatic - val empty = create() + val empty = create().immutable() @JvmStatic fun create(): Mode { @@ -23,19 +27,49 @@ class Mode { * inside the task of the current wrapper. * * @param wrapper the new wrapper to add, normally wrapping it inside the current wrapper - * @return this mode with the new wrapper applied + * @return this mode with the new wrapper applied, or a new Mode instance if this Mode was immutable */ - fun with(wrapper: ModeWrapper): Mode { - if (currentWrapper != null) { - currentWrapper = currentWrapper!!.combine(wrapper) + open fun with(wrapper: ModeWrapper): Mode { + currentWrapper = if (currentWrapper != null) { + currentWrapper!!.combine(wrapper) } else { - currentWrapper = wrapper + wrapper } return this } - fun getWrapper(): ModeWrapper? { + open fun getWrapper(): ModeWrapper? { return currentWrapper } -} \ No newline at end of file + /** + * Clone this Mode so that changes made to the returned Mode are not reflected by this instance. + */ + open fun fork(): Mode { + return Mode(currentWrapper?.fork()) + } + + /** + * Wraps this Mode into a [ImmutableMode], prohibiting modifications to this instance by making calls to [Mode.with] + * fork the Mode by calling [Mode.fork] and operating on the cloned instance. Note that instances returned by + * [ImmutableMode.with] are not immutable. + */ + fun immutable(): ImmutableMode { + return ImmutableMode(this) + } + + class ImmutableMode(private val mode: Mode) : Mode() { + override fun with(wrapper: ModeWrapper): Mode { + return mode.fork().with(wrapper) + } + + override fun getWrapper(): ModeWrapper? { + return mode.getWrapper() + } + + override fun fork(): Mode { + return mode.fork() + } + } + +} diff --git a/src/main/kotlin/net/robinfriedli/exec/ModeWrapper.kt b/src/main/kotlin/net/robinfriedli/exec/ModeWrapper.kt index 52d4bbb..d1f94a3 100644 --- a/src/main/kotlin/net/robinfriedli/exec/ModeWrapper.kt +++ b/src/main/kotlin/net/robinfriedli/exec/ModeWrapper.kt @@ -36,6 +36,14 @@ interface ModeWrapper : Iterable { fun setOuter(mode: ModeWrapper?) + /** + * Clones this ModeWrapper for use in a new [Mode]. Changes to the returned ModeWrapper are not reflected by this instance. + * + * If the [ModeWrapper.combine] implementation does not modify this instance, meaning this implementation is practically + * immutable, this method does not have to do anything and can return the current instance. + */ + fun fork(): ModeWrapper + override fun iterator(): Iterator { return object : Iterator { @@ -59,4 +67,4 @@ interface ModeWrapper : Iterable { } } -} \ No newline at end of file +} diff --git a/src/test/kotlin/net/robinfriedli/exec/modes/ModesTest.kt b/src/test/kotlin/net/robinfriedli/exec/modes/ModesTest.kt index dddfa98..89e2a11 100644 --- a/src/test/kotlin/net/robinfriedli/exec/modes/ModesTest.kt +++ b/src/test/kotlin/net/robinfriedli/exec/modes/ModesTest.kt @@ -1,15 +1,52 @@ package net.robinfriedli.exec.modes +import net.robinfriedli.exec.AbstractNestedModeWrapper import net.robinfriedli.exec.Invoker import net.robinfriedli.exec.Mode import net.robinfriedli.exec.MutexSync import org.testng.Assert.* +import org.testng.annotations.DataProvider import org.testng.annotations.Test +import java.util.concurrent.Callable import java.util.concurrent.atomic.AtomicBoolean import java.util.concurrent.atomic.AtomicInteger class ModesTest { + @DataProvider + fun boolDataProvider(): Array { + return arrayOf(true, false) + } + + @Test(dataProvider = "boolDataProvider") + fun testForkMode(immutableMode: Boolean) { + val mode1 = Mode + .create() + .with(AdditionModeWrapper(2)) + .with(AdditionModeWrapper(7)) + val mode2 = if (immutableMode) { + mode1.immutable() + } else { + mode1.fork() + }.with(AdditionModeWrapper(4)) + + val invoker = Invoker.defaultInstance + val res1 = invoker.invoke(mode1) { 3 } + val res2 = invoker.invoke(mode2) { 3 } + + assertEquals(res1, 12) + assertEquals(res2, 16) + } + + class AdditionModeWrapper(private val summand: Int) : AbstractNestedModeWrapper() { + override fun wrap(callable: Callable): Callable { + return Callable { + (callable.call() as Int + summand) as T + } + } + + } + @Test fun testExceptionWrappingMode() { val invoker = Invoker.newInstance()