From 243cb0a99c6a0e701c328545e49d58e0d90f5de6 Mon Sep 17 00:00:00 2001 From: Jenna Antilla <46546946+jennantilla@users.noreply.github.com> Date: Wed, 18 Oct 2023 15:26:34 -0700 Subject: [PATCH 1/7] WIP add synchronization blocks to prevent ConcurrentModificationException --- .../onesignal/common/events/EventProducer.kt | 20 ++- .../com/onesignal/common/modeling/Model.kt | 64 +++++---- .../onesignal/common/modeling/ModelStore.kt | 124 ++++++++++-------- .../common/modeling/SingletonModelStore.kt | 12 +- 4 files changed, 129 insertions(+), 91 deletions(-) diff --git a/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/common/events/EventProducer.kt b/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/common/events/EventProducer.kt index 7311cf7ff7..59bb8393a9 100644 --- a/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/common/events/EventProducer.kt +++ b/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/common/events/EventProducer.kt @@ -17,19 +17,25 @@ open class EventProducer : IEventNotifier { private val _subscribers: MutableList = Collections.synchronizedList(mutableListOf()) override fun subscribe(handler: THandler) { - _subscribers.add(handler) + synchronized(_subscribers) { + _subscribers.add(handler) + } } override fun unsubscribe(handler: THandler) { - _subscribers.remove(handler) + synchronized(_subscribers) { + _subscribers.remove(handler) + } } /** * Subscribe all from an existing producer to this subscriber. */ fun subscribeAll(from: EventProducer) { - for (s in from._subscribers) { - subscribe(s) + synchronized(_subscribers) { + for (s in from._subscribers) { + subscribe(s) + } } } @@ -40,8 +46,10 @@ open class EventProducer : IEventNotifier { * @param callback The callback will be invoked for each subscribed handler, allowing you to call the handler. */ fun fire(callback: (THandler) -> Unit) { - for (s in _subscribers) { - callback(s) + synchronized(_subscribers) { + for (s in _subscribers) { + callback(s) + } } } diff --git a/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/common/modeling/Model.kt b/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/common/modeling/Model.kt index 9b4e245727..2c71f381f8 100644 --- a/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/common/modeling/Model.kt +++ b/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/common/modeling/Model.kt @@ -53,6 +53,8 @@ open class Model( */ private val _parentProperty: String? = null, + private val initializationLock: Any = Any() + ) : IEventNotifier { /** @@ -120,19 +122,21 @@ open class Model( * @param model The model to initialize this model from. */ fun initializeFromModel(id: String?, model: Model) { - data.clear() - for (item in model.data) { - if (item.value is Model) { - val childModel = item.value as Model - childModel._parentModel = this - data[item.key] = childModel - } else { - data[item.key] = item.value + synchronized(initializationLock) { + data.clear() + for (item in model.data) { + if (item.value is Model) { + val childModel = item.value as Model + childModel._parentModel = this + data[item.key] = childModel + } else { + data[item.key] = item.value + } } - } - if (id != null) { - data[::id.name] = id + if (id != null) { + data[::id.name] = id + } } } @@ -308,29 +312,31 @@ open class Model( * @return The resulting [JSONObject]. */ fun toJSON(): JSONObject { - val jsonObject = JSONObject() - for (kvp in data) { - when (val value = kvp.value) { - is Model -> { - jsonObject.put(kvp.key, value.toJSON()) - } - is List<*> -> { - val jsonArray = JSONArray() - for (arrayItem in value) { - if (arrayItem is Model) { - jsonArray.put(arrayItem.toJSON()) - } else { - jsonArray.put(arrayItem) + synchronized(initializationLock) { + val jsonObject = JSONObject() + for (kvp in data) { + when (val value = kvp.value) { + is Model -> { + jsonObject.put(kvp.key, value.toJSON()) + } + is List<*> -> { + val jsonArray = JSONArray() + for (arrayItem in value) { + if (arrayItem is Model) { + jsonArray.put(arrayItem.toJSON()) + } else { + jsonArray.put(arrayItem) + } } + jsonObject.put(kvp.key, jsonArray) + } + else -> { + jsonObject.put(kvp.key, value) } - jsonObject.put(kvp.key, jsonArray) - } - else -> { - jsonObject.put(kvp.key, value) } } + return jsonObject } - return jsonObject } override fun subscribe(handler: IModelChangedHandler) = _changeNotifier.subscribe(handler) diff --git a/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/common/modeling/ModelStore.kt b/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/common/modeling/ModelStore.kt index 185b60415d..1ab36ce33c 100644 --- a/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/common/modeling/ModelStore.kt +++ b/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/common/modeling/ModelStore.kt @@ -38,21 +38,25 @@ abstract class ModelStore( private val _models: MutableList = mutableListOf() override fun add(model: TModel, tag: String) { - val oldModel = _models.firstOrNull { it.id == model.id } - if (oldModel != null) { - removeItem(oldModel, tag) - } + synchronized(_models) { + val oldModel = _models.firstOrNull { it.id == model.id } + if (oldModel != null) { + removeItem(oldModel, tag) + } - addItem(model, tag) + addItem(model, tag) + } } override fun add(index: Int, model: TModel, tag: String) { - val oldModel = _models.firstOrNull { it.id == model.id } - if (oldModel != null) { - removeItem(oldModel, tag) - } + synchronized(_models) { + val oldModel = _models.firstOrNull { it.id == model.id } + if (oldModel != null) { + removeItem(oldModel, tag) + } - addItem(model, tag, index) + addItem(model, tag, index) + } } override fun list(): Collection { @@ -64,84 +68,100 @@ abstract class ModelStore( } override fun remove(id: String, tag: String) { - val model = _models.firstOrNull { it.id == id } ?: return - removeItem(model, tag) + synchronized(_models) { + val model = _models.firstOrNull { it.id == id } ?: return + removeItem(model, tag) + } } override fun onChanged(args: ModelChangedArgs, tag: String) { - persist() + synchronized(_models) { + persist() - _changeSubscription.fire { it.onModelUpdated(args, tag) } + _changeSubscription.fire { it.onModelUpdated(args, tag) } + } } override fun replaceAll(models: List, tag: String) { - clear(tag) + synchronized(_models) { + clear(tag) - for (model in models) { - add(model, tag) + for (model in models) { + add(model, tag) + } } } override fun clear(tag: String) { - val localList = _models.toList() - _models.clear() + synchronized(_models) { + val localList = _models.toList() + _models.clear() - persist() + persist() - for (item in localList) { - // no longer listen for changes to this model - item.unsubscribe(this) - _changeSubscription.fire { it.onModelRemoved(item, tag) } + for (item in localList) { + // no longer listen for changes to this model + item.unsubscribe(this) + _changeSubscription.fire { it.onModelRemoved(item, tag) } + } } } private fun addItem(model: TModel, tag: String, index: Int? = null) { - if (index != null) { - _models.add(index, model) - } else { - _models.add(model) - } + synchronized(_models) { + if (index != null) { + _models.add(index, model) + } else { + _models.add(model) + } - // listen for changes to this model - model.subscribe(this) + // listen for changes to this model + model.subscribe(this) - persist() + persist() - _changeSubscription.fire { it.onModelAdded(model, tag) } + _changeSubscription.fire { it.onModelAdded(model, tag) } + } } private fun removeItem(model: TModel, tag: String) { - _models.remove(model) + synchronized(_models) { + _models.remove(model) - // no longer listen for changes to this model - model.unsubscribe(this) + // no longer listen for changes to this model + model.unsubscribe(this) - persist() + persist() - _changeSubscription.fire { it.onModelRemoved(model, tag) } + _changeSubscription.fire { it.onModelRemoved(model, tag) } + } } protected fun load() { - if (name != null && _prefs != null) { - val str = _prefs.getString(PreferenceStores.ONESIGNAL, PreferenceOneSignalKeys.MODEL_STORE_PREFIX + name, "[]") - val jsonArray = JSONArray(str) - for (index in 0 until jsonArray.length()) { - val newModel = create(jsonArray.getJSONObject(index)) ?: continue - _models.add(newModel) - // listen for changes to this model - newModel.subscribe(this) + synchronized(_models) { + if (name != null && _prefs != null) { + val str = _prefs.getString(PreferenceStores.ONESIGNAL, PreferenceOneSignalKeys.MODEL_STORE_PREFIX + name, "[]") + val jsonArray = JSONArray(str) + for (index in 0 until jsonArray.length()) { + val newModel = create(jsonArray.getJSONObject(index)) ?: continue + _models.add(newModel) + // listen for changes to this model + newModel.subscribe(this) + } } } } fun persist() { - if (name != null && _prefs != null) { - val jsonArray = JSONArray() - for (model in _models) { - jsonArray.put(model.toJSON()) + synchronized(_models) { + if (name != null && _prefs != null) { + val jsonArray = JSONArray() + for (model in _models) { + jsonArray.put(model.toJSON()) + } + + _prefs.saveString(PreferenceStores.ONESIGNAL, PreferenceOneSignalKeys.MODEL_STORE_PREFIX + name, jsonArray.toString()) } - - _prefs.saveString(PreferenceStores.ONESIGNAL, PreferenceOneSignalKeys.MODEL_STORE_PREFIX + name, jsonArray.toString()) } } diff --git a/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/common/modeling/SingletonModelStore.kt b/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/common/modeling/SingletonModelStore.kt index 73ba0aabd0..75dad35dda 100644 --- a/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/common/modeling/SingletonModelStore.kt +++ b/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/common/modeling/SingletonModelStore.kt @@ -13,6 +13,8 @@ open class SingletonModelStore( private val _changeSubscription: EventProducer> = EventProducer() private val _singletonId: String = "-singleton-" + private val replaceLock = Any() + init { store.subscribe(this) } @@ -31,10 +33,12 @@ open class SingletonModelStore( } override fun replace(model: TModel, tag: String) { - val existingModel = this.model - existingModel.initializeFromModel(_singletonId, model) - store.persist() - _changeSubscription.fire { it.onModelReplaced(existingModel, tag) } + synchronized(replaceLock) { + val existingModel = this.model + existingModel.initializeFromModel(_singletonId, model) + store.persist() + _changeSubscription.fire { it.onModelReplaced(existingModel, tag) } + } } override fun subscribe(handler: ISingletonModelStoreChangeHandler) = _changeSubscription.subscribe(handler) From 944783573f8d1c60089f67af375c7b936766f462 Mon Sep 17 00:00:00 2001 From: Jenna Antilla <46546946+jennantilla@users.noreply.github.com> Date: Wed, 1 Nov 2023 11:34:04 -0700 Subject: [PATCH 2/7] Add synchronized block to get method ensure additional thread safety in SingletonModelStore --- .../common/modeling/SingletonModelStore.kt | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/common/modeling/SingletonModelStore.kt b/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/common/modeling/SingletonModelStore.kt index 75dad35dda..6048d29351 100644 --- a/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/common/modeling/SingletonModelStore.kt +++ b/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/common/modeling/SingletonModelStore.kt @@ -21,15 +21,17 @@ open class SingletonModelStore( override val model: TModel get() { - val model = store.get(_singletonId) - if (model != null) { - return model + synchronized(this) { + val model = store.get(_singletonId) + if (model != null) { + return model + } + + val createdModel = store.create() ?: throw Exception("Unable to initialize model from store $store") + createdModel.id = _singletonId + store.add(createdModel) + return createdModel } - - val createdModel = store.create() ?: throw Exception("Unable to initialize model from store $store") - createdModel.id = _singletonId - store.add(createdModel) - return createdModel } override fun replace(model: TModel, tag: String) { From e8bf0e89859cb8ddc9b2b85517691f471db3c188 Mon Sep 17 00:00:00 2001 From: Jenna Antilla <46546946+jennantilla@users.noreply.github.com> Date: Wed, 1 Nov 2023 11:38:37 -0700 Subject: [PATCH 3/7] Update initializeFromModel to never clear the model id Prevent a null id --- .../core/src/main/java/com/onesignal/common/modeling/Model.kt | 1 - 1 file changed, 1 deletion(-) diff --git a/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/common/modeling/Model.kt b/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/common/modeling/Model.kt index 2c71f381f8..ce1ad89002 100644 --- a/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/common/modeling/Model.kt +++ b/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/common/modeling/Model.kt @@ -123,7 +123,6 @@ open class Model( */ fun initializeFromModel(id: String?, model: Model) { synchronized(initializationLock) { - data.clear() for (item in model.data) { if (item.value is Model) { val childModel = item.value as Model From 68d2b5a37568f803d792eb420ee658013944be32 Mon Sep 17 00:00:00 2001 From: Jenna Antilla <46546946+jennantilla@users.noreply.github.com> Date: Wed, 1 Nov 2023 14:11:03 -0700 Subject: [PATCH 4/7] Update initializeFromModel to build a local map to replace data --- .../com/onesignal/common/modeling/Model.kt | 29 +++++++++++-------- 1 file changed, 17 insertions(+), 12 deletions(-) diff --git a/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/common/modeling/Model.kt b/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/common/modeling/Model.kt index ce1ad89002..74f69fdf26 100644 --- a/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/common/modeling/Model.kt +++ b/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/common/modeling/Model.kt @@ -122,21 +122,26 @@ open class Model( * @param model The model to initialize this model from. */ fun initializeFromModel(id: String?, model: Model) { - synchronized(initializationLock) { - for (item in model.data) { - if (item.value is Model) { - val childModel = item.value as Model - childModel._parentModel = this - data[item.key] = childModel - } else { - data[item.key] = item.value - } - } + val newData = Collections.synchronizedMap(mutableMapOf()) - if (id != null) { - data[::id.name] = id + for (item in model.data) { + if (item.value is Model) { + val childModel = item.value as Model + childModel._parentModel = this + newData[item.key] = childModel + } else { + newData[item.key] = item.value } } + + if (id != null) { + newData[::id.name] = id + } + + synchronized(initializationLock) { + data.clear() + data.putAll(newData) + } } /** From df534d53a15ccb052b8321c0fcbf4ec085839a40 Mon Sep 17 00:00:00 2001 From: Jenna Antilla <46546946+jennantilla@users.noreply.github.com> Date: Thu, 2 Nov 2023 14:01:10 -0700 Subject: [PATCH 5/7] Add additional synchronized blocks to get methods --- .../java/com/onesignal/common/modeling/Model.kt | 16 ++++++++++------ .../com/onesignal/common/modeling/ModelStore.kt | 10 ++++++++-- 2 files changed, 18 insertions(+), 8 deletions(-) diff --git a/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/common/modeling/Model.kt b/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/common/modeling/Model.kt index 20f661249f..b8c0ef08f2 100644 --- a/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/common/modeling/Model.kt +++ b/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/common/modeling/Model.kt @@ -51,13 +51,15 @@ open class Model( * specified, must also specify [_parentModel] */ private val _parentProperty: String? = null, - private val initializationLock: Any = Any(), + private val modelSynchronizationLock: Any = Any(), ) : IEventNotifier { /** * A unique identifier for this model. */ var id: String - get() = getStringProperty(::id.name) + get() = synchronized(modelSynchronizationLock) { + getStringProperty(::id.name) + } set(value) { setStringProperty(::id.name, value) } @@ -124,7 +126,7 @@ open class Model( id: String?, model: Model, ) { - val newData = Collections.synchronizedMap(mutableMapOf()) + val newData = mutableMapOf() for (item in model.data) { if (item.value is Model) { @@ -140,7 +142,7 @@ open class Model( newData[::id.name] = id } - synchronized(initializationLock) { + synchronized(modelSynchronizationLock) { data.clear() data.putAll(newData) } @@ -667,7 +669,7 @@ open class Model( * @return The resulting [JSONObject]. */ fun toJSON(): JSONObject { - synchronized(initializationLock) { + synchronized(modelSynchronizationLock) { val jsonObject = JSONObject() for (kvp in data) { when (val value = kvp.value) { @@ -699,5 +701,7 @@ open class Model( override fun unsubscribe(handler: IModelChangedHandler) = changeNotifier.unsubscribe(handler) override val hasSubscribers: Boolean - get() = changeNotifier.hasSubscribers + get() = synchronized(modelSynchronizationLock) { + changeNotifier.hasSubscribers + } } diff --git a/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/common/modeling/ModelStore.kt b/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/common/modeling/ModelStore.kt index 64abecbfae..b77a36ec11 100644 --- a/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/common/modeling/ModelStore.kt +++ b/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/common/modeling/ModelStore.kt @@ -70,7 +70,9 @@ abstract class ModelStore( } override fun get(id: String): TModel? { - return models.firstOrNull { it.id == id } + synchronized(models) { + return models.firstOrNull { it.id == id } + } } override fun remove( @@ -192,5 +194,9 @@ abstract class ModelStore( override fun unsubscribe(handler: IModelStoreChangeHandler) = changeSubscription.unsubscribe(handler) override val hasSubscribers: Boolean - get() = changeSubscription.hasSubscribers + get() { + synchronized(changeSubscription) { + return changeSubscription.hasSubscribers + } + } } From 1847cb465e4c3555fa33430057995d6cd15bc291 Mon Sep 17 00:00:00 2001 From: Jenna Antilla <46546946+jennantilla@users.noreply.github.com> Date: Fri, 3 Nov 2023 12:25:09 -0700 Subject: [PATCH 6/7] Revert "Add additional synchronized blocks to get methods" This reverts commit df534d53a15ccb052b8321c0fcbf4ec085839a40. --- .../java/com/onesignal/common/modeling/Model.kt | 16 ++++++---------- .../com/onesignal/common/modeling/ModelStore.kt | 10 ++-------- 2 files changed, 8 insertions(+), 18 deletions(-) diff --git a/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/common/modeling/Model.kt b/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/common/modeling/Model.kt index b8c0ef08f2..20f661249f 100644 --- a/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/common/modeling/Model.kt +++ b/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/common/modeling/Model.kt @@ -51,15 +51,13 @@ open class Model( * specified, must also specify [_parentModel] */ private val _parentProperty: String? = null, - private val modelSynchronizationLock: Any = Any(), + private val initializationLock: Any = Any(), ) : IEventNotifier { /** * A unique identifier for this model. */ var id: String - get() = synchronized(modelSynchronizationLock) { - getStringProperty(::id.name) - } + get() = getStringProperty(::id.name) set(value) { setStringProperty(::id.name, value) } @@ -126,7 +124,7 @@ open class Model( id: String?, model: Model, ) { - val newData = mutableMapOf() + val newData = Collections.synchronizedMap(mutableMapOf()) for (item in model.data) { if (item.value is Model) { @@ -142,7 +140,7 @@ open class Model( newData[::id.name] = id } - synchronized(modelSynchronizationLock) { + synchronized(initializationLock) { data.clear() data.putAll(newData) } @@ -669,7 +667,7 @@ open class Model( * @return The resulting [JSONObject]. */ fun toJSON(): JSONObject { - synchronized(modelSynchronizationLock) { + synchronized(initializationLock) { val jsonObject = JSONObject() for (kvp in data) { when (val value = kvp.value) { @@ -701,7 +699,5 @@ open class Model( override fun unsubscribe(handler: IModelChangedHandler) = changeNotifier.unsubscribe(handler) override val hasSubscribers: Boolean - get() = synchronized(modelSynchronizationLock) { - changeNotifier.hasSubscribers - } + get() = changeNotifier.hasSubscribers } diff --git a/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/common/modeling/ModelStore.kt b/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/common/modeling/ModelStore.kt index b77a36ec11..64abecbfae 100644 --- a/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/common/modeling/ModelStore.kt +++ b/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/common/modeling/ModelStore.kt @@ -70,9 +70,7 @@ abstract class ModelStore( } override fun get(id: String): TModel? { - synchronized(models) { - return models.firstOrNull { it.id == id } - } + return models.firstOrNull { it.id == id } } override fun remove( @@ -194,9 +192,5 @@ abstract class ModelStore( override fun unsubscribe(handler: IModelStoreChangeHandler) = changeSubscription.unsubscribe(handler) override val hasSubscribers: Boolean - get() { - synchronized(changeSubscription) { - return changeSubscription.hasSubscribers - } - } + get() = changeSubscription.hasSubscribers } From ae758cf0ba783014461f61bb8fbca9a652875ee0 Mon Sep 17 00:00:00 2001 From: Jenna Antilla <46546946+jennantilla@users.noreply.github.com> Date: Fri, 3 Nov 2023 13:10:02 -0700 Subject: [PATCH 7/7] Add synchronized blocks to model get and set methods --- .../com/onesignal/common/modeling/Model.kt | 36 ++++++++++--------- 1 file changed, 20 insertions(+), 16 deletions(-) diff --git a/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/common/modeling/Model.kt b/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/common/modeling/Model.kt index 20f661249f..fe7cbaac77 100644 --- a/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/common/modeling/Model.kt +++ b/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/common/modeling/Model.kt @@ -436,19 +436,21 @@ open class Model( tag: String = ModelChangeTags.NORMAL, forceChange: Boolean = false, ) { - val oldValue = data[name] + synchronized(data) { + val oldValue = data[name] - if (oldValue == value && !forceChange) { - return - } + if (oldValue == value && !forceChange) { + return + } - if (value != null) { - data[name] = value - } else if (data.containsKey(name)) { - data.remove(name) - } + if (value != null) { + data[name] = value + } else if (data.containsKey(name)) { + data.remove(name) + } - notifyChanged(name, name, tag, oldValue, value) + notifyChanged(name, name, tag, oldValue, value) + } } /** @@ -634,12 +636,14 @@ open class Model( name: String, create: (() -> Any?)? = null, ): Any? { - return if (data.containsKey(name) || create == null) { - data[name] - } else { - val defaultValue = create() - data[name] = defaultValue as Any? - defaultValue + synchronized(data) { + return if (data.containsKey(name) || create == null) { + data[name] + } else { + val defaultValue = create() + data[name] = defaultValue as Any? + defaultValue + } } }