From ba02b58e7d28a5b31242350933dc1a74a636ca20 Mon Sep 17 00:00:00 2001 From: Walter Huf Date: Sat, 6 Apr 2024 11:14:55 -0700 Subject: [PATCH] Split the XML deserialization into jvmMain --- .../idriveconnectkit/rhmi/RHMIAction.kt | 67 +------ .../idriveconnectkit/rhmi/RHMIApplication.kt | 122 ------------ .../idriveconnectkit/rhmi/RHMIComponent.kt | 108 +--------- .../idriveconnectkit/rhmi/RHMIEvent.kt | 69 +------ .../idriveconnectkit/rhmi/RHMIModel.kt | 184 +++--------------- .../idriveconnectkit/rhmi/RHMIProperty.kt | 36 +--- .../idriveconnectkit/rhmi/RHMIState.kt | 104 +--------- .../rhmi/mocking/MockApplication.kt | 110 ----------- .../idriveconnectkit/TestRHMIApplication.kt | 6 +- .../rhmi/RHMIApplicationEtch.kt | 61 ++++++ .../rhmi/RHMIApplicationWrappers.kt | 2 + .../idriveconnectkit/rhmi/RHMIModelLive.kt | 79 ++++++++ .../rhmi/deserialization/RHMIAction.kt | 38 ++++ .../rhmi/deserialization/RHMIApplication.kt | 83 ++++++++ .../rhmi/deserialization/RHMIComponent.kt | 48 +++++ .../rhmi/deserialization/RHMIEvent.kt | 30 +++ .../rhmi/deserialization/RHMIModel.kt | 42 ++++ .../rhmi/deserialization/RHMIProperty.kt | 40 ++++ .../rhmi/deserialization/RHMIState.kt | 71 +++++++ .../rhmi/mocking/MockApplication.kt | 34 ++++ .../rhmi/mocking/RHMIActionMock.kt | 45 +++++ .../rhmi/mocking/RHMIComponentMock.kt | 82 ++++++++ .../rhmi/mocking/RHMIEventMock.kt | 52 +++++ .../rhmi/mocking/RHMIModelMock.kt | 76 ++++++++ .../rhmi/mocking/RHMIStateMock.kt | 53 +++++ .../idriveconnectkit/xmlutils/XMLUtils.kt | 0 .../TestRHMIApplicationIdempotent.kt | 16 +- .../TestRHMIApplicationSynchronized.kt | 4 +- .../idriveconnectkit/TestXMLParsing.kt | 1 + .../resources/ui_layout.xml | 0 30 files changed, 890 insertions(+), 773 deletions(-) delete mode 100644 core/src/commonMain/kotlin/io/bimmergestalt/idriveconnectkit/rhmi/mocking/MockApplication.kt create mode 100644 core/src/jvmMain/kotlin/io/bimmergestalt/idriveconnectkit/rhmi/RHMIApplicationEtch.kt rename core/src/{commonMain => jvmMain}/kotlin/io/bimmergestalt/idriveconnectkit/rhmi/RHMIApplicationWrappers.kt (97%) create mode 100644 core/src/jvmMain/kotlin/io/bimmergestalt/idriveconnectkit/rhmi/RHMIModelLive.kt create mode 100644 core/src/jvmMain/kotlin/io/bimmergestalt/idriveconnectkit/rhmi/deserialization/RHMIAction.kt create mode 100644 core/src/jvmMain/kotlin/io/bimmergestalt/idriveconnectkit/rhmi/deserialization/RHMIApplication.kt create mode 100644 core/src/jvmMain/kotlin/io/bimmergestalt/idriveconnectkit/rhmi/deserialization/RHMIComponent.kt create mode 100644 core/src/jvmMain/kotlin/io/bimmergestalt/idriveconnectkit/rhmi/deserialization/RHMIEvent.kt create mode 100644 core/src/jvmMain/kotlin/io/bimmergestalt/idriveconnectkit/rhmi/deserialization/RHMIModel.kt create mode 100644 core/src/jvmMain/kotlin/io/bimmergestalt/idriveconnectkit/rhmi/deserialization/RHMIProperty.kt create mode 100644 core/src/jvmMain/kotlin/io/bimmergestalt/idriveconnectkit/rhmi/deserialization/RHMIState.kt create mode 100644 core/src/jvmMain/kotlin/io/bimmergestalt/idriveconnectkit/rhmi/mocking/MockApplication.kt create mode 100644 core/src/jvmMain/kotlin/io/bimmergestalt/idriveconnectkit/rhmi/mocking/RHMIActionMock.kt create mode 100644 core/src/jvmMain/kotlin/io/bimmergestalt/idriveconnectkit/rhmi/mocking/RHMIComponentMock.kt create mode 100644 core/src/jvmMain/kotlin/io/bimmergestalt/idriveconnectkit/rhmi/mocking/RHMIEventMock.kt create mode 100644 core/src/jvmMain/kotlin/io/bimmergestalt/idriveconnectkit/rhmi/mocking/RHMIModelMock.kt create mode 100644 core/src/jvmMain/kotlin/io/bimmergestalt/idriveconnectkit/rhmi/mocking/RHMIStateMock.kt rename core/src/{commonMain => jvmMain}/kotlin/io/bimmergestalt/idriveconnectkit/xmlutils/XMLUtils.kt (100%) rename core/src/{commonTest => jvmTest}/kotlin/io/bimmergestalt/idriveconnectkit/TestRHMIApplicationIdempotent.kt (93%) rename core/src/{commonTest => jvmTest}/kotlin/io/bimmergestalt/idriveconnectkit/TestRHMIApplicationSynchronized.kt (95%) rename core/src/{commonTest => jvmTest}/kotlin/io/bimmergestalt/idriveconnectkit/TestXMLParsing.kt (99%) rename core/src/{commonTest => jvmTest}/resources/ui_layout.xml (100%) diff --git a/core/src/commonMain/kotlin/io/bimmergestalt/idriveconnectkit/rhmi/RHMIAction.kt b/core/src/commonMain/kotlin/io/bimmergestalt/idriveconnectkit/rhmi/RHMIAction.kt index 8671d48..1f0939c 100644 --- a/core/src/commonMain/kotlin/io/bimmergestalt/idriveconnectkit/rhmi/RHMIAction.kt +++ b/core/src/commonMain/kotlin/io/bimmergestalt/idriveconnectkit/rhmi/RHMIAction.kt @@ -1,46 +1,9 @@ package io.bimmergestalt.idriveconnectkit.rhmi -import io.bimmergestalt.idriveconnectkit.rhmi.mocking.RHMIApplicationMock -import io.bimmergestalt.idriveconnectkit.xmlutils.XMLUtils -import io.bimmergestalt.idriveconnectkit.xmlutils.getAttributesMap -import io.bimmergestalt.idriveconnectkit.xmlutils.getChildElements -import io.bimmergestalt.idriveconnectkit.xmlutils.getChildNamed -import org.w3c.dom.Node +abstract class RHMIAction protected constructor(open val app: RHMIApplication, open val id: Int) { -abstract class RHMIAction private constructor(open val app: RHMIApplication, open val id: Int) { - - companion object { - fun loadFromXML(app: RHMIApplication, node: Node): RHMIAction? { - val attrs = node.getAttributesMap() - - val id = attrs["id"]?.toInt() ?: return null - - if (node.nodeName == "combinedAction") { - val subactions = node.getChildNamed("actions").getChildElements().mapNotNull { subactionNode -> - loadFromXML(app, subactionNode) - } - val raAction = subactions.firstOrNull { it is RAAction } as RAAction? - val hmiAction = subactions.firstOrNull { it is HMIAction } as HMIAction? - val action = CombinedAction(app, id, raAction, hmiAction) - XMLUtils.unmarshalAttributes(action, attrs) - return action - } - - val action = when (node.nodeName) { - "hmiAction" -> HMIAction(app, id) - "raAction" -> RAAction(app, id) - "linkAction" -> LinkAction(app, id) - else -> null - } - - if (action != null) { - XMLUtils.unmarshalAttributes(action, attrs) - } - return action - } - - } + companion object { } class CombinedAction(override val app: RHMIApplication, override val id: Int, val raAction: RAAction?, val hmiAction: HMIAction?): RHMIAction(app, id) { var sync: String = "" @@ -87,32 +50,6 @@ abstract class RHMIAction private constructor(open val app: RHMIApplication, ope } } - class MockAction(override val app: RHMIApplicationMock, override val id: Int): RHMIAction(app, id) { - override fun asCombinedAction(): CombinedAction { - return app.actions.computeIfWrongType(id) { - CombinedAction(app, id, RAAction(app, id), HMIAction(app, id)) - } - } - - override fun asHMIAction(): HMIAction { - return app.actions.computeIfWrongType(id) { - HMIAction(app, id) - } - } - - override fun asRAAction(): RAAction { - return app.actions.computeIfWrongType(id) { - RAAction(app, id) - } - } - - override fun asLinkAction(): LinkAction { - return app.actions.computeIfWrongType(id) { - LinkAction(app, id) - } - } - } - open fun asCombinedAction(): CombinedAction? { return this as? CombinedAction } diff --git a/core/src/commonMain/kotlin/io/bimmergestalt/idriveconnectkit/rhmi/RHMIApplication.kt b/core/src/commonMain/kotlin/io/bimmergestalt/idriveconnectkit/rhmi/RHMIApplication.kt index abf2a3d..a04a7c8 100644 --- a/core/src/commonMain/kotlin/io/bimmergestalt/idriveconnectkit/rhmi/RHMIApplication.kt +++ b/core/src/commonMain/kotlin/io/bimmergestalt/idriveconnectkit/rhmi/RHMIApplication.kt @@ -1,13 +1,5 @@ package io.bimmergestalt.idriveconnectkit.rhmi -import de.bmw.idrive.BMWRemoting -import de.bmw.idrive.BMWRemotingServer -import io.bimmergestalt.idriveconnectkit.xmlutils.XMLUtils -import io.bimmergestalt.idriveconnectkit.xmlutils.getAttribute -import io.bimmergestalt.idriveconnectkit.xmlutils.getChildElements -import io.bimmergestalt.idriveconnectkit.xmlutils.getChildNamed -import org.w3c.dom.Document - abstract class RHMIApplication { abstract val models: MutableMap @@ -18,85 +10,14 @@ abstract class RHMIApplication { var ignoreUpdates = false - fun loadFromXML(description: String) { - return this.loadFromXML(description.toByteArray()) - } - fun loadFromXML(description: ByteArray) { - return this.loadFromXML(XMLUtils.loadXML(description)) - } - fun loadFromXML(description: Document) { - ignoreUpdates = true - description.getChildNamed("pluginApps").getChildElements().forEach { pluginAppNode -> - pluginAppNode.getChildNamed("models").getChildElements().forEach { modelNode -> - val model = RHMIModel.loadFromXML(this, modelNode) - if (model != null) { - models[model.id] = model - if (model is RHMIModel.FormatDataModel) { - model.submodels.forEach { models[it.id] = it } - } - } - } - pluginAppNode.getChildNamed("actions").getChildElements().forEach { actionNode -> - val action = RHMIAction.loadFromXML(this, actionNode) - if (action != null) { - actions[action.id] = action - if (action is RHMIAction.CombinedAction) { - if (action.raAction != null) actions[action.raAction.id] = action.raAction - if (action.hmiAction != null) actions[action.hmiAction.id] = action.hmiAction - } - } - } - pluginAppNode.getChildNamed("events").getChildElements().forEach { actionNode -> - val event = RHMIEvent.loadFromXML(this, actionNode) - if (event != null) { - events[event.id] = event - } - } - pluginAppNode.getChildNamed("hmiStates").getChildElements().forEach { stateNode -> - val state = RHMIState.loadFromXML(this, stateNode) - if (state != null) { - states[state.id] = state - components.putAll(state.components) - if (state is RHMIState.ToolbarState) { - components.putAll(state.toolbarComponents) - } - } - } - val entryButtonNode = pluginAppNode.getChildNamed("entryButton") - if (entryButtonNode != null) { - val component = RHMIComponent.loadFromXML(this, entryButtonNode) - if (component is RHMIComponent.EntryButton) { - pluginAppNode.getAttribute("applicationType")?.also { - component.applicationType = it - } - pluginAppNode.getAttribute("applicationWeight")?.toIntOrNull()?.also { - component.applicationWeight = it - } - components[component.id] = component - } - } - val instrumentClusterNode = pluginAppNode.getChildNamed("instrumentCluster") - if (instrumentClusterNode != null) { - val component = RHMIComponent.loadFromXML(this, instrumentClusterNode) - if (component != null) { - components[component.id] = component - } - } - } - ignoreUpdates = false - } - - @Throws(BMWRemoting.SecurityException::class, BMWRemoting.IllegalArgumentException::class, BMWRemoting.ServiceException::class) abstract fun setModel(modelId: Int, value: Any?) open fun getModel(modelId: Int): Any? = null - @Throws(BMWRemoting.SecurityException::class, BMWRemoting.IllegalArgumentException::class, BMWRemoting.ServiceException::class) abstract fun setProperty(componentId: Int, propertyId: Int, value: Any?) open fun getProperty(componentId: Int, propertyId: Int): Any? = null - @Throws(BMWRemoting.SecurityException::class, BMWRemoting.IllegalArgumentException::class, BMWRemoting.ServiceException::class) abstract fun triggerHMIEvent(eventId: Int, args: Map) } @@ -130,46 +51,3 @@ class RHMIApplicationConcrete : RHMIApplication() { } } - -class RHMIApplicationEtch(val remoteServer: BMWRemotingServer, val rhmiHandle: Int) : RHMIApplication() { - /** Represents an application layout that is backed by a Car connection - * Most data is not retained, so if you want to read data back out, - * use RHMIApplicationConcrete or RHMIApplicationIdempotent - * */ - override val models = HashMap() - override val actions = HashMap() - override val events = HashMap() - override val states = HashMap() - override val components = HashMap() - - // remember a little bit of properties and small ints - val modelData = HashMap() - val propertyData = HashMap>() - - @Throws(BMWRemoting.SecurityException::class, BMWRemoting.IllegalArgumentException::class, BMWRemoting.ServiceException::class) - override fun setModel(modelId: Int, value: Any?) { - if (value is Int || value is BMWRemoting.RHMIResourceIdentifier) { - modelData[modelId] = value - } else { - modelData.remove(modelId) - } - if (ignoreUpdates) return - this.remoteServer.rhmi_setData(this.rhmiHandle, modelId, value) - } - override fun getModel(modelId: Int): Any? = modelData[modelId] - - @Throws(BMWRemoting.SecurityException::class, BMWRemoting.IllegalArgumentException::class, BMWRemoting.ServiceException::class) - override fun setProperty(componentId: Int, propertyId: Int, value: Any?) { - propertyData.getOrPut(componentId){HashMap()}[propertyId] = value - if (ignoreUpdates) return - val propertyValue = HashMap() - propertyValue[0] = value - this.remoteServer.rhmi_setProperty(rhmiHandle, componentId, propertyId, propertyValue) - } - override fun getProperty(componentId: Int, propertyId: Int): Any? = propertyData[componentId]?.get(propertyId) - - @Throws(BMWRemoting.SecurityException::class, BMWRemoting.IllegalArgumentException::class, BMWRemoting.ServiceException::class) - override fun triggerHMIEvent(eventId: Int, args: Map) { - this.remoteServer.rhmi_triggerEvent(rhmiHandle, eventId, args) - } -} \ No newline at end of file diff --git a/core/src/commonMain/kotlin/io/bimmergestalt/idriveconnectkit/rhmi/RHMIComponent.kt b/core/src/commonMain/kotlin/io/bimmergestalt/idriveconnectkit/rhmi/RHMIComponent.kt index 2b62c5d..9c1dabe 100644 --- a/core/src/commonMain/kotlin/io/bimmergestalt/idriveconnectkit/rhmi/RHMIComponent.kt +++ b/core/src/commonMain/kotlin/io/bimmergestalt/idriveconnectkit/rhmi/RHMIComponent.kt @@ -1,58 +1,17 @@ package io.bimmergestalt.idriveconnectkit.rhmi import io.bimmergestalt.idriveconnectkit.Utils.etchAsInt -import io.bimmergestalt.idriveconnectkit.rhmi.mocking.RHMIApplicationMock import io.bimmergestalt.idriveconnectkit.withRealDefault -import io.bimmergestalt.idriveconnectkit.xmlutils.XMLUtils -import io.bimmergestalt.idriveconnectkit.xmlutils.getAttributesMap -import io.bimmergestalt.idriveconnectkit.xmlutils.getChildElements -import io.bimmergestalt.idriveconnectkit.xmlutils.getChildNamed -import org.w3c.dom.Node -abstract class RHMIComponent private constructor(open val app: RHMIApplication, open val id: Int) { +abstract class RHMIComponent protected constructor(open val app: RHMIApplication, open val id: Int) { val properties: MutableMap = HashMap().withRealDefault { propertyId -> // look up from the app storage if the property wasn't loaded from xml RHMIProperty.AppProperty(app, id, propertyId) } - companion object { - fun loadFromXML(app: RHMIApplication, node: Node): RHMIComponent? { - val attrs = node.getAttributesMap() - - val id = attrs["id"]?.toInt() ?: return null - - val component = when (node.nodeName) { - "separator" -> Separator(app, id) - "image" -> Image(app, id) - "label" -> Label(app, id) - "list" -> List(app, id) - "entryButton" -> EntryButton(app, id) - "instrumentCluster" -> InstrumentCluster(app, id) - "button" -> if (attrs["model"] == null) ToolbarButton(app, id) else Button(app, id) - "checkbox" -> Checkbox(app, id) - "gauge" -> Gauge(app, id) - "input" -> Input(app, id) - "calendarDay" -> CalendarDay(app, id) - else -> null - } - - if (component != null) { - XMLUtils.unmarshalAttributes(component, attrs) - - val propertyNodes = node.getChildNamed("properties") - if (propertyNodes != null) { - propertyNodes.getChildElements().filter { it.nodeName == "property" }.forEach { - val property = RHMIProperty.loadFromXML(app, component.id, it) - if (property != null) - component.properties[property.id] = property - } - } - } - return component - } - } + companion object { } fun setProperty(property: RHMIProperty.PropertyId, value: Any) { this.setProperty(property.id, value) @@ -342,69 +301,6 @@ abstract class RHMIComponent private constructor(open val app: RHMIApplication, } } - class MockComponent(override val app: RHMIApplicationMock, override val id: Int): RHMIComponent(app, id) { - override fun asSeparator(): Separator { - return app.components.computeIfWrongType(id) { - Separator(app, id) - } - } - override fun asImage(): Image { - return app.components.computeIfWrongType(id) { - Image(app, id) - } - } - override fun asLabel(): Label { - return app.components.computeIfWrongType(id) { - Label(app, id) - } - } - override fun asList(): List { - return app.components.computeIfWrongType(id) { - List(app, id) - } - } - override fun asEntryButton(): EntryButton { - return app.components.computeIfWrongType(id) { - EntryButton(app, id) - } - } - override fun asInstrumentCluster(): InstrumentCluster { - return app.components.computeIfWrongType(id) { - InstrumentCluster(app, id) - } - } - override fun asToolbarButton(): ToolbarButton { - return app.components.computeIfWrongType(id) { - ToolbarButton(app, id) - } - } - override fun asButton(): Button { - return app.components.computeIfWrongType(id) { - Button(app, id) - } - } - override fun asCheckbox(): Checkbox { - return app.components.computeIfWrongType(id) { - Checkbox(app, id) - } - } - override fun asGauge(): Gauge { - return app.components.computeIfWrongType(id) { - Gauge(app, id) - } - } - override fun asInput(): Input { - return app.components.computeIfWrongType(id) { - Input(app, id) - } - } - override fun asCalendarDay(): CalendarDay { - return app.components.computeIfWrongType(id) { - CalendarDay(app, id) - } - } - } - open fun asSeparator(): Separator? { return this as? Separator } diff --git a/core/src/commonMain/kotlin/io/bimmergestalt/idriveconnectkit/rhmi/RHMIEvent.kt b/core/src/commonMain/kotlin/io/bimmergestalt/idriveconnectkit/rhmi/RHMIEvent.kt index 7c735cc..872b7c6 100644 --- a/core/src/commonMain/kotlin/io/bimmergestalt/idriveconnectkit/rhmi/RHMIEvent.kt +++ b/core/src/commonMain/kotlin/io/bimmergestalt/idriveconnectkit/rhmi/RHMIEvent.kt @@ -1,43 +1,14 @@ package io.bimmergestalt.idriveconnectkit.rhmi -import de.bmw.idrive.BMWRemoting -import io.bimmergestalt.idriveconnectkit.rhmi.mocking.RHMIApplicationMock -import io.bimmergestalt.idriveconnectkit.xmlutils.XMLUtils -import io.bimmergestalt.idriveconnectkit.xmlutils.getAttributesMap -import org.w3c.dom.Node - -abstract class RHMIEvent private constructor(open val app: RHMIApplication, open val id: Int) { - - companion object { - fun loadFromXML(app: RHMIApplication, node: Node): RHMIEvent? { - val attrs = node.getAttributesMap() - - val id = attrs["id"]?.toInt() ?: return null - - val event = when (node.nodeName) { - "popupEvent" -> PopupEvent(app, id) - "actionEvent" -> ActionEvent(app, id) - "notificationEvent" -> NotificationEvent(app, id) - "notificationIconEvent" -> NotificationIconEvent(app, id) - "focusEvent" -> FocusEvent(app, id) - "multimediaInfoEvent" -> MultimediaInfoEvent(app, id) - "statusbarEvent" -> StatusbarEvent(app, id) - else -> null - } - - if (event != null) { - XMLUtils.unmarshalAttributes(event, attrs) - } - return event - } - } - @Throws(BMWRemoting.SecurityException::class, BMWRemoting.IllegalArgumentException::class, BMWRemoting.ServiceException::class) +abstract class RHMIEvent protected constructor(open val app: RHMIApplication, open val id: Int) { + + companion object { } + fun triggerEvent() { triggerEvent(mapOf(0 to null)) } - @Throws(BMWRemoting.SecurityException::class, BMWRemoting.IllegalArgumentException::class, BMWRemoting.ServiceException::class) fun triggerEvent(args: Map) { app.triggerHMIEvent(id, args) } @@ -130,38 +101,6 @@ abstract class RHMIEvent private constructor(open val app: RHMIApplication, open } } - class MockEvent(override val app: RHMIApplicationMock, override val id: Int): RHMIEvent(app, id) { - override fun asPopupEvent(): PopupEvent { - return app.events.computeIfWrongType(id) { - PopupEvent(app, id) - } - } - override fun asActionEvent(): ActionEvent { - return app.events.computeIfWrongType(id) { - ActionEvent(app, id) - } - } - override fun asNotificationIconEvent(): NotificationIconEvent { - return app.events.computeIfWrongType(id) { - NotificationIconEvent(app, id) - } - } - override fun asFocusEvent(): FocusEvent { - return app.events.computeIfWrongType(id) { - FocusEvent(app, id) - } - } - override fun asMultimediaInfoEvent(): MultimediaInfoEvent { - return app.events.computeIfWrongType(id) { - MultimediaInfoEvent(app, id) - } - } - override fun asStatusbarEvent(): StatusbarEvent { - return app.events.computeIfWrongType(id) { - StatusbarEvent(app, id) - } - } - } open fun asPopupEvent(): PopupEvent? { return this as? PopupEvent diff --git a/core/src/commonMain/kotlin/io/bimmergestalt/idriveconnectkit/rhmi/RHMIModel.kt b/core/src/commonMain/kotlin/io/bimmergestalt/idriveconnectkit/rhmi/RHMIModel.kt index 3d54840..d7b6a51 100644 --- a/core/src/commonMain/kotlin/io/bimmergestalt/idriveconnectkit/rhmi/RHMIModel.kt +++ b/core/src/commonMain/kotlin/io/bimmergestalt/idriveconnectkit/rhmi/RHMIModel.kt @@ -1,111 +1,56 @@ package io.bimmergestalt.idriveconnectkit.rhmi -import de.bmw.idrive.BMWRemoting -import de.bmw.idrive.BMWRemoting.RHMIDataTable -import de.bmw.idrive.BMWRemoting.RHMIResourceData -import io.bimmergestalt.idriveconnectkit.rhmi.mocking.RHMIApplicationMock -import io.bimmergestalt.idriveconnectkit.xmlutils.XMLUtils -import io.bimmergestalt.idriveconnectkit.xmlutils.getAttributesMap -import io.bimmergestalt.idriveconnectkit.xmlutils.getChildElements -import io.bimmergestalt.idriveconnectkit.xmlutils.getChildNamed -import org.w3c.dom.Node import kotlin.math.max import kotlin.math.min -abstract class RHMIModel private constructor(open val app: RHMIApplication, open val id: Int) { +/** + * These base classes just store their data locally + */ +abstract class RHMIModel protected constructor(val id: Int) { - companion object { - fun loadFromXML(app: RHMIApplication, node: Node): RHMIModel? { - val attrs = node.getAttributesMap() + companion object {} - val id = attrs["id"]?.toInt() ?: return null - - if (node.nodeName == "formatDataModel") { - val submodels = node.getChildNamed("models").getChildElements().mapNotNull { submodelNode -> - loadFromXML(app, submodelNode) - } - val model = FormatDataModel(app, id, submodels) - XMLUtils.unmarshalAttributes(model, attrs) - return model - } - - val model = when(node.nodeName) { - "textIdModel" -> TextIdModel(app, id) - "imageIdModel" -> ImageIdModel(app, id) - "raBoolModel" -> RaBoolModel(app, id) - "raDataModel" -> RaDataModel(app, id) - "raGaugeModel" -> RaGaugeModel(app, id) - "raImageModel" -> RaImageModel(app, id) - "raIntModel" -> RaIntModel(app, id) - "raListModel" -> RaListModel(app, id) - else -> null - } - - if (model != null) { - XMLUtils.unmarshalAttributes(model, attrs) - } - return model - } + open class TextIdModel(id: Int): RHMIModel(id) { + open var textId: Int = 0 } - - open class TextIdModel(override val app: RHMIApplication, override val id: Int): RHMIModel(app, id) { - var textId: Int - get() = (app.getModel(id) as? BMWRemoting.RHMIResourceIdentifier)?.id ?: 0 - set(value) { - val resource = BMWRemoting.RHMIResourceIdentifier(BMWRemoting.RHMIResourceType.TEXTID, value) - app.setModel(id, resource) - } + open class ImageIdModel(id: Int): RHMIModel(id) { + open var imageId: Int = 0 } - open class ImageIdModel(override val app: RHMIApplication, override val id: Int): RHMIModel(app, id) { - var imageId: Int - get() = (app.getModel(id) as? BMWRemoting.RHMIResourceIdentifier)?.id ?: 0 - set(value) { - val resource = BMWRemoting.RHMIResourceIdentifier(BMWRemoting.RHMIResourceType.IMAGEID, value) - app.setModel(id, resource) - } + open class RaBoolModel(id: Int): RHMIModel(id) { + open var value: Boolean = false } - class RaBoolModel(override val app: RHMIApplication, override val id: Int): RHMIModel(app, id) { - var value: Boolean - get() = app.getModel(id) as? Boolean ?: false - set(value) = app.setModel(id, value) - } - class RaDataModel(override val app: RHMIApplication, override val id: Int): RHMIModel(app, id) { + open class RaDataModel(id: Int): RHMIModel(id) { var modelType: String = "" - var value: String - get() = app.getModel(id) as? String ?: "" - set(value) = app.setModel(id, value) + open var value: String = "" } - open class RaIntModel(override val app: RHMIApplication, override val id: Int): RHMIModel(app, id) { - var value: Int - get() = app.getModel(id) as? Int ?: 0 - set(value) = app.setModel(id, value) + open class RaIntModel(id: Int): RHMIModel(id) { + open var value: Int = 0 } - class RaGaugeModel(override val app: RHMIApplication, override val id: Int): RaIntModel(app, id) { + + open class RaGaugeModel(id: Int, raIntModel: RaIntModel? = null): RHMIModel(id) { var modelType: String = "" var min: Int = 0 var max: Int = 100 var increment: Int = 1 + // has a value from RaIntModel + // so subclasses can pass in their own raIntModel to enable live functionality + private val raIntModel = raIntModel ?: RaIntModel(id) + var value: Int + get() = raIntModel.value + set(value) { raIntModel.value = value } } - class FormatDataModel(override val app: RHMIApplication, override val id: Int, val submodels: List): RHMIModel(app, id) { + open class FormatDataModel(id: Int, val submodels: List): RHMIModel(id) { var formatString: String = "" } - class RaImageModel(override val app: RHMIApplication, override val id: Int): RHMIModel(app, id) { - var value: ByteArray? - get() = (app.getModel(id) as? RHMIResourceData)?.data - set(value) { - if (value != null) { - val data = BMWRemoting.RHMIResourceData(BMWRemoting.RHMIResourceType.IMAGEDATA, value) - app.setModel(this.id, data) - } - } - + open class RaImageModel(id: Int): RHMIModel(id) { + open var value: ByteArray? = null } - class RaListModel(override val app: RHMIApplication, override val id: Int): RHMIModel(app, id) { + open class RaListModel(id: Int): RHMIModel(id) { var modelType: String = "" abstract class RHMIList(open val width: Int) { /** @@ -193,82 +138,9 @@ abstract class RHMIModel private constructor(open val app: RHMIApplication, open } } } - var value: RHMIList? - get() = _getValue() - set(value) { - if (value != null) { - setValue(value, value.startIndex, value.height, value.endIndex) - } - } - - fun setValue(data: RaListModel.RHMIList, startIndex: Int, numRows: Int, totalRows: Int) { - val table = BMWRemoting.RHMIDataTable(data.getWindow(startIndex, numRows), false, startIndex, numRows, totalRows, 0, data.width, data.width) - app.setModel(this.id, table) - } - private fun _getValue(): RHMIList? { - return (app.getModel(id) as? RHMIDataTable)?.let { - RHMIListConcrete(it.numColumns, startIndex = it.fromRow, endIndex = it.totalRows).apply { - addAll(it.data.toList()) - } - } - } + open var value: RHMIList? = null } - open class MockModel(override val app: RHMIApplicationMock, override val id: Int): RHMIModel(app, id) { - override fun asFormatDataModel(): FormatDataModel { - return app.models.computeIfWrongType(id) { - FormatDataModel(app, id, ArrayList()) - } - } - - override fun asImageIdModel(): ImageIdModel { - return app.models.computeIfWrongType(id) { - ImageIdModel(app, id) - } - } - - override fun asRaBoolModel(): RaBoolModel { - return app.models.computeIfWrongType(id) { - RaBoolModel(app, id) - } - } - - override fun asRaDataModel(): RaDataModel { - return app.models.computeIfWrongType(id) { - RaDataModel(app, id) - } - } - - override fun asRaGaugeModel(): RaGaugeModel { - return app.models.computeIfWrongType(id) { - RaGaugeModel(app, id) - } - } - - override fun asRaImageModel(): RaImageModel { - return app.models.computeIfWrongType(id) { - RaImageModel(app, id) - } - } - - override fun asRaIntModel(): RaIntModel { - return app.models.computeIfWrongType(id) { - RaIntModel(app, id) - } - } - - override fun asRaListModel(): RaListModel { - return app.models.computeIfWrongType(id) { - RaListModel(app, id) - } - } - - override fun asTextIdModel(): TextIdModel { - return app.models.computeIfWrongType(id) { - TextIdModel(app, id) - } - } - } open fun asFormatDataModel(): FormatDataModel? { return this as? FormatDataModel diff --git a/core/src/commonMain/kotlin/io/bimmergestalt/idriveconnectkit/rhmi/RHMIProperty.kt b/core/src/commonMain/kotlin/io/bimmergestalt/idriveconnectkit/rhmi/RHMIProperty.kt index c31fd3c..7a65c61 100644 --- a/core/src/commonMain/kotlin/io/bimmergestalt/idriveconnectkit/rhmi/RHMIProperty.kt +++ b/core/src/commonMain/kotlin/io/bimmergestalt/idriveconnectkit/rhmi/RHMIProperty.kt @@ -1,8 +1,5 @@ package io.bimmergestalt.idriveconnectkit.rhmi -import io.bimmergestalt.idriveconnectkit.xmlutils.* -import org.w3c.dom.Node - abstract class RHMIProperty(val id: Int, open var value: Any? = null) { enum class PropertyId(val id: Int) { @@ -34,38 +31,7 @@ abstract class RHMIProperty(val id: Int, open var value: Any? = null) { MODAL(56), } - companion object { - fun loadFromXML(app: RHMIApplication, componentId: Int, node: Node): RHMIProperty? { - val attrs = node.getAttributesMap() - - val id = attrs["id"]?.toInt() ?: return null - val value = attrs["value"]?.toIntOrNull() ?: - attrs["value"] ?: return null - - val condition = node.getChildNamed("condition") - val assignmentsNode = condition?.getChildNamed("assignments") - val assignments = if (assignmentsNode != null) assignmentMap(assignmentsNode) else null - return when (condition?.getAttribute("conditionType")) { - "LAYOUTBAG" -> LayoutBag(id, value, assignments as Map) - else -> AppProperty(app, componentId, id, value) - } - } - - fun assignmentMap(node: Node): Map { - /** Given the assignments node, return a map */ - val map = HashMap() - node.getChildElements().filter { it.nodeName == "assignment" }.forEach { - val attrs = it.getAttributesMap() - val id = attrs["conditionValue"]?.toIntOrNull() - val value = attrs["value"]?.toIntOrNull() ?: - attrs["value"] - if (id != null && value != null) { - map[id] = value - } - } - return map - } - } + companion object { } open fun getForLayout(layout: Int): Any? { return value diff --git a/core/src/commonMain/kotlin/io/bimmergestalt/idriveconnectkit/rhmi/RHMIState.kt b/core/src/commonMain/kotlin/io/bimmergestalt/idriveconnectkit/rhmi/RHMIState.kt index e6bbb09..fb86ea9 100644 --- a/core/src/commonMain/kotlin/io/bimmergestalt/idriveconnectkit/rhmi/RHMIState.kt +++ b/core/src/commonMain/kotlin/io/bimmergestalt/idriveconnectkit/rhmi/RHMIState.kt @@ -1,14 +1,6 @@ package io.bimmergestalt.idriveconnectkit.rhmi -import io.bimmergestalt.idriveconnectkit.rhmi.mocking.RHMIApplicationMock -import io.bimmergestalt.idriveconnectkit.xmlutils.XMLUtils -import io.bimmergestalt.idriveconnectkit.xmlutils.getAttributesMap -import io.bimmergestalt.idriveconnectkit.xmlutils.getChildElements -import io.bimmergestalt.idriveconnectkit.xmlutils.getChildNamed -import org.w3c.dom.Node -import java.util.HashMap - -abstract class RHMIState private constructor(open val app: RHMIApplication, open val id: Int) { +abstract class RHMIState protected constructor(open val app: RHMIApplication, open val id: Int) { val components: MutableMap = HashMap() val componentsList: MutableList = ArrayList() val optionComponentsList: MutableList = ArrayList() @@ -18,66 +10,7 @@ abstract class RHMIState private constructor(open val app: RHMIApplication, open return app.models[textModel] } - companion object { - fun loadFromXML(app: RHMIApplication, node: Node): RHMIState? { - val attrs = node.getAttributesMap() - - val id = attrs["id"]?.toInt() ?: return null - - val state = when (node.nodeName) { - "hmiState" -> PlainState(app, id) - "toolbarHmiState" -> ToolbarState(app, id) - "popupHmiState" -> PopupState(app, id) - "audioHmiState" -> AudioHmiState(app, id) - "calendarMonthHmiState" -> CalendarMonthState(app, id) - "calendarHmiState" -> CalendarState(app, id) - else -> null - } - - if (state != null) { - XMLUtils.unmarshalAttributes(state, attrs) - - node.getChildNamed("components").getChildElements().forEach { componentNode -> - val component = RHMIComponent.loadFromXML(app, componentNode) - if (component != null) { - state.components[component.id] = component - state.componentsList.add(component) - } - } - - val propertyNodes = node.getChildNamed("properties") - if (propertyNodes != null) { - propertyNodes.getChildElements().filter { it.nodeName == "property" }.forEach { - val property = RHMIProperty.loadFromXML(app, state.id, it) - if (property != null) - state.properties[property.id] = property - } - } - - if (state is ToolbarState) { - node.getChildNamed("toolbarComponents").getChildElements().forEach { componentNode -> - val component = RHMIComponent.loadFromXML(app, componentNode) - if (component is RHMIComponent.ToolbarButton) { - state.toolbarComponents[component.id] = component - state.toolbarComponentsList.add(component) - } - } - } - - val optionComponents = node.getChildNamed("optionComponents") - if (optionComponents != null) { - optionComponents.getChildElements().forEach { optionComponentNode -> - val component = RHMIComponent.loadFromXML(app, optionComponentNode) - if (component != null) { - state.optionComponentsList.add(component) - } - } - } - } - - return state - } - } + companion object { } fun setProperty(property: RHMIProperty.PropertyId, value: Any) { this.setProperty(property.id, value) @@ -247,39 +180,6 @@ abstract class RHMIState private constructor(open val app: RHMIApplication, open } class CalendarState(override val app: RHMIApplication, override val id: Int): RHMIState(app, id) - class MockState(override val app: RHMIApplicationMock, override val id: Int): RHMIState(app, id) { - override fun asPlainState(): PlainState { - return app.states.computeIfWrongType(id) { - PlainState(app, id) - } - } - override fun asPopupState(): PopupState { - return app.states.computeIfWrongType(id) { - PopupState(app, id) - } - } - override fun asToolbarState(): ToolbarState { - return app.states.computeIfWrongType(id) { - ToolbarState(app, id) - } - } - override fun asAudioState(): AudioHmiState { - return app.states.computeIfWrongType(id) { - AudioHmiState(app, id) - } - } - override fun asCalendarMonthState(): CalendarMonthState { - return app.states.computeIfWrongType(id) { - CalendarMonthState(app, id) - } - } - override fun asCalendarState(): CalendarState { - return app.states.computeIfWrongType(id) { - CalendarState(app, id) - } - } - } - open fun asPlainState(): PlainState? { return this as? PlainState } diff --git a/core/src/commonMain/kotlin/io/bimmergestalt/idriveconnectkit/rhmi/mocking/MockApplication.kt b/core/src/commonMain/kotlin/io/bimmergestalt/idriveconnectkit/rhmi/mocking/MockApplication.kt deleted file mode 100644 index 9d342a9..0000000 --- a/core/src/commonMain/kotlin/io/bimmergestalt/idriveconnectkit/rhmi/mocking/MockApplication.kt +++ /dev/null @@ -1,110 +0,0 @@ -package io.bimmergestalt.idriveconnectkit.rhmi.mocking - -import io.bimmergestalt.idriveconnectkit.rhmi.* - -class RHMIApplicationMock : RHMIApplication() { - /** Automatically instantiates elements of the application layout */ - override val models = MockModelMap(this) - override val actions = MockActionMap(this) - override val events = MockEventMap(this) - override val states = MockStateMap(this) - override val components = MockComponentMap(this) - val triggeredEvents = HashMap>() - - val modelData = HashMap() - val propertyData = HashMap>() - - - override fun setModel(modelId: Int, value: Any?) { - modelData[modelId] = value - } - override fun getModel(modelId: Int): Any? = modelData[modelId] - - override fun setProperty(componentId: Int, propertyId: Int, value: Any?) { - if (!propertyData.containsKey(componentId)) { - propertyData[componentId] = HashMap() - } - propertyData[componentId]!!.set(propertyId, value) - } - override fun getProperty(componentId: Int, propertyId: Int): Any? = propertyData[componentId]?.get(propertyId) - - override fun triggerHMIEvent(eventId: Int, args: Map) { - triggeredEvents[eventId] = args - } -} - -class MockModelMap(val app: RHMIApplicationMock) : HashMap() { - override fun get(key:Int):RHMIModel { - return super.get(key) ?: RHMIModel.MockModel(app, key) - } - - inline fun computeIfWrongType(id: Int, compute: (Int) -> R): R { - val existing = app.models[id] - return if (existing is R) { existing } - else { - compute(id).also { - app.models[id] = it - } - } - } -} -class MockActionMap(val app: RHMIApplicationMock) : HashMap() { - override fun get(key:Int):RHMIAction { - return super.get(key) ?: RHMIAction.MockAction(app, key) - } - - inline fun computeIfWrongType(id: Int, compute: (Int) -> R): R { - val existing = app.actions[id] - return if (existing is R) { existing } - else { - compute(id).also { - app.actions[id] = it - } - } - } -} -class MockEventMap(val app: RHMIApplicationMock) : HashMap() { - override fun get(key:Int):RHMIEvent { - return super.get(key) ?: RHMIEvent.MockEvent(app, key) - } - - inline fun computeIfWrongType(id: Int, compute: (Int) -> R): R { - val existing = app.events[id] - return if (existing is R) { existing } - else { - compute(id).also { - app.events[id] = it - } - } - } -} -class MockStateMap(val app: RHMIApplicationMock) : HashMap() { - override fun get(key:Int):RHMIState { - return super.get(key) ?: RHMIState.MockState(app, key) - } - - inline fun computeIfWrongType(id: Int, compute: (Int) -> R): R { - val existing = app.states[id] - return if (existing is R) { existing } - else { - compute(id).also { - app.states[id] = it - } - } - } -} -class MockComponentMap(val app: RHMIApplicationMock) : HashMap() { - override fun get(key:Int):RHMIComponent { - return super.get(key) ?: RHMIComponent.MockComponent(app, key) - } - - inline fun computeIfWrongType(id: Int, compute: (Int) -> R): R { - val existing = app.components[id] - return if (existing is R) { existing } - else { - compute(id).also { - app.components[id] = it - } - } - } -} \ No newline at end of file diff --git a/core/src/commonTest/kotlin/io/bimmergestalt/idriveconnectkit/TestRHMIApplication.kt b/core/src/commonTest/kotlin/io/bimmergestalt/idriveconnectkit/TestRHMIApplication.kt index 91f376d..b3f6499 100644 --- a/core/src/commonTest/kotlin/io/bimmergestalt/idriveconnectkit/TestRHMIApplication.kt +++ b/core/src/commonTest/kotlin/io/bimmergestalt/idriveconnectkit/TestRHMIApplication.kt @@ -3,6 +3,8 @@ package io.bimmergestalt.idriveconnectkit import junit.framework.TestCase.* import io.bimmergestalt.idriveconnectkit.rhmi.* import io.bimmergestalt.idriveconnectkit.rhmi.mocking.RHMIApplicationMock +import io.bimmergestalt.idriveconnectkit.rhmi.mocking.RHMIModelMock +import io.bimmergestalt.idriveconnectkit.rhmi.mocking.RHMIStateMock import org.junit.Test // Allow nested nullable access @@ -12,7 +14,7 @@ class TestRHMIApplication { @Test fun mockStates() { val app = RHMIApplicationMock() val state = app.states[10] - assertTrue(state is RHMIState.MockState) + assertTrue(state is RHMIStateMock) assertEquals(10, state.id) state.textModel = 12 @@ -32,7 +34,7 @@ class TestRHMIApplication { @Test fun mockModels() { val app = RHMIApplicationMock() val model = app.models[10] - assertTrue(model is RHMIModel.MockModel) + assertTrue(model is RHMIModelMock) assertEquals(10, model.id) val imageModel = model.asImageIdModel() as RHMIModel.ImageIdModel assertEquals(10, imageModel.id) diff --git a/core/src/jvmMain/kotlin/io/bimmergestalt/idriveconnectkit/rhmi/RHMIApplicationEtch.kt b/core/src/jvmMain/kotlin/io/bimmergestalt/idriveconnectkit/rhmi/RHMIApplicationEtch.kt new file mode 100644 index 0000000..2ddc20a --- /dev/null +++ b/core/src/jvmMain/kotlin/io/bimmergestalt/idriveconnectkit/rhmi/RHMIApplicationEtch.kt @@ -0,0 +1,61 @@ +package io.bimmergestalt.idriveconnectkit.rhmi + +import de.bmw.idrive.BMWRemoting +import de.bmw.idrive.BMWRemotingServer +import java.io.IOException + + +class RHMIApplicationEtch(val remoteServer: BMWRemotingServer, val rhmiHandle: Int) : RHMIApplication() { + /** Represents an application layout that is backed by a Car connection + * Most data is not retained, so if you want to read data back out, + * use RHMIApplicationConcrete or RHMIApplicationIdempotent + * */ + override val models = HashMap() + override val actions = HashMap() + override val events = HashMap() + override val states = HashMap() + override val components = HashMap() + + // remember a little bit of properties and small ints + val modelData = HashMap() + val propertyData = HashMap>() + + @Throws(IOException::class) + override fun setModel(modelId: Int, value: Any?) { + if (value is Int || value is BMWRemoting.RHMIResourceIdentifier) { + modelData[modelId] = value + } else { + modelData.remove(modelId) + } + if (ignoreUpdates) return + try { + this.remoteServer.rhmi_setData(this.rhmiHandle, modelId, value) + } catch (e: Exception) { + throw IOException(e) + } + } + override fun getModel(modelId: Int): Any? = modelData[modelId] + + @Throws(IOException::class) + override fun setProperty(componentId: Int, propertyId: Int, value: Any?) { + propertyData.getOrPut(componentId){HashMap()}[propertyId] = value + if (ignoreUpdates) return + val propertyValue = HashMap() + propertyValue[0] = value + try { + this.remoteServer.rhmi_setProperty(rhmiHandle, componentId, propertyId, propertyValue) + } catch (e: Exception) { + throw IOException(e) + } + } + override fun getProperty(componentId: Int, propertyId: Int): Any? = propertyData[componentId]?.get(propertyId) + + @Throws(IOException::class) + override fun triggerHMIEvent(eventId: Int, args: Map) { + try { + this.remoteServer.rhmi_triggerEvent(rhmiHandle, eventId, args) + } catch (e: Exception) { + throw IOException(e) + } + } +} \ No newline at end of file diff --git a/core/src/commonMain/kotlin/io/bimmergestalt/idriveconnectkit/rhmi/RHMIApplicationWrappers.kt b/core/src/jvmMain/kotlin/io/bimmergestalt/idriveconnectkit/rhmi/RHMIApplicationWrappers.kt similarity index 97% rename from core/src/commonMain/kotlin/io/bimmergestalt/idriveconnectkit/rhmi/RHMIApplicationWrappers.kt rename to core/src/jvmMain/kotlin/io/bimmergestalt/idriveconnectkit/rhmi/RHMIApplicationWrappers.kt index c7d191f..9bce364 100644 --- a/core/src/commonMain/kotlin/io/bimmergestalt/idriveconnectkit/rhmi/RHMIApplicationWrappers.kt +++ b/core/src/jvmMain/kotlin/io/bimmergestalt/idriveconnectkit/rhmi/RHMIApplicationWrappers.kt @@ -24,6 +24,7 @@ class RHMIApplicationIdempotent(val app: RHMIApplication): RHMIApplication(), RH val previouslySent = sentData.containsKey(modelId) val identical = when(model) { is RHMIModel.RaIntModel -> model.value == value + is RHMIModel.RaGaugeModel -> model.value == value is RHMIModel.RaDataModel -> model.value == value is RHMIModel.RaBoolModel -> model.value == value is RHMIModel.TextIdModel -> model.textId == (value as? RHMIResourceIdentifier)?.id @@ -35,6 +36,7 @@ class RHMIApplicationIdempotent(val app: RHMIApplication): RHMIApplication(), RH } val saved = when(model) { is RHMIModel.RaIntModel -> true + is RHMIModel.RaGaugeModel -> true is RHMIModel.RaDataModel -> true is RHMIModel.RaBoolModel -> true is RHMIModel.TextIdModel -> true diff --git a/core/src/jvmMain/kotlin/io/bimmergestalt/idriveconnectkit/rhmi/RHMIModelLive.kt b/core/src/jvmMain/kotlin/io/bimmergestalt/idriveconnectkit/rhmi/RHMIModelLive.kt new file mode 100644 index 0000000..97db831 --- /dev/null +++ b/core/src/jvmMain/kotlin/io/bimmergestalt/idriveconnectkit/rhmi/RHMIModelLive.kt @@ -0,0 +1,79 @@ +package io.bimmergestalt.idriveconnectkit.rhmi + +import de.bmw.idrive.BMWRemoting + +/** + * These models send to the underlying RHMIApplication + * and so are stored in jvmMain + */ +class RHMIModelLive { + open class TextIdModel(val app: RHMIApplication, id: Int): RHMIModel.TextIdModel(id) { + override var textId: Int + get() = (app.getModel(id) as? BMWRemoting.RHMIResourceIdentifier)?.id ?: 0 + set(value) { + val resource = BMWRemoting.RHMIResourceIdentifier(BMWRemoting.RHMIResourceType.TEXTID, value) + app.setModel(id, resource) + } + } + open class ImageIdModel(val app: RHMIApplication, id: Int): RHMIModel.ImageIdModel(id) { + override var imageId: Int + get() = (app.getModel(id) as? BMWRemoting.RHMIResourceIdentifier)?.id ?: 0 + set(value) { + val resource = BMWRemoting.RHMIResourceIdentifier(BMWRemoting.RHMIResourceType.IMAGEID, value) + app.setModel(id, resource) + } + } + + open class RaBoolModel(val app: RHMIApplication, id: Int): RHMIModel.RaBoolModel(id) { + override var value: Boolean + get() = app.getModel(id) as? Boolean ?: false + set(value) = app.setModel(id, value) + } + open class RaDataModel(val app: RHMIApplication, id: Int): RHMIModel.RaDataModel(id) { + override var value: String + get() = app.getModel(id) as? String ?: "" + set(value) = app.setModel(id, value) + } + open class RaIntModel(val app: RHMIApplication, id: Int): RHMIModel.RaIntModel(id) { + override var value: Int + get() = app.getModel(id) as? Int ?: 0 + set(value) = app.setModel(id, value) + } + open class RaGaugeModel(app: RHMIApplication, id: Int): RHMIModel.RaGaugeModel(id, RaIntModel(app, id)) + + open class FormatDataModel(app: RHMIApplication, id: Int, submodels: List): RHMIModel.FormatDataModel(id, submodels) { + } + + class RaImageModel(val app: RHMIApplication, id: Int): RHMIModel.RaImageModel(id) { + override var value: ByteArray? + get() = (app.getModel(id) as? BMWRemoting.RHMIResourceData)?.data + set(value) { + if (value != null) { + val data = BMWRemoting.RHMIResourceData(BMWRemoting.RHMIResourceType.IMAGEDATA, value) + app.setModel(this.id, data) + } + } + } + + class RaListModel(val app: RHMIApplication, id: Int): RHMIModel.RaListModel(id) { + override var value: RHMIList? + get() = _getValue() + set(value) { + if (value != null) { + setValue(value, value.startIndex, value.height, value.endIndex) + } + } + + fun setValue(data: RHMIList, startIndex: Int, numRows: Int, totalRows: Int) { + val table = BMWRemoting.RHMIDataTable(data.getWindow(startIndex, numRows), false, startIndex, numRows, totalRows, 0, data.width, data.width) + app.setModel(this.id, table) + } + private fun _getValue(): RHMIList? { + return (app.getModel(id) as? BMWRemoting.RHMIDataTable)?.let { + RHMIListConcrete(it.numColumns, startIndex = it.fromRow, endIndex = it.totalRows).apply { + addAll(it.data.toList()) + } + } + } + } +} \ No newline at end of file diff --git a/core/src/jvmMain/kotlin/io/bimmergestalt/idriveconnectkit/rhmi/deserialization/RHMIAction.kt b/core/src/jvmMain/kotlin/io/bimmergestalt/idriveconnectkit/rhmi/deserialization/RHMIAction.kt new file mode 100644 index 0000000..0632e19 --- /dev/null +++ b/core/src/jvmMain/kotlin/io/bimmergestalt/idriveconnectkit/rhmi/deserialization/RHMIAction.kt @@ -0,0 +1,38 @@ +package io.bimmergestalt.idriveconnectkit.rhmi.deserialization + +import io.bimmergestalt.idriveconnectkit.rhmi.RHMIAction +import io.bimmergestalt.idriveconnectkit.rhmi.RHMIApplication +import io.bimmergestalt.idriveconnectkit.xmlutils.XMLUtils +import io.bimmergestalt.idriveconnectkit.xmlutils.getAttributesMap +import io.bimmergestalt.idriveconnectkit.xmlutils.getChildElements +import io.bimmergestalt.idriveconnectkit.xmlutils.getChildNamed +import org.w3c.dom.Node + +fun RHMIAction.Companion.loadFromXML(app: RHMIApplication, node: Node): RHMIAction? { + val attrs = node.getAttributesMap() + + val id = attrs["id"]?.toInt() ?: return null + + if (node.nodeName == "combinedAction") { + val subactions = node.getChildNamed("actions").getChildElements().mapNotNull { subactionNode -> + loadFromXML(app, subactionNode) + } + val raAction = subactions.firstOrNull { it is RHMIAction.RAAction } as RHMIAction.RAAction? + val hmiAction = subactions.firstOrNull { it is RHMIAction.HMIAction } as RHMIAction.HMIAction? + val action = RHMIAction.CombinedAction(app, id, raAction, hmiAction) + XMLUtils.unmarshalAttributes(action, attrs) + return action + } + + val action = when (node.nodeName) { + "hmiAction" -> RHMIAction.HMIAction(app, id) + "raAction" -> RHMIAction.RAAction(app, id) + "linkAction" -> RHMIAction.LinkAction(app, id) + else -> null + } + + if (action != null) { + XMLUtils.unmarshalAttributes(action, attrs) + } + return action +} \ No newline at end of file diff --git a/core/src/jvmMain/kotlin/io/bimmergestalt/idriveconnectkit/rhmi/deserialization/RHMIApplication.kt b/core/src/jvmMain/kotlin/io/bimmergestalt/idriveconnectkit/rhmi/deserialization/RHMIApplication.kt new file mode 100644 index 0000000..282b4bc --- /dev/null +++ b/core/src/jvmMain/kotlin/io/bimmergestalt/idriveconnectkit/rhmi/deserialization/RHMIApplication.kt @@ -0,0 +1,83 @@ +package io.bimmergestalt.idriveconnectkit.rhmi.deserialization + +import io.bimmergestalt.idriveconnectkit.rhmi.RHMIAction +import io.bimmergestalt.idriveconnectkit.rhmi.RHMIApplication +import io.bimmergestalt.idriveconnectkit.rhmi.RHMIComponent +import io.bimmergestalt.idriveconnectkit.rhmi.RHMIEvent +import io.bimmergestalt.idriveconnectkit.rhmi.RHMIModel +import io.bimmergestalt.idriveconnectkit.rhmi.RHMIState +import io.bimmergestalt.idriveconnectkit.xmlutils.XMLUtils +import io.bimmergestalt.idriveconnectkit.xmlutils.getAttribute +import io.bimmergestalt.idriveconnectkit.xmlutils.getChildElements +import io.bimmergestalt.idriveconnectkit.xmlutils.getChildNamed +import org.w3c.dom.Document + +fun RHMIApplication.loadFromXML(description: String) { + this.loadFromXML(description.toByteArray()) +} + +fun RHMIApplication.loadFromXML(description: ByteArray) { + this.loadFromXML(XMLUtils.loadXML(description)) +} + +fun RHMIApplication.loadFromXML(description: Document) { + ignoreUpdates = true + description.getChildNamed("pluginApps").getChildElements().forEach { pluginAppNode -> + pluginAppNode.getChildNamed("models").getChildElements().forEach { modelNode -> + val model = RHMIModel.loadFromXML(this, modelNode) + if (model != null) { + models[model.id] = model + if (model is RHMIModel.FormatDataModel) { + model.submodels.forEach { models[it.id] = it } + } + } + } + pluginAppNode.getChildNamed("actions").getChildElements().forEach { actionNode -> + val action = RHMIAction.loadFromXML(this, actionNode) + if (action != null) { + actions[action.id] = action + if (action is RHMIAction.CombinedAction) { + if (action.raAction != null) actions[action.raAction.id] = action.raAction + if (action.hmiAction != null) actions[action.hmiAction.id] = action.hmiAction + } + } + } + pluginAppNode.getChildNamed("events").getChildElements().forEach { actionNode -> + val event = RHMIEvent.loadFromXML(this, actionNode) + if (event != null) { + events[event.id] = event + } + } + pluginAppNode.getChildNamed("hmiStates").getChildElements().forEach { stateNode -> + val state = RHMIState.loadFromXML(this, stateNode) + if (state != null) { + states[state.id] = state + components.putAll(state.components) + if (state is RHMIState.ToolbarState) { + components.putAll(state.toolbarComponents) + } + } + } + val entryButtonNode = pluginAppNode.getChildNamed("entryButton") + if (entryButtonNode != null) { + val component = RHMIComponent.loadFromXML(this, entryButtonNode) + if (component is RHMIComponent.EntryButton) { + pluginAppNode.getAttribute("applicationType")?.also { + component.applicationType = it + } + pluginAppNode.getAttribute("applicationWeight")?.toIntOrNull()?.also { + component.applicationWeight = it + } + components[component.id] = component + } + } + val instrumentClusterNode = pluginAppNode.getChildNamed("instrumentCluster") + if (instrumentClusterNode != null) { + val component = RHMIComponent.loadFromXML(this, instrumentClusterNode) + if (component != null) { + components[component.id] = component + } + } + } + ignoreUpdates = false +} \ No newline at end of file diff --git a/core/src/jvmMain/kotlin/io/bimmergestalt/idriveconnectkit/rhmi/deserialization/RHMIComponent.kt b/core/src/jvmMain/kotlin/io/bimmergestalt/idriveconnectkit/rhmi/deserialization/RHMIComponent.kt new file mode 100644 index 0000000..9b2a9bb --- /dev/null +++ b/core/src/jvmMain/kotlin/io/bimmergestalt/idriveconnectkit/rhmi/deserialization/RHMIComponent.kt @@ -0,0 +1,48 @@ +package io.bimmergestalt.idriveconnectkit.rhmi.deserialization + +import io.bimmergestalt.idriveconnectkit.rhmi.RHMIApplication +import io.bimmergestalt.idriveconnectkit.rhmi.RHMIComponent +import io.bimmergestalt.idriveconnectkit.rhmi.RHMIProperty +import io.bimmergestalt.idriveconnectkit.xmlutils.XMLUtils +import io.bimmergestalt.idriveconnectkit.xmlutils.getAttributesMap +import io.bimmergestalt.idriveconnectkit.xmlutils.getChildElements +import io.bimmergestalt.idriveconnectkit.xmlutils.getChildNamed +import org.w3c.dom.Node + +fun RHMIComponent.Companion.loadFromXML(app: RHMIApplication, node: Node): RHMIComponent? { + val attrs = node.getAttributesMap() + + val id = attrs["id"]?.toInt() ?: return null + + val component = when (node.nodeName) { + "separator" -> RHMIComponent.Separator(app, id) + "image" -> RHMIComponent.Image(app, id) + "label" -> RHMIComponent.Label(app, id) + "list" -> RHMIComponent.List(app, id) + "entryButton" -> RHMIComponent.EntryButton(app, id) + "instrumentCluster" -> RHMIComponent.InstrumentCluster(app, id) + "button" -> if (attrs["model"] == null) RHMIComponent.ToolbarButton( + app, + id + ) else RHMIComponent.Button(app, id) + "checkbox" -> RHMIComponent.Checkbox(app, id) + "gauge" -> RHMIComponent.Gauge(app, id) + "input" -> RHMIComponent.Input(app, id) + "calendarDay" -> RHMIComponent.CalendarDay(app, id) + else -> null + } + + if (component != null) { + XMLUtils.unmarshalAttributes(component, attrs) + + val propertyNodes = node.getChildNamed("properties") + if (propertyNodes != null) { + propertyNodes.getChildElements().filter { it.nodeName == "property" }.forEach { + val property = RHMIProperty.loadFromXML(app, component.id, it) + if (property != null) + component.properties[property.id] = property + } + } + } + return component +} \ No newline at end of file diff --git a/core/src/jvmMain/kotlin/io/bimmergestalt/idriveconnectkit/rhmi/deserialization/RHMIEvent.kt b/core/src/jvmMain/kotlin/io/bimmergestalt/idriveconnectkit/rhmi/deserialization/RHMIEvent.kt new file mode 100644 index 0000000..e0d007b --- /dev/null +++ b/core/src/jvmMain/kotlin/io/bimmergestalt/idriveconnectkit/rhmi/deserialization/RHMIEvent.kt @@ -0,0 +1,30 @@ +package io.bimmergestalt.idriveconnectkit.rhmi.deserialization + +import io.bimmergestalt.idriveconnectkit.rhmi.RHMIApplication +import io.bimmergestalt.idriveconnectkit.rhmi.RHMIEvent +import io.bimmergestalt.idriveconnectkit.xmlutils.XMLUtils +import io.bimmergestalt.idriveconnectkit.xmlutils.getAttributesMap +import org.w3c.dom.Node + + +fun RHMIEvent.Companion.loadFromXML(app: RHMIApplication, node: Node): RHMIEvent? { + val attrs = node.getAttributesMap() + + val id = attrs["id"]?.toInt() ?: return null + + val event = when (node.nodeName) { + "popupEvent" -> RHMIEvent.PopupEvent(app, id) + "actionEvent" -> RHMIEvent.ActionEvent(app, id) + "notificationEvent" -> RHMIEvent.NotificationEvent(app, id) + "notificationIconEvent" -> RHMIEvent.NotificationIconEvent(app, id) + "focusEvent" -> RHMIEvent.FocusEvent(app, id) + "multimediaInfoEvent" -> RHMIEvent.MultimediaInfoEvent(app, id) + "statusbarEvent" -> RHMIEvent.StatusbarEvent(app, id) + else -> null + } + + if (event != null) { + XMLUtils.unmarshalAttributes(event, attrs) + } + return event +} \ No newline at end of file diff --git a/core/src/jvmMain/kotlin/io/bimmergestalt/idriveconnectkit/rhmi/deserialization/RHMIModel.kt b/core/src/jvmMain/kotlin/io/bimmergestalt/idriveconnectkit/rhmi/deserialization/RHMIModel.kt new file mode 100644 index 0000000..3f43e24 --- /dev/null +++ b/core/src/jvmMain/kotlin/io/bimmergestalt/idriveconnectkit/rhmi/deserialization/RHMIModel.kt @@ -0,0 +1,42 @@ +package io.bimmergestalt.idriveconnectkit.rhmi.deserialization + +import io.bimmergestalt.idriveconnectkit.rhmi.RHMIApplication +import io.bimmergestalt.idriveconnectkit.rhmi.RHMIModel +import io.bimmergestalt.idriveconnectkit.rhmi.RHMIModelLive +import io.bimmergestalt.idriveconnectkit.xmlutils.XMLUtils +import io.bimmergestalt.idriveconnectkit.xmlutils.getAttributesMap +import io.bimmergestalt.idriveconnectkit.xmlutils.getChildElements +import io.bimmergestalt.idriveconnectkit.xmlutils.getChildNamed +import org.w3c.dom.Node + +fun RHMIModel.Companion.loadFromXML(app: RHMIApplication, node: Node): RHMIModel? { + val attrs = node.getAttributesMap() + + val id = attrs["id"]?.toInt() ?: return null + + if (node.nodeName == "formatDataModel") { + val submodels = node.getChildNamed("models").getChildElements().mapNotNull { submodelNode -> + loadFromXML(app, submodelNode) + } + val model = RHMIModelLive.FormatDataModel(app, id, submodels) + XMLUtils.unmarshalAttributes(model, attrs) + return model + } + + val model = when(node.nodeName) { + "textIdModel" -> RHMIModelLive.TextIdModel(app, id) + "imageIdModel" -> RHMIModelLive.ImageIdModel(app, id) + "raBoolModel" -> RHMIModelLive.RaBoolModel(app, id) + "raDataModel" -> RHMIModelLive.RaDataModel(app, id) + "raGaugeModel" -> RHMIModelLive.RaGaugeModel(app, id) + "raImageModel" -> RHMIModelLive.RaImageModel(app, id) + "raIntModel" -> RHMIModelLive.RaIntModel(app, id) + "raListModel" -> RHMIModelLive.RaListModel(app, id) + else -> null + } + + if (model != null) { + XMLUtils.unmarshalAttributes(model, attrs) + } + return model +} \ No newline at end of file diff --git a/core/src/jvmMain/kotlin/io/bimmergestalt/idriveconnectkit/rhmi/deserialization/RHMIProperty.kt b/core/src/jvmMain/kotlin/io/bimmergestalt/idriveconnectkit/rhmi/deserialization/RHMIProperty.kt new file mode 100644 index 0000000..6270ed3 --- /dev/null +++ b/core/src/jvmMain/kotlin/io/bimmergestalt/idriveconnectkit/rhmi/deserialization/RHMIProperty.kt @@ -0,0 +1,40 @@ +package io.bimmergestalt.idriveconnectkit.rhmi.deserialization + +import io.bimmergestalt.idriveconnectkit.rhmi.RHMIApplication +import io.bimmergestalt.idriveconnectkit.rhmi.RHMIProperty +import io.bimmergestalt.idriveconnectkit.xmlutils.getAttribute +import io.bimmergestalt.idriveconnectkit.xmlutils.getAttributesMap +import io.bimmergestalt.idriveconnectkit.xmlutils.getChildElements +import io.bimmergestalt.idriveconnectkit.xmlutils.getChildNamed +import org.w3c.dom.Node + +fun RHMIProperty.Companion.loadFromXML(app: RHMIApplication, componentId: Int, node: Node): RHMIProperty? { + val attrs = node.getAttributesMap() + + val id = attrs["id"]?.toInt() ?: return null + val value = attrs["value"]?.toIntOrNull() ?: + attrs["value"] ?: return null + + val condition = node.getChildNamed("condition") + val assignmentsNode = condition?.getChildNamed("assignments") + val assignments = if (assignmentsNode != null) assignmentMap(assignmentsNode) else null + return when (condition?.getAttribute("conditionType")) { + "LAYOUTBAG" -> RHMIProperty.LayoutBag(id, value, assignments as Map) + else -> RHMIProperty.AppProperty(app, componentId, id, value) + } +} + +fun assignmentMap(node: Node): Map { + /** Given the assignments node, return a map */ + val map = HashMap() + node.getChildElements().filter { it.nodeName == "assignment" }.forEach { + val attrs = it.getAttributesMap() + val id = attrs["conditionValue"]?.toIntOrNull() + val value = attrs["value"]?.toIntOrNull() ?: + attrs["value"] + if (id != null && value != null) { + map[id] = value + } + } + return map +} \ No newline at end of file diff --git a/core/src/jvmMain/kotlin/io/bimmergestalt/idriveconnectkit/rhmi/deserialization/RHMIState.kt b/core/src/jvmMain/kotlin/io/bimmergestalt/idriveconnectkit/rhmi/deserialization/RHMIState.kt new file mode 100644 index 0000000..8a62a3e --- /dev/null +++ b/core/src/jvmMain/kotlin/io/bimmergestalt/idriveconnectkit/rhmi/deserialization/RHMIState.kt @@ -0,0 +1,71 @@ +package io.bimmergestalt.idriveconnectkit.rhmi.deserialization + +import io.bimmergestalt.idriveconnectkit.rhmi.RHMIApplication +import io.bimmergestalt.idriveconnectkit.rhmi.RHMIComponent +import io.bimmergestalt.idriveconnectkit.rhmi.RHMIProperty +import io.bimmergestalt.idriveconnectkit.rhmi.RHMIState +import io.bimmergestalt.idriveconnectkit.xmlutils.XMLUtils +import io.bimmergestalt.idriveconnectkit.xmlutils.getAttributesMap +import io.bimmergestalt.idriveconnectkit.xmlutils.getChildElements +import io.bimmergestalt.idriveconnectkit.xmlutils.getChildNamed +import org.w3c.dom.Node + + +fun RHMIState.Companion.loadFromXML(app: RHMIApplication, node: Node): RHMIState? { + val attrs = node.getAttributesMap() + + val id = attrs["id"]?.toInt() ?: return null + + val state = when (node.nodeName) { + "hmiState" -> RHMIState.PlainState(app, id) + "toolbarHmiState" -> RHMIState.ToolbarState(app, id) + "popupHmiState" -> RHMIState.PopupState(app, id) + "audioHmiState" -> RHMIState.AudioHmiState(app, id) + "calendarMonthHmiState" -> RHMIState.CalendarMonthState(app, id) + "calendarHmiState" -> RHMIState.CalendarState(app, id) + else -> null + } + + if (state != null) { + XMLUtils.unmarshalAttributes(state, attrs) + + node.getChildNamed("components").getChildElements().forEach { componentNode -> + val component = RHMIComponent.loadFromXML(app, componentNode) + if (component != null) { + state.components[component.id] = component + state.componentsList.add(component) + } + } + + val propertyNodes = node.getChildNamed("properties") + if (propertyNodes != null) { + propertyNodes.getChildElements().filter { it.nodeName == "property" }.forEach { + val property = RHMIProperty.loadFromXML(app, state.id, it) + if (property != null) + state.properties[property.id] = property + } + } + + if (state is RHMIState.ToolbarState) { + node.getChildNamed("toolbarComponents").getChildElements().forEach { componentNode -> + val component = RHMIComponent.loadFromXML(app, componentNode) + if (component is RHMIComponent.ToolbarButton) { + state.toolbarComponents[component.id] = component + state.toolbarComponentsList.add(component) + } + } + } + + val optionComponents = node.getChildNamed("optionComponents") + if (optionComponents != null) { + optionComponents.getChildElements().forEach { optionComponentNode -> + val component = RHMIComponent.loadFromXML(app, optionComponentNode) + if (component != null) { + state.optionComponentsList.add(component) + } + } + } + } + + return state +} \ No newline at end of file diff --git a/core/src/jvmMain/kotlin/io/bimmergestalt/idriveconnectkit/rhmi/mocking/MockApplication.kt b/core/src/jvmMain/kotlin/io/bimmergestalt/idriveconnectkit/rhmi/mocking/MockApplication.kt new file mode 100644 index 0000000..28ab6d9 --- /dev/null +++ b/core/src/jvmMain/kotlin/io/bimmergestalt/idriveconnectkit/rhmi/mocking/MockApplication.kt @@ -0,0 +1,34 @@ +package io.bimmergestalt.idriveconnectkit.rhmi.mocking + +import io.bimmergestalt.idriveconnectkit.rhmi.* + +class RHMIApplicationMock : RHMIApplication() { + /** Automatically instantiates elements of the application layout */ + override val models = MockModelMap(this) + override val actions = MockActionMap(this) + override val events = MockEventMap(this) + override val states = MockStateMap(this) + override val components = MockComponentMap(this) + val triggeredEvents = HashMap>() + + val modelData = HashMap() + val propertyData = HashMap>() + + + override fun setModel(modelId: Int, value: Any?) { + modelData[modelId] = value + } + override fun getModel(modelId: Int): Any? = modelData[modelId] + + override fun setProperty(componentId: Int, propertyId: Int, value: Any?) { + if (!propertyData.containsKey(componentId)) { + propertyData[componentId] = HashMap() + } + propertyData[componentId]!!.set(propertyId, value) + } + override fun getProperty(componentId: Int, propertyId: Int): Any? = propertyData[componentId]?.get(propertyId) + + override fun triggerHMIEvent(eventId: Int, args: Map) { + triggeredEvents[eventId] = args + } +} \ No newline at end of file diff --git a/core/src/jvmMain/kotlin/io/bimmergestalt/idriveconnectkit/rhmi/mocking/RHMIActionMock.kt b/core/src/jvmMain/kotlin/io/bimmergestalt/idriveconnectkit/rhmi/mocking/RHMIActionMock.kt new file mode 100644 index 0000000..db7df12 --- /dev/null +++ b/core/src/jvmMain/kotlin/io/bimmergestalt/idriveconnectkit/rhmi/mocking/RHMIActionMock.kt @@ -0,0 +1,45 @@ +package io.bimmergestalt.idriveconnectkit.rhmi.mocking + +import io.bimmergestalt.idriveconnectkit.rhmi.RHMIAction + +class MockActionMap(val app: RHMIApplicationMock) : HashMap() { + override fun get(key:Int):RHMIAction { + return super.get(key) ?: RHMIActionMock(app, key) + } + + inline fun computeIfWrongType(id: Int, compute: (Int) -> R): R { + val existing = app.actions[id] + return if (existing is R) { existing } + else { + compute(id).also { + app.actions[id] = it + } + } + } +} + +class RHMIActionMock(override val app: RHMIApplicationMock, override val id: Int): RHMIAction(app, id) { + override fun asCombinedAction(): CombinedAction { + return app.actions.computeIfWrongType(id) { + CombinedAction(app, id, RAAction(app, id), HMIAction(app, id)) + } + } + + override fun asHMIAction(): HMIAction { + return app.actions.computeIfWrongType(id) { + HMIAction(app, id) + } + } + + override fun asRAAction(): RAAction { + return app.actions.computeIfWrongType(id) { + RAAction(app, id) + } + } + + override fun asLinkAction(): LinkAction { + return app.actions.computeIfWrongType(id) { + LinkAction(app, id) + } + } +} \ No newline at end of file diff --git a/core/src/jvmMain/kotlin/io/bimmergestalt/idriveconnectkit/rhmi/mocking/RHMIComponentMock.kt b/core/src/jvmMain/kotlin/io/bimmergestalt/idriveconnectkit/rhmi/mocking/RHMIComponentMock.kt new file mode 100644 index 0000000..c4106be --- /dev/null +++ b/core/src/jvmMain/kotlin/io/bimmergestalt/idriveconnectkit/rhmi/mocking/RHMIComponentMock.kt @@ -0,0 +1,82 @@ +package io.bimmergestalt.idriveconnectkit.rhmi.mocking + +import io.bimmergestalt.idriveconnectkit.rhmi.RHMIComponent + +class MockComponentMap(val app: RHMIApplicationMock) : HashMap() { + override fun get(key:Int):RHMIComponent { + return super.get(key) ?: RHMIComponentMock(app, key) + } + + inline fun computeIfWrongType(id: Int, compute: (Int) -> R): R { + val existing = app.components[id] + return if (existing is R) { existing } + else { + compute(id).also { + app.components[id] = it + } + } + } +} + +class RHMIComponentMock(override val app: RHMIApplicationMock, override val id: Int): RHMIComponent(app, id) { + override fun asSeparator(): Separator { + return app.components.computeIfWrongType(id) { + Separator(app, id) + } + } + override fun asImage(): Image { + return app.components.computeIfWrongType(id) { + Image(app, id) + } + } + override fun asLabel(): Label { + return app.components.computeIfWrongType(id) { + Label(app, id) + } + } + override fun asList(): List { + return app.components.computeIfWrongType(id) { + List(app, id) + } + } + override fun asEntryButton(): EntryButton { + return app.components.computeIfWrongType(id) { + EntryButton(app, id) + } + } + override fun asInstrumentCluster(): InstrumentCluster { + return app.components.computeIfWrongType(id) { + InstrumentCluster(app, id) + } + } + override fun asToolbarButton(): ToolbarButton { + return app.components.computeIfWrongType(id) { + ToolbarButton(app, id) + } + } + override fun asButton(): Button { + return app.components.computeIfWrongType(id) { + Button(app, id) + } + } + override fun asCheckbox(): Checkbox { + return app.components.computeIfWrongType(id) { + Checkbox(app, id) + } + } + override fun asGauge(): Gauge { + return app.components.computeIfWrongType(id) { + Gauge(app, id) + } + } + override fun asInput(): Input { + return app.components.computeIfWrongType(id) { + Input(app, id) + } + } + override fun asCalendarDay(): CalendarDay { + return app.components.computeIfWrongType(id) { + CalendarDay(app, id) + } + } +} diff --git a/core/src/jvmMain/kotlin/io/bimmergestalt/idriveconnectkit/rhmi/mocking/RHMIEventMock.kt b/core/src/jvmMain/kotlin/io/bimmergestalt/idriveconnectkit/rhmi/mocking/RHMIEventMock.kt new file mode 100644 index 0000000..75c0d00 --- /dev/null +++ b/core/src/jvmMain/kotlin/io/bimmergestalt/idriveconnectkit/rhmi/mocking/RHMIEventMock.kt @@ -0,0 +1,52 @@ +package io.bimmergestalt.idriveconnectkit.rhmi.mocking + +import io.bimmergestalt.idriveconnectkit.rhmi.RHMIEvent + +class MockEventMap(val app: RHMIApplicationMock) : HashMap() { + override fun get(key:Int):RHMIEvent { + return super.get(key) ?: RHMIEventMock(app, key) + } + + inline fun computeIfWrongType(id: Int, compute: (Int) -> R): R { + val existing = app.events[id] + return if (existing is R) { existing } + else { + compute(id).also { + app.events[id] = it + } + } + } +} + +class RHMIEventMock(override val app: RHMIApplicationMock, override val id: Int): RHMIEvent(app, id) { + override fun asPopupEvent(): PopupEvent { + return app.events.computeIfWrongType(id) { + PopupEvent(app, id) + } + } + override fun asActionEvent(): ActionEvent { + return app.events.computeIfWrongType(id) { + ActionEvent(app, id) + } + } + override fun asNotificationIconEvent(): NotificationIconEvent { + return app.events.computeIfWrongType(id) { + NotificationIconEvent(app, id) + } + } + override fun asFocusEvent(): FocusEvent { + return app.events.computeIfWrongType(id) { + FocusEvent(app, id) + } + } + override fun asMultimediaInfoEvent(): MultimediaInfoEvent { + return app.events.computeIfWrongType(id) { + MultimediaInfoEvent(app, id) + } + } + override fun asStatusbarEvent(): StatusbarEvent { + return app.events.computeIfWrongType(id) { + StatusbarEvent(app, id) + } + } +} \ No newline at end of file diff --git a/core/src/jvmMain/kotlin/io/bimmergestalt/idriveconnectkit/rhmi/mocking/RHMIModelMock.kt b/core/src/jvmMain/kotlin/io/bimmergestalt/idriveconnectkit/rhmi/mocking/RHMIModelMock.kt new file mode 100644 index 0000000..31b2b70 --- /dev/null +++ b/core/src/jvmMain/kotlin/io/bimmergestalt/idriveconnectkit/rhmi/mocking/RHMIModelMock.kt @@ -0,0 +1,76 @@ +package io.bimmergestalt.idriveconnectkit.rhmi.mocking + +import io.bimmergestalt.idriveconnectkit.rhmi.RHMIModel +import io.bimmergestalt.idriveconnectkit.rhmi.RHMIModelLive + +class MockModelMap(val app: RHMIApplicationMock) : HashMap() { + override fun get(key:Int):RHMIModel { + return super.get(key) ?: RHMIModelMock(app, key) + } + + inline fun computeIfWrongType(id: Int, compute: (Int) -> R): R { + val existing = app.models[id] + return if (existing is R) { existing } + else { + compute(id).also { + app.models[id] = it + } + } + } +} + +open class RHMIModelMock(val app: RHMIApplicationMock, id: Int): RHMIModel(id) { + override fun asFormatDataModel(): FormatDataModel { + return app.models.computeIfWrongType(id) { + RHMIModelLive.FormatDataModel(app, id, ArrayList()) + } + } + + override fun asImageIdModel(): ImageIdModel { + return app.models.computeIfWrongType(id) { + RHMIModelLive.ImageIdModel(app, id) + } + } + + override fun asRaBoolModel(): RaBoolModel { + return app.models.computeIfWrongType(id) { + RHMIModelLive.RaBoolModel(app, id) + } + } + + override fun asRaDataModel(): RaDataModel { + return app.models.computeIfWrongType(id) { + RHMIModelLive.RaDataModel(app, id) + } + } + + override fun asRaGaugeModel(): RaGaugeModel { + return app.models.computeIfWrongType(id) { + RHMIModelLive.RaGaugeModel(app, id) + } + } + + override fun asRaImageModel(): RaImageModel { + return app.models.computeIfWrongType(id) { + RHMIModelLive.RaImageModel(app, id) + } + } + + override fun asRaIntModel(): RaIntModel { + return app.models.computeIfWrongType(id) { + RHMIModelLive.RaIntModel(app, id) + } + } + + override fun asRaListModel(): RaListModel { + return app.models.computeIfWrongType(id) { + RHMIModelLive.RaListModel(app, id) + } + } + + override fun asTextIdModel(): TextIdModel { + return app.models.computeIfWrongType(id) { + RHMIModelLive.TextIdModel(app, id) + } + } +} \ No newline at end of file diff --git a/core/src/jvmMain/kotlin/io/bimmergestalt/idriveconnectkit/rhmi/mocking/RHMIStateMock.kt b/core/src/jvmMain/kotlin/io/bimmergestalt/idriveconnectkit/rhmi/mocking/RHMIStateMock.kt new file mode 100644 index 0000000..70fcc57 --- /dev/null +++ b/core/src/jvmMain/kotlin/io/bimmergestalt/idriveconnectkit/rhmi/mocking/RHMIStateMock.kt @@ -0,0 +1,53 @@ +package io.bimmergestalt.idriveconnectkit.rhmi.mocking + +import io.bimmergestalt.idriveconnectkit.rhmi.RHMIState + + +class MockStateMap(val app: RHMIApplicationMock) : HashMap() { + override fun get(key:Int):RHMIState { + return super.get(key) ?: RHMIStateMock(app, key) + } + + inline fun computeIfWrongType(id: Int, compute: (Int) -> R): R { + val existing = app.states[id] + return if (existing is R) { existing } + else { + compute(id).also { + app.states[id] = it + } + } + } +} + +class RHMIStateMock(override val app: RHMIApplicationMock, override val id: Int): RHMIState(app, id) { + override fun asPlainState(): PlainState { + return app.states.computeIfWrongType(id) { + PlainState(app, id) + } + } + override fun asPopupState(): PopupState { + return app.states.computeIfWrongType(id) { + PopupState(app, id) + } + } + override fun asToolbarState(): ToolbarState { + return app.states.computeIfWrongType(id) { + ToolbarState(app, id) + } + } + override fun asAudioState(): AudioHmiState { + return app.states.computeIfWrongType(id) { + AudioHmiState(app, id) + } + } + override fun asCalendarMonthState(): CalendarMonthState { + return app.states.computeIfWrongType(id) { + CalendarMonthState(app, id) + } + } + override fun asCalendarState(): CalendarState { + return app.states.computeIfWrongType(id) { + CalendarState(app, id) + } + } +} diff --git a/core/src/commonMain/kotlin/io/bimmergestalt/idriveconnectkit/xmlutils/XMLUtils.kt b/core/src/jvmMain/kotlin/io/bimmergestalt/idriveconnectkit/xmlutils/XMLUtils.kt similarity index 100% rename from core/src/commonMain/kotlin/io/bimmergestalt/idriveconnectkit/xmlutils/XMLUtils.kt rename to core/src/jvmMain/kotlin/io/bimmergestalt/idriveconnectkit/xmlutils/XMLUtils.kt diff --git a/core/src/commonTest/kotlin/io/bimmergestalt/idriveconnectkit/TestRHMIApplicationIdempotent.kt b/core/src/jvmTest/kotlin/io/bimmergestalt/idriveconnectkit/TestRHMIApplicationIdempotent.kt similarity index 93% rename from core/src/commonTest/kotlin/io/bimmergestalt/idriveconnectkit/TestRHMIApplicationIdempotent.kt rename to core/src/jvmTest/kotlin/io/bimmergestalt/idriveconnectkit/TestRHMIApplicationIdempotent.kt index 0d2dcee..cdd83b7 100644 --- a/core/src/commonTest/kotlin/io/bimmergestalt/idriveconnectkit/TestRHMIApplicationIdempotent.kt +++ b/core/src/jvmTest/kotlin/io/bimmergestalt/idriveconnectkit/TestRHMIApplicationIdempotent.kt @@ -13,14 +13,14 @@ class TestRHMIApplicationIdempotent { @Before fun setUp() { - subject.models[1] = RHMIModel.RaDataModel(subject, 1) - subject.models[2] = RHMIModel.RaIntModel(subject, 2) - subject.models[3] = RHMIModel.RaGaugeModel(subject, 3) - subject.models[4] = RHMIModel.RaBoolModel(subject, 4) - subject.models[5] = RHMIModel.TextIdModel(subject, 5) - subject.models[6] = RHMIModel.ImageIdModel(subject, 6) - subject.models[7] = RHMIModel.RaImageModel(subject, 7) - subject.models[8] = RHMIModel.RaListModel(subject, 8) + subject.models[1] = RHMIModelLive.RaDataModel(subject, 1) + subject.models[2] = RHMIModelLive.RaIntModel(subject, 2) + subject.models[3] = RHMIModelLive.RaGaugeModel(subject, 3) + subject.models[4] = RHMIModelLive.RaBoolModel(subject, 4) + subject.models[5] = RHMIModelLive.TextIdModel(subject, 5) + subject.models[6] = RHMIModelLive.ImageIdModel(subject, 6) + subject.models[7] = RHMIModelLive.RaImageModel(subject, 7) + subject.models[8] = RHMIModelLive.RaListModel(subject, 8) subject.components[10] = RHMIComponent.Label(subject, 10) } diff --git a/core/src/commonTest/kotlin/io/bimmergestalt/idriveconnectkit/TestRHMIApplicationSynchronized.kt b/core/src/jvmTest/kotlin/io/bimmergestalt/idriveconnectkit/TestRHMIApplicationSynchronized.kt similarity index 95% rename from core/src/commonTest/kotlin/io/bimmergestalt/idriveconnectkit/TestRHMIApplicationSynchronized.kt rename to core/src/jvmTest/kotlin/io/bimmergestalt/idriveconnectkit/TestRHMIApplicationSynchronized.kt index 77301ce..0c6bef5 100644 --- a/core/src/commonTest/kotlin/io/bimmergestalt/idriveconnectkit/TestRHMIApplicationSynchronized.kt +++ b/core/src/jvmTest/kotlin/io/bimmergestalt/idriveconnectkit/TestRHMIApplicationSynchronized.kt @@ -19,8 +19,8 @@ class TestRHMIApplicationSynchronized { @Before fun setUp() { - backing.models[0] = RHMIModel.RaDataModel(backing, 0) - subject.models[1] = RHMIModel.RaDataModel(subject, 1) + backing.models[0] = RHMIModelLive.RaDataModel(backing, 0) + subject.models[1] = RHMIModelLive.RaDataModel(subject, 1) subject.components[10] = RHMIComponent.Label(subject, 10) } diff --git a/core/src/commonTest/kotlin/io/bimmergestalt/idriveconnectkit/TestXMLParsing.kt b/core/src/jvmTest/kotlin/io/bimmergestalt/idriveconnectkit/TestXMLParsing.kt similarity index 99% rename from core/src/commonTest/kotlin/io/bimmergestalt/idriveconnectkit/TestXMLParsing.kt rename to core/src/jvmTest/kotlin/io/bimmergestalt/idriveconnectkit/TestXMLParsing.kt index c8ae6f9..3f25dfb 100644 --- a/core/src/commonTest/kotlin/io/bimmergestalt/idriveconnectkit/TestXMLParsing.kt +++ b/core/src/jvmTest/kotlin/io/bimmergestalt/idriveconnectkit/TestXMLParsing.kt @@ -2,6 +2,7 @@ package io.bimmergestalt.idriveconnectkit import de.bmw.idrive.BMWRemoting import io.bimmergestalt.idriveconnectkit.rhmi.* +import io.bimmergestalt.idriveconnectkit.rhmi.deserialization.loadFromXML import io.bimmergestalt.idriveconnectkit.rhmi.mocking.RHMIApplicationMock import io.bimmergestalt.idriveconnectkit.xmlutils.XMLUtils import io.bimmergestalt.idriveconnectkit.xmlutils.getChildNamed diff --git a/core/src/commonTest/resources/ui_layout.xml b/core/src/jvmTest/resources/ui_layout.xml similarity index 100% rename from core/src/commonTest/resources/ui_layout.xml rename to core/src/jvmTest/resources/ui_layout.xml