From 76b56679e6137f5c9d99b7080d6f8d6ab887f4c5 Mon Sep 17 00:00:00 2001
From: Adel El-Beik <adel.el-beik@r3.com>
Date: Sun, 13 Oct 2024 19:26:01 +0100
Subject: [PATCH 1/5] ENT-11975: Updating config parsing with 4.12 version,
 which correctly parses the 4.12 rotated keys config item.

---
 .../internal/config/ConfigUtilities.kt        | 109 +++++-------------
 1 file changed, 28 insertions(+), 81 deletions(-)

diff --git a/node-api/src/main/kotlin/net/corda/nodeapi/internal/config/ConfigUtilities.kt b/node-api/src/main/kotlin/net/corda/nodeapi/internal/config/ConfigUtilities.kt
index 56f12e0dde..95d195bab8 100644
--- a/node-api/src/main/kotlin/net/corda/nodeapi/internal/config/ConfigUtilities.kt
+++ b/node-api/src/main/kotlin/net/corda/nodeapi/internal/config/ConfigUtilities.kt
@@ -3,7 +3,13 @@
 
 package net.corda.nodeapi.internal.config
 
-import com.typesafe.config.*
+import com.typesafe.config.Config
+import com.typesafe.config.ConfigException
+import com.typesafe.config.ConfigFactory
+import com.typesafe.config.ConfigUtil
+import com.typesafe.config.ConfigValue
+import com.typesafe.config.ConfigValueFactory
+import com.typesafe.config.ConfigValueType
 import net.corda.core.identity.CordaX500Name
 import net.corda.core.internal.isStatic
 import net.corda.core.internal.noneOrSingle
@@ -11,9 +17,7 @@ import net.corda.core.internal.uncheckedCast
 import net.corda.core.utilities.NetworkHostAndPort
 import org.slf4j.Logger
 import org.slf4j.LoggerFactory
-import java.lang.reflect.Field
 import java.lang.reflect.InvocationTargetException
-import java.lang.reflect.ParameterizedType
 import java.net.Proxy
 import java.net.URL
 import java.nio.file.Path
@@ -22,7 +26,9 @@ import java.time.Duration
 import java.time.Instant
 import java.time.LocalDate
 import java.time.temporal.Temporal
-import java.util.*
+import java.time.temporal.TemporalAmount
+import java.util.Properties
+import java.util.UUID
 import javax.security.auth.x500.X500Principal
 import kotlin.reflect.KClass
 import kotlin.reflect.KProperty
@@ -181,7 +187,7 @@ private fun Config.getSingleValue(
             X500Principal::class -> X500Principal(getString(path))
             CordaX500Name::class -> {
                 when (getValue(path).valueType()) {
-                    ConfigValueType.OBJECT -> getConfig(path).parseAs(onUnknownKeys)
+                    ConfigValueType.OBJECT -> getConfig(path).parseAs(onUnknownKeys) as CordaX500Name
                     else -> CordaX500Name.parse(getString(path))
                 }
             }
@@ -291,98 +297,39 @@ private fun <T : Enum<T>> enumBridge(clazz: Class<T>, name: String): T {
  */
 fun Any.toConfig(): Config = ConfigValueFactory.fromMap(toConfigMap()).toConfig()
 
-fun Any?.toConfigValue(): ConfigValue = if (this is ConfigValue) {
-    this
-} else if (this != null) {
-    ConfigValueFactory.fromAnyRef(convertValue(this))
-} else {
-    ConfigValueFactory.fromAnyRef(null)
-}
+fun Any?.toConfigValue(): ConfigValue = ConfigValueFactory.fromAnyRef(sanitiseForFromAnyRef(this))
 
-@Suppress("PLATFORM_CLASS_MAPPED_TO_KOTLIN")
 // Reflect over the fields of the receiver and generate a value Map that can use to create Config object.
-private fun Any.toConfigMap(): Map<String, Any> {
-    val values = HashMap<String, Any>()
+private fun Any.toConfigMap(): Map<String, Any?> {
+    val values = LinkedHashMap<String, Any?>()
     for (field in javaClass.declaredFields) {
         if (field.isStatic || field.isSynthetic) continue
         field.isAccessible = true
         val value = field.get(this) ?: continue
-        val configValue = if (value is String || value is Boolean || value is Number) {
-            // These types are supported by Config as use as is
-            value
-        } else if (value is Temporal || value is NetworkHostAndPort || value is CordaX500Name ||
-                value is Path || value is URL || value is UUID || value is X500Principal) {
-            // These types make sense to be represented as Strings and the exact inverse parsing function for use in parseAs
-            value.toString()
-        } else if (value is Enum<*>) {
-            // Expicitly use the Enum's name in case the toString is overridden, which would make parsing problematic.
-            value.name
-        } else if (value is Properties) {
-            // For Properties we treat keys with . as nested configs
-            ConfigFactory.parseMap(uncheckedCast(value)).root()
-        } else if (value is Iterable<*>) {
-            value.toConfigIterable(field)
-        } else {
-            // Else this is a custom object recursed over
-            value.toConfigMap()
-        }
-        values[field.name] = configValue
+        values[field.name] = sanitiseForFromAnyRef(value)
     }
     return values
 }
 
-private fun convertValue(value: Any): Any {
-
-    return if (value is String || value is Boolean || value is Number) {
+/**
+ * @see ConfigValueFactory.fromAnyRef
+ */
+private fun sanitiseForFromAnyRef(value: Any?): Any? {
+    return when (value) {
         // These types are supported by Config as use as is
-        value
-    } else if (value is Temporal || value is NetworkHostAndPort || value is CordaX500Name ||
-            value is Path || value is URL || value is UUID || value is X500Principal) {
-        // These types make sense to be represented as Strings and the exact inverse parsing function for use in parseAs
-        value.toString()
-    } else if (value is Enum<*>) {
-        // Expicitly use the Enum's name in case the toString is overridden, which would make parsing problematic.
-        value.name
-    } else if (value is Properties) {
+        is String, is Boolean, is Number, is ConfigValue, is Duration, null -> value
+        is Enum<*> -> value.name
+        // These types make sense to be represented as Strings
+        is Temporal, is TemporalAmount, is NetworkHostAndPort, is CordaX500Name, is Path, is URL, is UUID, is X500Principal -> value.toString()
         // For Properties we treat keys with . as nested configs
-        ConfigFactory.parseMap(uncheckedCast(value)).root()
-    } else if (value is Iterable<*>) {
-        value.toConfigIterable()
-    } else {
+        is Properties -> ConfigFactory.parseMap(uncheckedCast(value)).root()
+        is Map<*, *> -> ConfigFactory.parseMap(value.map { it.key.toString() to sanitiseForFromAnyRef(it.value) }.toMap()).root()
+        is Iterable<*> -> value.map(::sanitiseForFromAnyRef)
         // Else this is a custom object recursed over
-        value.toConfigMap()
+        else -> value.toConfigMap()
     }
 }
 
-// For Iterables figure out the type parameter and apply the same logic as above on the individual elements.
-private fun Iterable<*>.toConfigIterable(field: Field): Iterable<Any?> {
-    val elementType = (field.genericType as ParameterizedType).actualTypeArguments[0] as Class<*>
-    return when (elementType) {
-    // For the types already supported by Config we can use the Iterable as is
-        String::class.java -> this
-        Integer::class.java -> this
-        java.lang.Long::class.java -> this
-        java.lang.Double::class.java -> this
-        java.lang.Boolean::class.java -> this
-        LocalDate::class.java -> map(Any?::toString)
-        Instant::class.java -> map(Any?::toString)
-        NetworkHostAndPort::class.java -> map(Any?::toString)
-        Path::class.java -> map(Any?::toString)
-        URL::class.java -> map(Any?::toString)
-        X500Principal::class.java -> map(Any?::toString)
-        UUID::class.java -> map(Any?::toString)
-        CordaX500Name::class.java -> map(Any?::toString)
-        Properties::class.java -> map { ConfigFactory.parseMap(uncheckedCast(it)).root() }
-        else -> if (elementType.isEnum) {
-            map { (it as Enum<*>).name }
-        } else {
-            map { it?.toConfigMap() }
-        }
-    }
-}
-
-private fun Iterable<*>.toConfigIterable(): Iterable<Any?> = map { element -> element?.let(::convertValue) }
-
 // The typesafe .getBoolean function is case sensitive, this is a case insensitive version
 fun Config.getBooleanCaseInsensitive(path: String): Boolean {
     try {

From 94bd9dceac640461842ca4bb228e51f3cf61be60 Mon Sep 17 00:00:00 2001
From: chriscochrane <chris.cochrane@r3.com>
Date: Mon, 14 Oct 2024 13:34:49 +0100
Subject: [PATCH 2/5] Dependency update

---
 build.gradle | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/build.gradle b/build.gradle
index b7e96776d0..9ae8e9bdf9 100644
--- a/build.gradle
+++ b/build.gradle
@@ -126,7 +126,7 @@ buildscript {
     ext.jcabi_manifests_version = '1.1'
     ext.picocli_version = '3.9.6'
     ext.commons_lang_version = '3.9'
-    ext.commons_io_version = '2.6'
+    ext.commons_io_version = '2.17.0'
     ext.controlsfx_version = '8.40.15'
     ext.detekt_version = constants.getProperty('detektVersion')
     ext.docker_java_version = constants.getProperty("dockerJavaVersion")

From 31ad1d99c497e43420c22a2c41412cf84f4048d1 Mon Sep 17 00:00:00 2001
From: chriscochrane <chris.cochrane@r3.com>
Date: Mon, 14 Oct 2024 14:43:45 +0100
Subject: [PATCH 3/5] New WriterOutputStream API

---
 .../kotlin/net/corda/blobinspector/BlobInspectorTest.kt    | 7 ++++++-
 1 file changed, 6 insertions(+), 1 deletion(-)

diff --git a/tools/blobinspector/src/test/kotlin/net/corda/blobinspector/BlobInspectorTest.kt b/tools/blobinspector/src/test/kotlin/net/corda/blobinspector/BlobInspectorTest.kt
index 210a5e9e11..fd00341ea5 100644
--- a/tools/blobinspector/src/test/kotlin/net/corda/blobinspector/BlobInspectorTest.kt
+++ b/tools/blobinspector/src/test/kotlin/net/corda/blobinspector/BlobInspectorTest.kt
@@ -55,7 +55,12 @@ class BlobInspectorTest {
     private fun run(resourceName: String): String {
         blobInspector.source = javaClass.getResource(resourceName)
         val writer = StringWriter()
-        blobInspector.run(PrintStream(WriterOutputStream(writer, UTF_8)))
+        val oStream = WriterOutputStream.builder()
+                .setCharset(UTF_8)
+                .setWriter(writer)
+                .get()
+        blobInspector.run(PrintStream(oStream))
+
         val output = writer.toString()
         println(output)
         return output

From 78e7f62df3a104637f53b53cf2fb01db57868832 Mon Sep 17 00:00:00 2001
From: chriscochrane <chris.cochrane@r3.com>
Date: Tue, 15 Oct 2024 10:11:58 +0100
Subject: [PATCH 4/5] Updated commons-io version

---
 constants.properties | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/constants.properties b/constants.properties
index 2874bcc1df..d29b038341 100644
--- a/constants.properties
+++ b/constants.properties
@@ -99,7 +99,7 @@ snappyVersion=0.5
 jcabiManifestsVersion=1.1
 picocliVersion=3.9.6
 commonsLangVersion=3.9
-commonsIoVersion=2.6
+commonsIoVersion=2.17.0
 controlsfxVersion=8.40.15
 # FontAwesomeFX for Java8
 fontawesomefxCommonsJava8Version=8.15

From c59040fbc2a8fcee5619f7d4eb3e57ad11be49fa Mon Sep 17 00:00:00 2001
From: Adel El-Beik <adel.el-beik@r3.com>
Date: Mon, 21 Oct 2024 09:01:25 +0100
Subject: [PATCH 5/5] ENT-11975:Merge typo, removed commons_lang_version added.

---
 constants.properties | 1 -
 1 file changed, 1 deletion(-)

diff --git a/constants.properties b/constants.properties
index b99bff5255..b570aaec15 100644
--- a/constants.properties
+++ b/constants.properties
@@ -93,7 +93,6 @@ protonjVersion=0.33.0
 snappyVersion=0.5
 jcabiManifestsVersion=1.1
 picocliVersion=3.9.6
-commonsLangVersion=3.9
 commonsIoVersion=2.17.0
 controlsfxVersion=8.40.15
 fontawesomefxCommonsVersion=11.0