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 > enumBridge(clazz: Class, 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 { - val values = HashMap() +private fun Any.toConfigMap(): Map { + val values = LinkedHashMap() 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 { - 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 = 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 {