Skip to content

Commit f12d68d

Browse files
committed
enable forking of Modes and add ImmutableMode
- make sure the default empty Mode is immutable
1 parent dfa7dcd commit f12d68d

File tree

6 files changed

+101
-17
lines changed

6 files changed

+101
-17
lines changed

README.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ repositories {
1313
}
1414
1515
dependencies {
16-
implementation "com.github.robinfriedli:exec:1.3"
16+
implementation "com.github.robinfriedli:exec:1.4"
1717
}
1818
```
1919

@@ -22,7 +22,7 @@ dependencies {
2222
<dependency>
2323
<groupId>com.github.robinfriedli</groupId>
2424
<artifactId>exec</artifactId>
25-
<version>1.3</version>
25+
<version>1.4</version>
2626
<type>pom</type>
2727
</dependency>
2828

build.gradle

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ plugins {
55
}
66

77
group = "net.robinfriedli"
8-
version = "1.3"
8+
version = "1.4"
99
sourceCompatibility = "8"
1010
targetCompatibility = "8"
1111

src/main/kotlin/net/robinfriedli/exec/AbstractNestedModeWrapper.kt

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,17 +9,22 @@ abstract class AbstractNestedModeWrapper : ModeWrapper {
99

1010
private var outer: ModeWrapper? = null
1111

12-
override fun combine(mode: ModeWrapper): ModeWrapper {
12+
final override fun combine(mode: ModeWrapper): ModeWrapper {
1313
mode.setOuter(this)
1414
return mode
1515
}
1616

17-
override fun getOuter(): ModeWrapper? {
17+
final override fun getOuter(): ModeWrapper? {
1818
return outer
1919
}
2020

21-
override fun setOuter(mode: ModeWrapper?) {
21+
final override fun setOuter(mode: ModeWrapper?) {
2222
this.outer = mode
2323
}
2424

25-
}
25+
override fun fork(): ModeWrapper {
26+
// since this combine implementation does not modify this ModeWrapper instance it is inherently immutable
27+
return this
28+
}
29+
30+
}

src/main/kotlin/net/robinfriedli/exec/Mode.kt

Lines changed: 43 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -3,13 +3,17 @@ package net.robinfriedli.exec
33
/**
44
* Helper class to build a mode with the given [ModeWrapper] applied
55
*/
6-
class Mode {
6+
open class Mode() {
7+
8+
private constructor(currentWrapper: ModeWrapper?) : this() {
9+
this.currentWrapper = currentWrapper
10+
}
711

812
private var currentWrapper: ModeWrapper? = null
913

1014
companion object {
1115
@JvmStatic
12-
val empty = create()
16+
val empty = create().immutable()
1317

1418
@JvmStatic
1519
fun create(): Mode {
@@ -23,19 +27,49 @@ class Mode {
2327
* inside the task of the current wrapper.
2428
*
2529
* @param wrapper the new wrapper to add, normally wrapping it inside the current wrapper
26-
* @return this mode with the new wrapper applied
30+
* @return this mode with the new wrapper applied, or a new Mode instance if this Mode was immutable
2731
*/
28-
fun with(wrapper: ModeWrapper): Mode {
29-
if (currentWrapper != null) {
30-
currentWrapper = currentWrapper!!.combine(wrapper)
32+
open fun with(wrapper: ModeWrapper): Mode {
33+
currentWrapper = if (currentWrapper != null) {
34+
currentWrapper!!.combine(wrapper)
3135
} else {
32-
currentWrapper = wrapper
36+
wrapper
3337
}
3438
return this
3539
}
3640

37-
fun getWrapper(): ModeWrapper? {
41+
open fun getWrapper(): ModeWrapper? {
3842
return currentWrapper
3943
}
4044

41-
}
45+
/**
46+
* Clone this Mode so that changes made to the returned Mode are not reflected by this instance.
47+
*/
48+
open fun fork(): Mode {
49+
return Mode(currentWrapper?.fork())
50+
}
51+
52+
/**
53+
* Wraps this Mode into a [ImmutableMode], prohibiting modifications to this instance by making calls to [Mode.with]
54+
* fork the Mode by calling [Mode.fork] and operating on the cloned instance. Note that instances returned by
55+
* [ImmutableMode.with] are not immutable.
56+
*/
57+
fun immutable(): ImmutableMode {
58+
return ImmutableMode(this)
59+
}
60+
61+
class ImmutableMode(private val mode: Mode) : Mode() {
62+
override fun with(wrapper: ModeWrapper): Mode {
63+
return mode.fork().with(wrapper)
64+
}
65+
66+
override fun getWrapper(): ModeWrapper? {
67+
return mode.getWrapper()
68+
}
69+
70+
override fun fork(): Mode {
71+
return mode.fork()
72+
}
73+
}
74+
75+
}

src/main/kotlin/net/robinfriedli/exec/ModeWrapper.kt

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,14 @@ interface ModeWrapper : Iterable<ModeWrapper> {
3636

3737
fun setOuter(mode: ModeWrapper?)
3838

39+
/**
40+
* Clones this ModeWrapper for use in a new [Mode]. Changes to the returned ModeWrapper are not reflected by this instance.
41+
*
42+
* If the [ModeWrapper.combine] implementation does not modify this instance, meaning this implementation is practically
43+
* immutable, this method does not have to do anything and can return the current instance.
44+
*/
45+
fun fork(): ModeWrapper
46+
3947
override fun iterator(): Iterator<ModeWrapper> {
4048
return object : Iterator<ModeWrapper> {
4149

@@ -59,4 +67,4 @@ interface ModeWrapper : Iterable<ModeWrapper> {
5967

6068
}
6169
}
62-
}
70+
}

src/test/kotlin/net/robinfriedli/exec/modes/ModesTest.kt

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,52 @@
11
package net.robinfriedli.exec.modes
22

3+
import net.robinfriedli.exec.AbstractNestedModeWrapper
34
import net.robinfriedli.exec.Invoker
45
import net.robinfriedli.exec.Mode
56
import net.robinfriedli.exec.MutexSync
67
import org.testng.Assert.*
8+
import org.testng.annotations.DataProvider
79
import org.testng.annotations.Test
10+
import java.util.concurrent.Callable
811
import java.util.concurrent.atomic.AtomicBoolean
912
import java.util.concurrent.atomic.AtomicInteger
1013

1114
class ModesTest {
1215

16+
@DataProvider
17+
fun boolDataProvider(): Array<Any> {
18+
return arrayOf(true, false)
19+
}
20+
21+
@Test(dataProvider = "boolDataProvider")
22+
fun testForkMode(immutableMode: Boolean) {
23+
val mode1 = Mode
24+
.create()
25+
.with(AdditionModeWrapper(2))
26+
.with(AdditionModeWrapper(7))
27+
val mode2 = if (immutableMode) {
28+
mode1.immutable()
29+
} else {
30+
mode1.fork()
31+
}.with(AdditionModeWrapper(4))
32+
33+
val invoker = Invoker.defaultInstance
34+
val res1 = invoker.invoke<Int>(mode1) { 3 }
35+
val res2 = invoker.invoke<Int>(mode2) { 3 }
36+
37+
assertEquals(res1, 12)
38+
assertEquals(res2, 16)
39+
}
40+
41+
class AdditionModeWrapper(private val summand: Int) : AbstractNestedModeWrapper() {
42+
override fun <T> wrap(callable: Callable<T>): Callable<T> {
43+
return Callable {
44+
(callable.call() as Int + summand) as T
45+
}
46+
}
47+
48+
}
49+
1350
@Test
1451
fun testExceptionWrappingMode() {
1552
val invoker = Invoker.newInstance()

0 commit comments

Comments
 (0)