Repository: config4k/config4k (kotlin)

Issue:
Respect Mutable* (List / Map / Set)
Hi,

When loading a class containing a MutableSet AND the set contains only one element is loaded as SingletonSet (which is not mutable)

(Following is not compile checked)

```
data class Bla (val test: MutableSet<String>)
```

Pull request:
Fixes #108

Patch:
diff --git a/src/main/kotlin/io/github/config4k/readers/ListReader.kt b/src/main/kotlin/io/github/config4k/readers/ListReader.kt
index 05ef5c4..5efbe8c 100644
--- a/src/main/kotlin/io/github/config4k/readers/ListReader.kt
+++ b/src/main/kotlin/io/github/config4k/readers/ListReader.kt
@@ -2,10 +2,10 @@ package io.github.config4k.readers
 
 import io.github.config4k.ClassContainer
 
-internal class ListReader(clazz: ClassContainer) : Reader<List<*>>({ config, path ->
+internal class ListReader(clazz: ClassContainer, mutable: Boolean = false) : Reader<List<*>>({ config, path ->
     val reader = SelectReader.getReader(clazz)
     config.getList(path).map {
         val dummyName = "key"
         reader(it.atKey(dummyName), dummyName)
-    }
+    }.let { if (mutable) it.toMutableList() else it }
 })
diff --git a/src/main/kotlin/io/github/config4k/readers/MapReader.kt b/src/main/kotlin/io/github/config4k/readers/MapReader.kt
index 5875842..3d59f36 100644
--- a/src/main/kotlin/io/github/config4k/readers/MapReader.kt
+++ b/src/main/kotlin/io/github/config4k/readers/MapReader.kt
@@ -4,13 +4,11 @@ import com.typesafe.config.ConfigUtil
 import io.github.config4k.ClassContainer
 import io.github.config4k.MapEntry
 
-internal class MapReader(keyClass: ClassContainer, valueClass: ClassContainer) : Reader<Map<*, *>?>({ config, path ->
+internal class MapReader(keyClass: ClassContainer, valueClass: ClassContainer, mutable: Boolean = false) : Reader<Map<*, *>?>({ config, path ->
     when (keyClass.mapperClass) {
         String::class -> {
             val child = config.getConfig(path)
-            child.root().keys.associate { key ->
-                key to SelectReader.getReader(valueClass)(child, ConfigUtil.joinPath(key))
-            }
+            child.root().keys.associateWith { key -> SelectReader.getReader(valueClass)(child, ConfigUtil.joinPath(key)) }
         }
         else -> {
             val mapEntryClassContainer = ClassContainer(MapEntry::class, mapOf("K" to keyClass, "V" to valueClass))
@@ -19,5 +17,5 @@ internal class MapReader(keyClass: ClassContainer, valueClass: ClassContainer) :
                 mapEntry.key to mapEntry.value
             }
         }
-    }
+    }.let { if (mutable) it?.toMutableMap() else it }
 })
diff --git a/src/main/kotlin/io/github/config4k/readers/SelectReader.kt b/src/main/kotlin/io/github/config4k/readers/SelectReader.kt
index ebc5586..b25114c 100644
--- a/src/main/kotlin/io/github/config4k/readers/SelectReader.kt
+++ b/src/main/kotlin/io/github/config4k/readers/SelectReader.kt
@@ -46,8 +46,11 @@ public object SelectReader {
             ConfigMemorySize::class -> MemorySizeReader()
             Config::class -> ConfigReader()
             ConfigValue::class -> ConfigValueReader()
+            MutableList::class -> ListReader(clazz.typeArguments.getValue("E"), mutable = true)
             List::class -> ListReader(clazz.typeArguments.getValue("E"))
+            MutableSet::class -> SetReader(clazz.typeArguments.getValue("E"), mutable = true)
             Set::class -> SetReader(clazz.typeArguments.getValue("E"))
+            MutableMap::class -> MapReader(clazz.typeArguments.getValue("K"), clazz.typeArguments.getValue("V"), mutable = true)
             Map::class -> MapReader(clazz.typeArguments.getValue("K"), clazz.typeArguments.getValue("V"))
             File::class -> FileReader()
             Path::class -> PathReader()
diff --git a/src/main/kotlin/io/github/config4k/readers/SetReader.kt b/src/main/kotlin/io/github/config4k/readers/SetReader.kt
index c551353..542f1ad 100644
--- a/src/main/kotlin/io/github/config4k/readers/SetReader.kt
+++ b/src/main/kotlin/io/github/config4k/readers/SetReader.kt
@@ -2,6 +2,6 @@ package io.github.config4k.readers
 
 import io.github.config4k.ClassContainer
 
-internal class SetReader(clazz: ClassContainer) : Reader<Set<*>?>({ config, path ->
-    ListReader(clazz).getValue(config, path)?.toSet()
+internal class SetReader(clazz: ClassContainer, mutable: Boolean = false) : Reader<Set<*>?>({ config, path ->
+    ListReader(clazz).getValue(config, path)?.let { if (mutable) it.toMutableSet() else it.toSet() }
 })


Test patch:
diff --git a/src/test/kotlin/io/github/config4k/TestCollections.kt b/src/test/kotlin/io/github/config4k/TestCollections.kt
index 7007452..8115f31 100644
--- a/src/test/kotlin/io/github/config4k/TestCollections.kt
+++ b/src/test/kotlin/io/github/config4k/TestCollections.kt
@@ -5,7 +5,6 @@ import io.kotest.matchers.collections.haveSize
 import io.kotest.matchers.should
 import io.kotest.matchers.shouldBe
 import java.time.Duration
-import java.util.Arrays
 
 class TestCollections : WordSpec({
     val config = "key = [0m, 1m, 1m, 2m]".toConfig()
@@ -20,6 +19,24 @@ class TestCollections : WordSpec({
             )
         }
 
+        "return MutableList" {
+            val list = config.extract<MutableList<Duration>>("key")
+            list shouldBe listOf(
+                Duration.ofMinutes(0),
+                Duration.ofMinutes(1),
+                Duration.ofMinutes(1),
+                Duration.ofMinutes(2)
+            )
+            list.add(Duration.ofMinutes(10))
+            list shouldBe listOf(
+                Duration.ofMinutes(0),
+                Duration.ofMinutes(1),
+                Duration.ofMinutes(1),
+                Duration.ofMinutes(2),
+                Duration.ofMinutes(10)
+            )
+        }
+
         "return List<List<Int>>" {
             val intListConfig = "key = [[0, 0], [1, 1]]".toConfig()
             val list = intListConfig.extract<List<List<Int>>>("key")
@@ -32,25 +49,38 @@ class TestCollections : WordSpec({
             set shouldBe setOf(
                 Duration.ofMinutes(0),
                 Duration.ofMinutes(1),
-                Duration.ofMinutes(1),
                 Duration.ofMinutes(2)
             )
         }
 
+        "return MutableSet" {
+            val setConfig = "key = [1m]".toConfig()
+            val set = setConfig.extract<MutableSet<Duration>>("key")
+            set should haveSize(1)
+            set shouldBe setOf(Duration.ofMinutes(1))
+            set.add(Duration.ofMinutes(10))
+            set should haveSize(2)
+            set shouldBe setOf(
+                Duration.ofMinutes(1),
+                Duration.ofMinutes(10)
+            )
+        }
+
         "return Array<T>" {
-            Arrays.deepEquals(
-                """key = ["a", "b", "c", "d"]""".toConfig()
-                    .extract<Array<String>>("key"),
-                arrayOf("a", "b", "c", "d")
-            ) shouldBe true
-            Arrays.deepEquals(
-                """key = ["0m", "1m"]""".toConfig()
-                    .extract<Array<Duration>>("key"),
-                arrayOf(
-                    Duration.ofMinutes(0),
-                    Duration.ofMinutes(1)
-                )
-            ) shouldBe true
+            """key = ["a", "b", "c", "d"]""".toConfig()
+                .extract<Array<String>>("key")
+                .contentDeepEquals(
+                    arrayOf("a", "b", "c", "d")
+                ) shouldBe true
+
+            """key = ["0m", "1m"]""".toConfig()
+                .extract<Array<Duration>>("key")
+                .contentDeepEquals(
+                    arrayOf(
+                        Duration.ofMinutes(0),
+                        Duration.ofMinutes(1)
+                    )
+                ) shouldBe true
         }
     }
 })
diff --git a/src/test/kotlin/io/github/config4k/TestMap.kt b/src/test/kotlin/io/github/config4k/TestMap.kt
index 3bc4b62..b94dba1 100644
--- a/src/test/kotlin/io/github/config4k/TestMap.kt
+++ b/src/test/kotlin/io/github/config4k/TestMap.kt
@@ -73,4 +73,19 @@ class TestMap : WordSpec({
             list shouldBe mapOf("key1.with.dot" to 10, "key2.with.dot" to 20)
         }
     }
+
+    "Config.extract<MutableMap<String, T>>" should {
+        "return MutableMap<String, T>" {
+            val mapConfig =
+                """
+                nest = {
+                  key1 = 10
+                  key2 = 20
+                }""".toConfig()
+            val map = mapConfig.extract<MutableMap<String, Int>>("nest")
+            map shouldBe mapOf("key1" to 10, "key2" to 20)
+            map["key3"] = 30
+            map shouldBe mapOf("key1" to 10, "key2" to 20, "key3" to 30)
+        }
+    }
 })