Fixed issues with array/list expansion for loading, cli/sys/env.
Requirement for cli/sys/env is that it MUST match casemaster
parent
c3b23990bd
commit
106ca040af
|
@ -28,14 +28,13 @@ import dorkbox.json.Json.Companion.isShort
|
|||
import dorkbox.json.Json.Companion.isString
|
||||
import dorkbox.json.OutputType
|
||||
import dorkbox.os.OS
|
||||
import org.slf4j.Logger
|
||||
import org.slf4j.LoggerFactory
|
||||
import java.io.File
|
||||
import java.lang.IllegalArgumentException
|
||||
import java.lang.reflect.Modifier
|
||||
import java.util.*
|
||||
import kotlin.collections.AbstractList
|
||||
import kotlin.collections.ArrayList
|
||||
import kotlin.math.max
|
||||
import kotlin.reflect.KClass
|
||||
import kotlin.reflect.KProperty
|
||||
import kotlin.reflect.KVisibility
|
||||
|
@ -131,12 +130,14 @@ class ConfigProcessor<T : Any>
|
|||
dorkbox.updates.Updates.add(ConfigProcessor::class.java, "23475d7cdfef4c1e9c38c310420086ca", version)
|
||||
}
|
||||
|
||||
private fun <T: Any> createConfigMap(config: T, objectType: KClass<T>): Map<String, ConfigProp> {
|
||||
private fun <T: Any> createConfigMap(config: T): MutableMap<String, ConfigProp> {
|
||||
val klass = config::class
|
||||
|
||||
// this creates an EASY-to-use map of all arguments we have
|
||||
val argumentMap = mutableMapOf<String, ConfigProp>()
|
||||
|
||||
// get all the members of this class.
|
||||
for (member in objectType.declaredMemberProperties) {
|
||||
for (member in klass.declaredMemberProperties) {
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
assignFieldsToMap(
|
||||
argMap = argumentMap,
|
||||
|
@ -184,7 +185,7 @@ class ConfigProcessor<T : Any>
|
|||
jsonName += "[$index]"
|
||||
}
|
||||
|
||||
val prop = ConfigProp(parentConf, parentObj, field, arrayName, index)
|
||||
val prop = ConfigProp(jsonName, parentConf, parentObj, field, arrayName, index, false)
|
||||
|
||||
val type = obj::class.java
|
||||
if (isString(type) ||
|
||||
|
@ -202,6 +203,9 @@ class ConfigProcessor<T : Any>
|
|||
|
||||
argMap[jsonName] = prop
|
||||
} else if (type.isArray) {
|
||||
// assign the actual array object. this makes access MUCH easier later on!
|
||||
argMap[jsonName] = ConfigProp(jsonName, parentConf, parentObj, field, jsonName, index, true)
|
||||
|
||||
// iterate over the array, but assign the index with the name.
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
val collection = obj as Array<Any>
|
||||
|
@ -217,6 +221,9 @@ class ConfigProcessor<T : Any>
|
|||
index = i)
|
||||
}
|
||||
} else if (Collection::class.java.isAssignableFrom(type)) {
|
||||
// assign the actual array object. this makes access MUCH easier later on!
|
||||
argMap[jsonName] = ConfigProp(jsonName, parentConf, parentObj, field, jsonName, index, true)
|
||||
|
||||
// iterate over the collection, but assign the index with the name.
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
val collection = obj as Collection<Any>
|
||||
|
@ -277,6 +284,16 @@ class ConfigProcessor<T : Any>
|
|||
*/
|
||||
val json = Json()
|
||||
|
||||
/**
|
||||
* Specify a logger to output errors, if any (instead of silently ignoring them)
|
||||
*/
|
||||
var logger: Logger? = null
|
||||
set(value) {
|
||||
field = value
|
||||
json.logger = LoggerFactory.getLogger("test")
|
||||
}
|
||||
|
||||
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
private val objectType: KClass<T> = configObject::class as KClass<T>
|
||||
|
||||
|
@ -294,16 +311,14 @@ class ConfigProcessor<T : Any>
|
|||
*/
|
||||
var arguments: List<String> = mutableListOf()
|
||||
|
||||
private val config: T = configObject
|
||||
|
||||
private var processed = false
|
||||
|
||||
/**
|
||||
* this creates an EASY-to-use map of all arguments we have
|
||||
*
|
||||
* our configMap will ALWAYS modify the default (or rather, incoming object)!!
|
||||
*/
|
||||
private val configMap: Map<String, ConfigProp> = createConfigMap(configObject, objectType)
|
||||
private val configMap = createConfigMap(configObject)
|
||||
|
||||
// DEEP COPY of the original values from the file, so when saving, we know what the
|
||||
// overridden values are and can skip saving them
|
||||
|
@ -391,8 +406,9 @@ class ConfigProcessor<T : Any>
|
|||
} else {
|
||||
null
|
||||
}
|
||||
} catch (ignored: Exception) {
|
||||
} catch (exception: Exception) {
|
||||
// there was a problem parsing the config
|
||||
logger?.error("Error loading JSON data", exception)
|
||||
null
|
||||
}
|
||||
|
||||
|
@ -403,7 +419,7 @@ class ConfigProcessor<T : Any>
|
|||
this.configString = configString
|
||||
load(configObject)
|
||||
|
||||
return process(configObject)
|
||||
return postProcess()
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -421,8 +437,9 @@ class ConfigProcessor<T : Any>
|
|||
try {
|
||||
json.fromJson(objectType.java, fileContents)
|
||||
}
|
||||
catch (ignored: Exception) {
|
||||
catch (exception: Exception) {
|
||||
// there was a problem parsing the config
|
||||
logger?.error("Error loading JSON data", exception)
|
||||
null
|
||||
}
|
||||
}
|
||||
|
@ -441,7 +458,7 @@ class ConfigProcessor<T : Any>
|
|||
this.configFile = configFile
|
||||
load(configObject)
|
||||
|
||||
return process(configObject)
|
||||
return postProcess()
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -458,8 +475,9 @@ class ConfigProcessor<T : Any>
|
|||
} else {
|
||||
null
|
||||
}
|
||||
} catch (ignored: Exception) {
|
||||
} catch (exception: Exception) {
|
||||
// there was a problem parsing the config
|
||||
logger?.error("Error loading JSON data", exception)
|
||||
null
|
||||
}
|
||||
|
||||
|
@ -488,8 +506,9 @@ class ConfigProcessor<T : Any>
|
|||
try {
|
||||
json.fromJson(objectType.java, fileContents)
|
||||
}
|
||||
catch (ignored: Exception) {
|
||||
catch (exception: Exception) {
|
||||
// there was a problem parsing the config
|
||||
logger?.error("Error loading JSON data", exception)
|
||||
null
|
||||
}
|
||||
}
|
||||
|
@ -512,62 +531,148 @@ class ConfigProcessor<T : Any>
|
|||
}
|
||||
|
||||
|
||||
// support
|
||||
// thing[0].flag = true (array + list)
|
||||
// thing[0].bah.flag = true (array + list)
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
@Synchronized
|
||||
private fun load(configObject: T) {
|
||||
// we must make sure to modify the ORIGINAL object (which has already been set)
|
||||
// if we are invoked multiple times, then we might "undo" the overloaded value, but then "redo" it later.
|
||||
val incomingDataConfigMap = createConfigMap(configObject, objectType)
|
||||
incomingDataConfigMap.forEach { (k,v) ->
|
||||
// when setting a district array of length > 1, this is causing errors (since the "default" has no length, and we do.
|
||||
val incomingDataConfigMap = createConfigMap(configObject)
|
||||
|
||||
// if we are an array/list/object -- then we REPLACE it (not set an element of it at a specific spot)
|
||||
val current = configMap[k]
|
||||
if (current != null) {
|
||||
current.set(v.get())
|
||||
} else {
|
||||
configMap as MutableMap
|
||||
configMap.put(k, v)
|
||||
// step 1 is to expand arrays...
|
||||
// step 2 is to assign values (that are not an array themself)
|
||||
val configuredArrays = mutableSetOf("")
|
||||
|
||||
incomingDataConfigMap.values.forEach { prop ->
|
||||
// have to expand arrays here!
|
||||
|
||||
var p: ConfigProp? = prop
|
||||
while (p != null && p.isSupported()) {
|
||||
val parent = p.parentObj
|
||||
|
||||
if (parent is Array<*>) {
|
||||
// do we need to GROW the array? (we never shrink, we only grow)
|
||||
val arrayName = p.collectionName
|
||||
|
||||
if (!configuredArrays.contains(arrayName)) {
|
||||
configuredArrays.add(arrayName)
|
||||
|
||||
// expand, and get the new array (or original one)
|
||||
val newArray = expandForProp(p, configMap)
|
||||
|
||||
val affectedProps = incomingDataConfigMap.filterKeys { it.startsWith("$arrayName[") }
|
||||
|
||||
affectedProps.forEach { (k, v) ->
|
||||
val newProp = ConfigProp(k, v.parentConf, newArray, v.member, arrayName, v.index, v.ignore)
|
||||
newProp.set(v.get())
|
||||
configMap[k] = newProp
|
||||
}
|
||||
}
|
||||
} else if (Collection::class.java.isAssignableFrom(parent::class.java)) {
|
||||
// do we add items to the original collection (we never shrink, we only grow)
|
||||
val arrayName = p.collectionName
|
||||
|
||||
if (!configuredArrays.contains(arrayName)) {
|
||||
configuredArrays.add(arrayName)
|
||||
|
||||
// get the original list (because we have to ADD to it!)
|
||||
val origList = configMap[arrayName]!!.get()!!
|
||||
|
||||
val affectedProps = incomingDataConfigMap.filterKeys { it.startsWith("$arrayName[") }
|
||||
|
||||
affectedProps.forEach { (k, v) ->
|
||||
// do we have an existing value for the affected property?
|
||||
val existing = configMap[k]
|
||||
if (existing == null) {
|
||||
// we don't exist at all, so we have to add it.
|
||||
// when we add it, we have to add ALL properties of the new object, so that all references are to the same base object
|
||||
var affectedProp: ConfigProp? = v
|
||||
while (affectedProp != null && affectedProp.parentObj != parent) {
|
||||
affectedProp = affectedProp.parentConf
|
||||
}
|
||||
affectedProp!!
|
||||
|
||||
val affectedObj = affectedProp.get()
|
||||
if (origList is ArrayList<*>) {
|
||||
(origList as ArrayList<Any?>).add(affectedObj)
|
||||
} else if (origList is MutableList<*>) {
|
||||
(origList as MutableList<Any?>).add(v)
|
||||
}
|
||||
|
||||
if (affectedObj != null) {
|
||||
val affectedObjConfigMap = createConfigMap(affectedObj)
|
||||
affectedObjConfigMap.forEach { (aK,aV) ->
|
||||
// place all the new object property objects into the new ap (they are assign from the old map -- and so they reference the correct objects
|
||||
|
||||
val newK = affectedProp.key + "." + aV.key
|
||||
val newProp = ConfigProp(newK, affectedProp, aV.parentObj, aV.member, affectedProp.collectionName, aV.index, aV.ignore)
|
||||
|
||||
configMap[newK] = newProp
|
||||
}
|
||||
}
|
||||
} else {
|
||||
existing.set(v.get())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
p = p.parentConf
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Processes and populates the values of the config object, if any
|
||||
*/
|
||||
@Synchronized
|
||||
private fun process(configObject: T): ConfigProcessor<T> {
|
||||
if (!processed) {
|
||||
processed = true
|
||||
// only do this once
|
||||
origCopyConfig = json.fromJson(objectType.java, json.toJson(configObject))!!
|
||||
origCopyConfigMap = createConfigMap(origCopyConfig, objectType)
|
||||
incomingDataConfigMap.values.forEach { prop ->
|
||||
val parent = prop.parentObj
|
||||
if (prop.isSupported() &&
|
||||
parent !is Array<*> &&
|
||||
!Collection::class.java.isAssignableFrom(parent::class.java)) {
|
||||
|
||||
// it's not an array or collection, so we just assign the value
|
||||
|
||||
// we have to just assign the value to our "original" value
|
||||
val original = configMap[prop.key]
|
||||
|
||||
if (original != null) {
|
||||
original.set(prop.get())
|
||||
} else {
|
||||
throw Exception("Unable to assign an incoming property to the original configuration when it doesn't exist.")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return postProcess()
|
||||
|
||||
// now setup the "original" objects. ORIGINAL means...
|
||||
// 1) the original, passed in config object IFF no other config data was loaded
|
||||
// 2) the file (as an object)
|
||||
// 3) the text (as an object)
|
||||
origCopyConfig = json.fromJson(objectType.java, json.toJson(configObject))!!
|
||||
origCopyConfigMap = createConfigMap(origCopyConfig)
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Processes and populates the values of the config object, if any
|
||||
*/
|
||||
@Synchronized
|
||||
fun process(): ConfigProcessor<T> {
|
||||
if (!processed) {
|
||||
processed = true
|
||||
// only do this once
|
||||
origCopyConfig = json.fromJson(objectType.java, json.toJson(configObject))!!
|
||||
origCopyConfigMap = createConfigMap(origCopyConfig, objectType)
|
||||
}
|
||||
|
||||
// when processing data, the FILE, if present, is always the FIRST to load.
|
||||
configFile?.also { load(it) }
|
||||
|
||||
// the string will be used to MODIFY what was set by the FILE (if present),
|
||||
configString?.also { load(it) }
|
||||
|
||||
if (configFile == null && configString == null) {
|
||||
// now setup the "original" objects. ORIGINAL means...
|
||||
// 1) the original, passed in config object IFF no other config data was loaded
|
||||
// 2) the file (as an object)
|
||||
// 3) the text (as an object)
|
||||
origCopyConfig = json.fromJson(objectType.java, json.toJson(configObject))!!
|
||||
origCopyConfigMap = createConfigMap(origCopyConfig)
|
||||
}
|
||||
|
||||
return postProcess()
|
||||
}
|
||||
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
private fun postProcess() : ConfigProcessor<T> {
|
||||
// this permits a bash/batch/CLI invocation of "get xyz" or "set xyz" so we can be interactive with the CLI
|
||||
// if there is no get/set CLI argument, then this does nothing.
|
||||
|
@ -588,106 +693,38 @@ class ConfigProcessor<T : Any>
|
|||
|
||||
|
||||
/**
|
||||
* we have to check to see if we must grow the arrays, because if we ADD an element to an array based on the DEFAULT, meaning
|
||||
* FOR CLI/SYS/ENV... we have to check to see if we must grow the arrays, because if we ADD an element to an array based on the DEFAULT, meaning
|
||||
* that the default array is size 3, and we add an element at position 4 -- we want to make sure that saving/printing, etc all work properly.
|
||||
*/
|
||||
run {
|
||||
val configuredArrays = mutableSetOf("")
|
||||
|
||||
val newProps = mutableMapOf<String, ConfigProp>()
|
||||
configMap.forEach { (_, prop) ->
|
||||
val returnType = prop.returnType
|
||||
configMap.forEach { (arg, prop) ->
|
||||
if (!prop.isSupported()) {
|
||||
return@forEach
|
||||
}
|
||||
|
||||
if (prop.isSupported()) {
|
||||
val parent = prop.parent
|
||||
val parent = prop.parentObj
|
||||
|
||||
if (parent is Array<*> ||
|
||||
parent is ArrayList<*> ||
|
||||
parent is AbstractList<*>
|
||||
) {
|
||||
if (parent is Array<*> ||
|
||||
parent is ArrayList<*> ||
|
||||
parent is MutableList<*>
|
||||
) {
|
||||
|
||||
// do we need to GROW the array? (we never shrink, we only grow)
|
||||
val arrayName = prop.collectionName
|
||||
// do we need to GROW the array? (we never shrink, we only grow)
|
||||
val arrayName = prop.collectionName
|
||||
|
||||
if (configuredArrays.contains(arrayName)) {
|
||||
return@forEach
|
||||
}
|
||||
if (!configuredArrays.contains(arrayName)) {
|
||||
configuredArrays.add(arrayName)
|
||||
|
||||
val foundArgs = commandLineArguments.filter { it.startsWith("$arrayName[") }
|
||||
|
||||
// if any of the foundArgs INDEX is larger than our array, we have to grow to that size.
|
||||
val largestIndex: Int
|
||||
|
||||
val array =
|
||||
if (parent is Array<*>) {
|
||||
largestIndex = parent.size - 1
|
||||
parent
|
||||
} else if (parent is ArrayList<*>) {
|
||||
largestIndex = parent.size - 1
|
||||
parent as ArrayList<Any?>
|
||||
} else if (parent is MutableList<*>) {
|
||||
largestIndex = parent.size - 1
|
||||
parent as MutableList<Any?>
|
||||
}
|
||||
else {
|
||||
throw IllegalArgumentException("Unknown array type: ${parent.javaClass}")
|
||||
}
|
||||
|
||||
|
||||
val len = "$arrayName[".length
|
||||
val cleaned = foundArgs.map { arg ->
|
||||
val last = arg.indexOfFirst { it == ']' }
|
||||
arg.substring(len, last).toInt()
|
||||
}.maxOf { it }
|
||||
val largest = max(largestIndex, cleaned)
|
||||
|
||||
if (largest > largestIndex) {
|
||||
val newObj = if (array is Array<*>) {
|
||||
// we have to grow (offset by 1 again because of size vs index difference
|
||||
val newArray = array.copyOf(largest + 1)
|
||||
|
||||
// now we have to set the index so we can set it later
|
||||
prop.parentConf!!.set(newArray)
|
||||
newArray
|
||||
} else {
|
||||
array
|
||||
}
|
||||
|
||||
|
||||
// we must
|
||||
// 1) assign what the parent obj is for the array members
|
||||
// 2) create the new ones
|
||||
for (index in 0 .. largest) {
|
||||
val keyName = "$arrayName[$index]"
|
||||
// prop.member DOES NOT MATTER for arrays BECAUSE we ignore it when get/set!
|
||||
newProps[keyName] = ConfigProp(prop.parentConf, newObj, prop.member, arrayName, index)
|
||||
}
|
||||
|
||||
for (index in largestIndex + 1 .. largest) {
|
||||
val keyName = "$arrayName[$index]"
|
||||
// prop.member DOES NOT MATTER for arrays BECAUSE we ignore it when get/set!
|
||||
val newProp = newProps[keyName]!!
|
||||
|
||||
|
||||
if (parent is ArrayList<*>) {
|
||||
(parent as ArrayList<Any>).add(defaultType(returnType))
|
||||
}
|
||||
else if (parent is MutableList<*>) {
|
||||
(parent as MutableList<Any>).add(defaultType(returnType))
|
||||
}
|
||||
|
||||
// we have to set a default value!!!
|
||||
newProp.set(defaultType(returnType))
|
||||
newProp.override = true // cannot use the method because we have to forceSet it!
|
||||
}
|
||||
}
|
||||
// this checks CLI, SYS, ENV for array expansion
|
||||
expandArrays(prop, newProps)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
configMap as MutableMap
|
||||
|
||||
newProps.forEach { (k, v) ->
|
||||
newProps.forEach { (k,v) ->
|
||||
configMap[k] = v
|
||||
}
|
||||
}
|
||||
|
@ -704,91 +741,73 @@ class ConfigProcessor<T : Any>
|
|||
configMap.forEach { (arg, prop) ->
|
||||
val returnType = prop.returnType
|
||||
|
||||
if (prop.isSupported()) {
|
||||
////
|
||||
// CLI CHECK IF PROPERTY EXIST (explicit check for arg=value)
|
||||
// if arg is found, no more processing happens
|
||||
////
|
||||
var foundArg = commandLineArguments.firstOrNull { it.startsWith("$arg=") }
|
||||
if (!prop.isSupported()) {
|
||||
LoggerFactory.getLogger(ConfigProcessor::class.java).error("${prop.member.name} (${returnType.javaObjectType.simpleName}) overloading is not supported. Ignoring")
|
||||
return@forEach
|
||||
}
|
||||
|
||||
|
||||
////
|
||||
// CLI CHECK IF PROPERTY EXIST (explicit check for arg=value)
|
||||
// if arg is found, no more processing happens
|
||||
////
|
||||
var foundArg = commandLineArguments.firstOrNull { it.startsWith("$arg=") }
|
||||
if (foundArg != null) {
|
||||
// we know that split[0] == 'arg=' because we already checked for that
|
||||
val overriddenValue = foundArg.split(regexEquals)[1].trim().getType(returnType)
|
||||
|
||||
// This will only be saved if different, so we can figure out what the values are on save (and not save
|
||||
// overridden properties, that are unchanged)
|
||||
prop.override(overriddenValue)
|
||||
|
||||
arguments.remove(foundArg)
|
||||
return@forEach
|
||||
}
|
||||
|
||||
////
|
||||
// CLI CHECK IF PROPERTY EXIST (check if 'arg' exists, and is a boolean)
|
||||
// if arg is found, no more processing happens
|
||||
////
|
||||
if (returnType.isSubclassOf(Boolean::class)) {
|
||||
// this is a boolean type? if present then we make it TRUE
|
||||
foundArg = commandLineArguments.firstOrNull { it.startsWith(arg) }
|
||||
if (foundArg != null) {
|
||||
// we know that split[0] == 'arg=' because we already checked for that
|
||||
val overriddenValue = foundArg.split(regexEquals)[1].trim().getType(returnType)
|
||||
|
||||
// This will only be saved if different, so we can figure out what the values are on save (and not save
|
||||
// overridden properties, that are unchanged)
|
||||
prop.override(overriddenValue)
|
||||
prop.override(true)
|
||||
|
||||
arguments.remove(foundArg)
|
||||
return@forEach
|
||||
}
|
||||
|
||||
////
|
||||
// CLI CHECK IF PROPERTY EXIST (check if 'arg' exists, and is a boolean)
|
||||
// if arg is found, no more processing happens
|
||||
////
|
||||
if (returnType.isSubclassOf(Boolean::class)) {
|
||||
// this is a boolean type? if present then we make it TRUE
|
||||
foundArg = commandLineArguments.firstOrNull { it.startsWith(arg) }
|
||||
if (foundArg != null) {
|
||||
|
||||
// This will only be saved if different, so we can figure out what the values are on save (and not save
|
||||
// overridden properties, that are unchanged)
|
||||
prop.override(true)
|
||||
|
||||
arguments.remove(foundArg)
|
||||
return@forEach
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
////
|
||||
// SYSTEM PROPERTY CHECK
|
||||
////
|
||||
var sysProperty: String? = OS.getProperty(arg)?.trim()
|
||||
////
|
||||
// SYSTEM PROPERTY CHECK
|
||||
////
|
||||
val sysProperty: String? = OS.getProperty(arg)?.trim()
|
||||
if (!sysProperty.isNullOrEmpty()) {
|
||||
val overriddenValue = sysProperty.getType(returnType)
|
||||
|
||||
// try lowercase
|
||||
if (sysProperty.isNullOrEmpty()) {
|
||||
sysProperty = OS.getProperty(arg.lowercase(Locale.getDefault()))?.trim()
|
||||
}
|
||||
// try uppercase
|
||||
if (sysProperty.isNullOrEmpty()) {
|
||||
sysProperty = OS.getProperty(arg.uppercase(Locale.getDefault()))?.trim()
|
||||
}
|
||||
// This will only be saved if different, so we can figure out what the values are on save (and not save
|
||||
// overridden properties, that are unchanged)
|
||||
prop.override(overriddenValue)
|
||||
|
||||
if (!sysProperty.isNullOrEmpty()) {
|
||||
val overriddenValue = sysProperty.getType(returnType)
|
||||
|
||||
// This will only be saved if different, so we can figure out what the values are on save (and not save
|
||||
// overridden properties, that are unchanged)
|
||||
prop.override(overriddenValue)
|
||||
|
||||
return@forEach
|
||||
}
|
||||
return@forEach
|
||||
}
|
||||
|
||||
|
||||
////
|
||||
// ENVIRONMENT VARIABLE CHECK
|
||||
////
|
||||
var envProperty = OS.getEnv(environmentVarPrefix + arg)?.trim()
|
||||
////
|
||||
// ENVIRONMENT VARIABLE CHECK
|
||||
////
|
||||
val envProperty = OS.getEnv(environmentVarPrefix + arg)?.trim()
|
||||
if (!envProperty.isNullOrEmpty()) {
|
||||
// This will only be saved if different, so we can figure out what the values are on save (and not save
|
||||
// overridden properties, that are unchanged)
|
||||
prop.override(envProperty)
|
||||
|
||||
// try lowercase
|
||||
if (envProperty.isNullOrEmpty()) {
|
||||
envProperty = OS.getEnv(environmentVarPrefix + arg.lowercase(Locale.getDefault()))?.trim()
|
||||
}
|
||||
// try uppercase
|
||||
if (envProperty.isNullOrEmpty()) {
|
||||
envProperty = OS.getEnv(environmentVarPrefix + arg.uppercase(Locale.getDefault()))?.trim()
|
||||
}
|
||||
|
||||
if (!envProperty.isNullOrEmpty()) {
|
||||
// This will only be saved if different, so we can figure out what the values are on save (and not save
|
||||
// overridden properties, that are unchanged)
|
||||
prop.override(envProperty)
|
||||
|
||||
return@forEach
|
||||
}
|
||||
} else {
|
||||
LoggerFactory.getLogger(ConfigProcessor::class.java).error("${prop.member.name} (${returnType.javaObjectType.simpleName}) overloading is not supported. Ignoring")
|
||||
return@forEach
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -798,6 +817,155 @@ class ConfigProcessor<T : Any>
|
|||
return this
|
||||
}
|
||||
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
private fun expandForProp(prop: ConfigProp, configMap: MutableMap<String, ConfigProp>): Array<Any?> {
|
||||
|
||||
// do we need to GROW the array? (we never shrink, we only grow)
|
||||
val arrayName = prop.collectionName
|
||||
|
||||
// if we have more args found that we have, then we have to grow the array
|
||||
|
||||
val len = "$arrayName[".length
|
||||
val affectedProps = configMap.filterKeys { it.startsWith("$arrayName[") }
|
||||
val maxFound = affectedProps.map { arg ->
|
||||
val last = arg.key.indexOfFirst { it == ']' }
|
||||
arg.key.substring(len, last).toInt()
|
||||
}
|
||||
|
||||
val max = if (maxFound.isEmpty()) {
|
||||
0
|
||||
} else {
|
||||
// +1 because we go from index -> size
|
||||
maxFound.maxOf { it } + 1
|
||||
}
|
||||
|
||||
val array = prop.parentObj as Array<Any?>
|
||||
val largestIndex = array.size
|
||||
|
||||
val originalProp = configMap[arrayName] as ConfigProp
|
||||
val originalArray = originalProp.get() as Array<Any?>
|
||||
|
||||
if (largestIndex > max) {
|
||||
val newArray = originalArray.copyOf(largestIndex)
|
||||
|
||||
// replace the array with the copy
|
||||
originalProp.set(newArray)
|
||||
|
||||
// reset all the original "parent objects" to be the new one
|
||||
affectedProps.forEach { (k, v) ->
|
||||
configMap[k] = ConfigProp(k, v.parentConf, newArray, v.member, arrayName, v.index, v.ignore)
|
||||
}
|
||||
|
||||
return newArray
|
||||
}
|
||||
|
||||
return originalArray
|
||||
}
|
||||
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
private fun expandArrays(prop: ConfigProp, newProps: MutableMap<String, ConfigProp>) {
|
||||
|
||||
val returnType = prop.returnType
|
||||
val parent = prop.parentObj
|
||||
|
||||
// do we need to GROW the array? (we never shrink, we only grow)
|
||||
val arrayName = prop.collectionName
|
||||
val maxFound = mutableListOf<Int>()
|
||||
|
||||
run {
|
||||
// CLI
|
||||
var foundArgs: Collection<String> = commandLineArguments.filter { it.startsWith("$arrayName[") }
|
||||
var len = "$arrayName[".length
|
||||
foundArgs.map { arg ->
|
||||
val last = arg.indexOfFirst { it == ']' }
|
||||
arg.substring(len, last).toInt()
|
||||
}.also { maxFound.addAll(it) }
|
||||
|
||||
|
||||
// SYS
|
||||
foundArgs = OS.getProperties().map { it.key }.filter { it.startsWith("$environmentVarPrefix$arrayName[") }
|
||||
len = "$environmentVarPrefix$arrayName[".length
|
||||
foundArgs.map { arg ->
|
||||
val last = arg.indexOfFirst { it == ']' }
|
||||
arg.substring(len, last).toInt()
|
||||
}.also { maxFound.addAll(it) }
|
||||
|
||||
|
||||
// ENV
|
||||
foundArgs = OS.getEnv().map { it.key }.filter { it.startsWith("$environmentVarPrefix$arrayName[") }
|
||||
foundArgs.map { arg ->
|
||||
val last = arg.indexOfFirst { it == ']' }
|
||||
arg.substring(len, last).toInt()
|
||||
}.also { maxFound.addAll(it) }
|
||||
}
|
||||
|
||||
|
||||
|
||||
val max = if (maxFound.isEmpty()) {
|
||||
0
|
||||
} else {
|
||||
// +1 because we go from index -> size
|
||||
maxFound.maxOf { it } + 1
|
||||
}
|
||||
|
||||
// if any of the foundArgs INDEX is larger than our array, we have to grow to that size.
|
||||
val largestIndex: Int =
|
||||
if (parent is Array<*>) {
|
||||
parent.size
|
||||
} else if (parent is ArrayList<*>) {
|
||||
parent.size
|
||||
} else if (parent is MutableList<*>) {
|
||||
parent.size
|
||||
}
|
||||
else {
|
||||
throw IllegalArgumentException("Unknown array type: ${parent.javaClass}")
|
||||
}
|
||||
|
||||
|
||||
// slightly different than expandForProp() because we are not expanding for found props, but for
|
||||
// CLI/SYS/ENV
|
||||
if (max > largestIndex) {
|
||||
val newObj = if (parent is Array<*>) {
|
||||
val newArray = parent.copyOf(max)
|
||||
|
||||
// now we have to set the index so we can set it later
|
||||
prop.parentConf!!.set(newArray)
|
||||
newArray
|
||||
} else {
|
||||
parent
|
||||
}
|
||||
|
||||
// we must
|
||||
// 1) assign what the parent obj is for the array members
|
||||
// 2) create the new ones
|
||||
for (index in 0 until max) {
|
||||
val keyName = "$arrayName[$index]"
|
||||
// prop.member DOES NOT MATTER for arrays BECAUSE we ignore it when get/set!
|
||||
newProps[keyName] = ConfigProp(keyName, prop.parentConf, newObj, prop.member, arrayName, index, prop.ignore)
|
||||
}
|
||||
|
||||
for (index in largestIndex until max) {
|
||||
val keyName = "$arrayName[$index]"
|
||||
// prop.member DOES NOT MATTER for arrays BECAUSE we ignore it when get/set!
|
||||
val newProp = newProps[keyName]!!
|
||||
|
||||
if (parent is ArrayList<*>) {
|
||||
(parent as ArrayList<Any>).add(defaultType(returnType))
|
||||
}
|
||||
else if (parent is MutableList<*>) {
|
||||
(parent as MutableList<Any>).add(defaultType(returnType))
|
||||
} else {
|
||||
// array
|
||||
// we have to set a default value!!!
|
||||
newProp.set(defaultType(returnType))
|
||||
}
|
||||
|
||||
newProp.override = true // cannot use the method because we have to forceSet it!
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @return the JSON string representing the ORIGINAL configuration (does not include overridden properties)
|
||||
*/
|
||||
|
@ -806,7 +974,7 @@ class ConfigProcessor<T : Any>
|
|||
// we use configCopy to save the state of everything as a snapshot (and then we serialize it)
|
||||
origCopyConfigMap.forEach { (k,v) ->
|
||||
val configured = configMap[k]
|
||||
if (configured?.override == false) {
|
||||
if (configured != null && !configured.ignore && !configured.override) {
|
||||
// this will change what the "original copy" is recorded as having.
|
||||
v.set(configured.get())
|
||||
}
|
||||
|
@ -820,7 +988,7 @@ class ConfigProcessor<T : Any>
|
|||
*/
|
||||
@Synchronized
|
||||
fun json(): String {
|
||||
return json.toJson(config)
|
||||
return json.toJson(configObject)
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -20,16 +20,13 @@ import java.lang.Exception
|
|||
import kotlin.reflect.KClass
|
||||
import kotlin.reflect.KMutableProperty
|
||||
import kotlin.reflect.KProperty
|
||||
import kotlin.reflect.jvm.javaType
|
||||
import kotlin.reflect.jvm.jvmErasure
|
||||
|
||||
data class ConfigProp(val parentConf: ConfigProp?, val parent: Any, val member: KProperty<Any>, val collectionName: String, val index: Int) {
|
||||
|
||||
val isCollection: Boolean = parent is Collection<*>
|
||||
internal data class ConfigProp(val key: String, val parentConf: ConfigProp?, val parentObj: Any, val member: KProperty<Any>, val collectionName: String, val index: Int, val ignore: Boolean) {
|
||||
|
||||
val returnType: KClass<*>
|
||||
get() {
|
||||
return when (parent) {
|
||||
return when (parentObj) {
|
||||
is Array<*> -> {
|
||||
member.returnType.jvmErasure.javaObjectType.componentType.kotlin
|
||||
}
|
||||
|
@ -46,22 +43,25 @@ data class ConfigProp(val parentConf: ConfigProp?, val parent: Any, val member:
|
|||
|
||||
@Synchronized
|
||||
fun isSupported(): Boolean {
|
||||
return member is KMutableProperty<*>
|
||||
return !ignore && member is KMutableProperty<*>
|
||||
}
|
||||
|
||||
@Synchronized
|
||||
fun get(): Any? {
|
||||
return if (parent is Array<*>) {
|
||||
parent[index]
|
||||
return if (parentObj is Array<*>) {
|
||||
parentObj[index]
|
||||
}
|
||||
else if (parent is ArrayList<*>) {
|
||||
parent[index]
|
||||
else if (parentObj is ArrayList<*>) {
|
||||
parentObj[index]
|
||||
}
|
||||
else if (parent is MutableList<*>) {
|
||||
parent[index]
|
||||
else if (parentObj is AbstractList<*>) {
|
||||
parentObj[index]
|
||||
}
|
||||
else if (parentObj is MutableList<*>) {
|
||||
parentObj[index]
|
||||
}
|
||||
else {
|
||||
member.getter.call(parent)
|
||||
member.getter.call(parentObj)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -69,17 +69,17 @@ data class ConfigProp(val parentConf: ConfigProp?, val parent: Any, val member:
|
|||
@Synchronized
|
||||
fun set(value: Any?) {
|
||||
if (member is KMutableProperty<*>) {
|
||||
if (parent is Array<*>) {
|
||||
(parent as Array<Any?>)[index] = value
|
||||
if (parentObj is Array<*>) {
|
||||
(parentObj as Array<Any?>)[index] = value
|
||||
}
|
||||
else if (parent is ArrayList<*>) {
|
||||
(parent as ArrayList<Any?>).set(index, value)
|
||||
else if (parentObj is ArrayList<*>) {
|
||||
(parentObj as ArrayList<Any?>).set(index, value)
|
||||
}
|
||||
else if (parent is AbstractList<*>) {
|
||||
(parent as MutableList<Any?>).set(index, value)
|
||||
else if (parentObj is MutableList<*>) {
|
||||
(parentObj as MutableList<Any?>).set(index, value)
|
||||
}
|
||||
else {
|
||||
member.setter.call(parent, value)
|
||||
member.setter.call(parentObj, value)
|
||||
}
|
||||
|
||||
// if the value is manually "set", then we consider it "not overridden"
|
||||
|
@ -107,6 +107,8 @@ data class ConfigProp(val parentConf: ConfigProp?, val parent: Any, val member:
|
|||
if (other !is ConfigProp) return false
|
||||
|
||||
if (isSupported() != other.isSupported()) return false
|
||||
if (ignore != other.ignore) return false
|
||||
if (key != other.key) return false
|
||||
if (get() != other.get()) return false
|
||||
return true
|
||||
}
|
||||
|
|
|
@ -17,8 +17,10 @@ package dorkbox
|
|||
|
||||
import dorkbox.config.ConfigProcessor
|
||||
import dorkbox.json.annotation.Json
|
||||
import dorkbox.os.OS
|
||||
import org.junit.Assert
|
||||
import org.junit.Test
|
||||
import org.slf4j.LoggerFactory
|
||||
|
||||
|
||||
class Test {
|
||||
|
@ -101,21 +103,24 @@ class Test {
|
|||
|
||||
Assert.assertTrue(conf.ip == "127.0.0.1")
|
||||
|
||||
System.setProperty("server", "true")
|
||||
val config = ConfigProcessor(conf)
|
||||
.envPrefix("")
|
||||
.cliArguments(arrayOf("ip_address=11.12.13.14"))
|
||||
.process()
|
||||
OS.setProperty("server", "true")
|
||||
try {
|
||||
val config = ConfigProcessor(conf)
|
||||
.envPrefix("")
|
||||
.cliArguments(arrayOf("ip_address=11.12.13.14"))
|
||||
.process()
|
||||
|
||||
|
||||
Assert.assertTrue(conf.ip == "11.12.13.14")
|
||||
Assert.assertTrue(conf.server)
|
||||
Assert.assertFalse(conf.client)
|
||||
Assert.assertTrue(conf.ip == "11.12.13.14")
|
||||
Assert.assertTrue(conf.server)
|
||||
Assert.assertFalse(conf.client)
|
||||
|
||||
Assert.assertEquals("{\"ip_address\":\"127.0.0.1\",\"server\":false,\"client\":false,\"nested\":[{\"iceCream\":false,\"potatoes\":true}]}", config.originalJson())
|
||||
Assert.assertEquals("{\"ip_address\":\"11.12.13.14\",\"server\":true,\"client\":false,\"nested\":[{\"iceCream\":false,\"potatoes\":true}]}", config.json())
|
||||
|
||||
System.clearProperty("server")
|
||||
Assert.assertEquals("{\"ip_address\":\"127.0.0.1\",\"server\":false,\"client\":false,\"nested\":[{\"iceCream\":false,\"potatoes\":true}]}", config.originalJson())
|
||||
Assert.assertEquals("{\"ip_address\":\"11.12.13.14\",\"server\":true,\"client\":false,\"nested\":[{\"iceCream\":false,\"potatoes\":true}]}", config.json())
|
||||
}
|
||||
finally {
|
||||
OS.clearProperty("server")
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@ -179,11 +184,195 @@ class Test {
|
|||
Assert.assertTrue(conf.ip == "0.0.0.0")
|
||||
Assert.assertTrue(conf.server)
|
||||
Assert.assertTrue(conf.client)
|
||||
Assert.assertFalse(conf.nested[0].iceCream)
|
||||
|
||||
Assert.assertEquals("{\"ip_address\":\"0.0.0.0\",\"server\":true,\"client\":true,\"nested\":[{\"iceCream\":false,\"potatoes\":true}]}", config.originalJson())
|
||||
Assert.assertEquals("{\"ip_address\":\"0.0.0.0\",\"server\":true,\"client\":true,\"nested\":[{\"iceCream\":false,\"potatoes\":true}]}", config.json())
|
||||
}
|
||||
|
||||
@Test
|
||||
fun updateInvalidTest() {
|
||||
val conf = Conf()
|
||||
|
||||
Assert.assertTrue(conf.ip == "127.0.0.1")
|
||||
|
||||
val config = ConfigProcessor(conf)
|
||||
.envPrefix("")
|
||||
.cliArguments(arrayOf("ip_address=1.2.3.4", "nested[0].iceCream"))
|
||||
.process()
|
||||
|
||||
|
||||
Assert.assertTrue(conf.ip == "1.2.3.4")
|
||||
Assert.assertFalse(conf.server)
|
||||
Assert.assertFalse(conf.client)
|
||||
Assert.assertTrue(conf.nested[0].iceCream)
|
||||
|
||||
|
||||
Assert.assertEquals("{\"ip_address\":\"127.0.0.1\",\"server\":false,\"client\":false,\"nested\":[{\"iceCream\":false,\"potatoes\":true}]}", config.originalJson())
|
||||
Assert.assertEquals("{\"ip_address\":\"1.2.3.4\",\"server\":false,\"client\":false,\"nested\":[{\"iceCream\":true,\"potatoes\":true}]}", config.json())
|
||||
|
||||
|
||||
// on purpose, there is a field here that doesn't exist
|
||||
config.logger = LoggerFactory.getLogger("test")
|
||||
config.loadAndProcess("{does_not_exit:0}")
|
||||
|
||||
Assert.assertTrue(conf.ip == "1.2.3.4")
|
||||
Assert.assertFalse(conf.server)
|
||||
Assert.assertFalse(conf.client)
|
||||
Assert.assertTrue(conf.nested[0].iceCream)
|
||||
|
||||
Assert.assertEquals("{\"ip_address\":\"127.0.0.1\",\"server\":false,\"client\":false,\"nested\":[{\"iceCream\":false,\"potatoes\":true}]}", config.originalJson())
|
||||
Assert.assertEquals("{\"ip_address\":\"1.2.3.4\",\"server\":false,\"client\":false,\"nested\":[{\"iceCream\":true,\"potatoes\":true}]}", config.json())
|
||||
}
|
||||
|
||||
@Test
|
||||
fun updateInvalid2Test() {
|
||||
val conf = Conf()
|
||||
|
||||
Assert.assertTrue(conf.ip == "127.0.0.1")
|
||||
|
||||
val config = ConfigProcessor(conf)
|
||||
.envPrefix("")
|
||||
.cliArguments(arrayOf("ip_address=1.2.3.4", "nested[0].iceCream"))
|
||||
.process()
|
||||
|
||||
|
||||
Assert.assertTrue(conf.ip == "1.2.3.4")
|
||||
Assert.assertFalse(conf.server)
|
||||
Assert.assertFalse(conf.client)
|
||||
Assert.assertTrue(conf.nested[0].iceCream)
|
||||
|
||||
|
||||
Assert.assertEquals("{\"ip_address\":\"127.0.0.1\",\"server\":false,\"client\":false,\"nested\":[{\"iceCream\":false,\"potatoes\":true}]}", config.originalJson())
|
||||
Assert.assertEquals("{\"ip_address\":\"1.2.3.4\",\"server\":false,\"client\":false,\"nested\":[{\"iceCream\":true,\"potatoes\":true}]}", config.json())
|
||||
|
||||
|
||||
// on purpose, there is a field here that doesn't exist
|
||||
config.json.exceptionOnMissingFields = false
|
||||
config.logger = LoggerFactory.getLogger("test")
|
||||
config.loadAndProcess("{does_not_exit:0}")
|
||||
|
||||
// the "default" object should be loaded!!! (but the override should still take effect)
|
||||
|
||||
Assert.assertTrue(conf.ip == "1.2.3.4")
|
||||
Assert.assertFalse(conf.server)
|
||||
Assert.assertFalse(conf.client)
|
||||
Assert.assertTrue(conf.nested[0].iceCream)
|
||||
|
||||
Assert.assertEquals("{\"ip_address\":\"127.0.0.1\",\"server\":false,\"client\":false,\"nested\":[{\"iceCream\":false,\"potatoes\":true}]}", config.originalJson())
|
||||
Assert.assertEquals("{\"ip_address\":\"1.2.3.4\",\"server\":false,\"client\":false,\"nested\":[{\"iceCream\":true,\"potatoes\":true}]}", config.json())
|
||||
}
|
||||
|
||||
@Test
|
||||
fun updateArrayTest() {
|
||||
val conf = ArrayConf()
|
||||
|
||||
Assert.assertArrayEquals(arrayOf(1, 2, 3, 4), conf.ips)
|
||||
|
||||
val config = ConfigProcessor(conf)
|
||||
.envPrefix("")
|
||||
.cliArguments(arrayOf("ips[0]=7"))
|
||||
.process()
|
||||
|
||||
// since we did not PROCESS the cli arguments again -- it means that the original has not been overloaded. To consider overloading stuff, process() must be called
|
||||
config.load("""
|
||||
{"ips":[1,2,3,4,5,6,7,]}
|
||||
""".trimIndent())
|
||||
|
||||
Assert.assertArrayEquals(arrayOf(1,2,3,4,5,6,7), conf.ips)
|
||||
|
||||
Assert.assertEquals("{\"ips\":[1,2,3,4,5,6,7]}", config.originalJson())
|
||||
Assert.assertEquals("{\"ips\":[1,2,3,4,5,6,7]}", config.json())
|
||||
|
||||
config.process() // overrides ips[0] -> 7
|
||||
Assert.assertEquals("{\"ips\":[1,2,3,4,5,6,7]}", config.originalJson())
|
||||
Assert.assertEquals("{\"ips\":[7,2,3,4,5,6,7]}", config.json())
|
||||
}
|
||||
|
||||
@Test
|
||||
fun updateArrayCliTest() {
|
||||
val conf = ArrayConf()
|
||||
|
||||
Assert.assertArrayEquals(arrayOf(1, 2, 3, 4), conf.ips)
|
||||
|
||||
val config = ConfigProcessor(conf)
|
||||
.envPrefix("")
|
||||
.cliArguments(arrayOf("ips[7]=7"))
|
||||
.process()
|
||||
|
||||
Assert.assertArrayEquals(arrayOf(1,2,3,4,0,0,0,7), conf.ips)
|
||||
|
||||
Assert.assertEquals("{\"ips\":[1,2,3,4]}", config.originalJson())
|
||||
Assert.assertEquals("{\"ips\":[1,2,3,4,0,0,0,7]}", config.json())
|
||||
}
|
||||
|
||||
@Test
|
||||
fun updateArraySysTest() {
|
||||
val conf = ArrayConf()
|
||||
|
||||
Assert.assertArrayEquals(arrayOf(1, 2, 3, 4), conf.ips)
|
||||
|
||||
OS.setProperty("ips[7]", "7")
|
||||
try {
|
||||
val config = ConfigProcessor(conf)
|
||||
.envPrefix("")
|
||||
.process()
|
||||
|
||||
Assert.assertArrayEquals(arrayOf(1,2,3,4,0,0,0,7), conf.ips)
|
||||
|
||||
Assert.assertEquals("{\"ips\":[1,2,3,4]}", config.originalJson())
|
||||
Assert.assertEquals("{\"ips\":[1,2,3,4,0,0,0,7]}", config.json())
|
||||
}
|
||||
finally {
|
||||
OS.clearProperty("ips[7]")
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun updateArrayEmptyTest() {
|
||||
val conf = ArrayEmptyConf()
|
||||
|
||||
Assert.assertArrayEquals(arrayOf<Int>(), conf.ips)
|
||||
|
||||
val config = ConfigProcessor(conf)
|
||||
.envPrefix("")
|
||||
.cliArguments(arrayOf("ips[0]=7"))
|
||||
.process()
|
||||
|
||||
// since we did not PROCESS the cli arguments again -- it means that the original has not been overloaded. To consider overloading stuff, process() must be called
|
||||
config.load("""
|
||||
{"ips":[1,2,3,4,5,6,7,]}
|
||||
""".trimIndent())
|
||||
|
||||
Assert.assertArrayEquals(arrayOf(1,2,3,4,5,6,7), conf.ips)
|
||||
|
||||
Assert.assertEquals("{\"ips\":[1,2,3,4,5,6,7]}", config.originalJson())
|
||||
Assert.assertEquals("{\"ips\":[1,2,3,4,5,6,7]}", config.json())
|
||||
|
||||
config.process()
|
||||
Assert.assertEquals("{\"ips\":[1,2,3,4,5,6,7]}", config.originalJson())
|
||||
Assert.assertEquals("{\"ips\":[7,2,3,4,5,6,7]}", config.json())
|
||||
}
|
||||
|
||||
@Test
|
||||
fun updateArrayListTest() {
|
||||
val conf = Conf()
|
||||
|
||||
val config = ConfigProcessor(conf)
|
||||
.envPrefix("")
|
||||
.cliArguments(arrayOf("nested[0].iceCream=true"))
|
||||
.process()
|
||||
|
||||
// since we did not PROCESS the cli arguments again -- it means that the original has not been overloaded. To consider overloading stuff, process() must be called
|
||||
config.load("""
|
||||
{"ip_address":"127.0.0.1","server":false,"client":false,"nested":[{"iceCream":false,"potatoes":true},{"iceCream":true,"potatoes":true},{"iceCream":false,"potatoes":false}]}
|
||||
""".trimIndent())
|
||||
|
||||
Assert.assertEquals("{\"ip_address\":\"127.0.0.1\",\"server\":false,\"client\":false,\"nested\":[{\"iceCream\":false,\"potatoes\":true},{\"iceCream\":true,\"potatoes\":true},{\"iceCream\":false,\"potatoes\":false}]}", config.originalJson())
|
||||
|
||||
Assert.assertEquals("{\"ip_address\":\"127.0.0.1\",\"server\":false,\"client\":false,\"nested\":[{\"iceCream\":false,\"potatoes\":true},{\"iceCream\":true,\"potatoes\":true},{\"iceCream\":false,\"potatoes\":false}]}", config.json())
|
||||
}
|
||||
|
||||
class ListConf {
|
||||
var ips = mutableListOf(1, 2, 3, 4)
|
||||
}
|
||||
|
@ -192,6 +381,10 @@ class Test {
|
|||
var ips = arrayOf(1, 2, 3, 4)
|
||||
}
|
||||
|
||||
class ArrayEmptyConf {
|
||||
var ips = arrayOf<Int>()
|
||||
}
|
||||
|
||||
class CharArrayConf {
|
||||
var ips = arrayOf('1', '2', '3', '4')
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue