Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add dataType property to VssSignal #104

Merged
merged 10 commits into from
Mar 19, 2024
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,8 @@
import org.eclipse.kuksa.connectivity.databroker.DataBrokerConnection;
Chrylo marked this conversation as resolved.
Show resolved Hide resolved
import org.eclipse.kuksa.connectivity.databroker.DataBrokerConnector;
import org.eclipse.kuksa.connectivity.databroker.listener.DisconnectListener;
import org.eclipse.kuksa.connectivity.databroker.listener.VssPathListener;
import org.eclipse.kuksa.connectivity.databroker.listener.VssNodeListener;
import org.eclipse.kuksa.connectivity.databroker.listener.VssPathListener;
import org.eclipse.kuksa.connectivity.databroker.request.FetchRequest;
import org.eclipse.kuksa.connectivity.databroker.request.SubscribeRequest;
import org.eclipse.kuksa.connectivity.databroker.request.UpdateRequest;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,9 @@

package org.eclipse.kuksa.extension

import kotlin.reflect.KParameter
import kotlin.reflect.full.instanceParameter
import kotlin.reflect.full.memberFunctions
import kotlin.reflect.full.valueParameters

/**
* Uses reflection to create a copy with any constructor parameter which matches the given [paramToValue] map.
Expand All @@ -37,16 +37,20 @@ internal fun <T : Any> T.copy(paramToValue: Map<String, Any?> = emptyMap()): T {
val copyFunction = instanceClass::memberFunctions.get().first { it.name == "copy" }
val instanceParameter = copyFunction.instanceParameter ?: return this

val valueArgs = copyFunction.parameters
.filter { parameter ->
parameter.kind == KParameter.Kind.VALUE
}.mapNotNull { parameter ->
val valueArgs = copyFunction.valueParameters
.mapNotNull { parameter ->
paramToValue[parameter.name]?.let { value -> parameter to value }
}

val parameterToInstance = mapOf(instanceParameter to this)
val parameterToValue = parameterToInstance + valueArgs
val copy = copyFunction.callBy(parameterToValue) ?: this

val copy: Any
try {
copy = copyFunction.callBy(parameterToValue) ?: this
} catch (e: IllegalArgumentException) {
throw IllegalArgumentException("${this::class.simpleName} copy parameters do not match: $paramToValue", e)
}

return copy as T
}
Original file line number Diff line number Diff line change
Expand Up @@ -35,14 +35,16 @@ val Types.Metadata.valueType: ValueCase
get() = dataType.dataPointValueCase

/**
* Converts the [VssSignal.value] into a [Datapoint] object.
* Converts the [VssSignal.value] into a [Datapoint] object. The [VssSignal.dataType] is used to derive the correct
* [ValueCase].
*
* @throws IllegalArgumentException if the [VssSignal] could not be converted to a [Datapoint].
*/
@OptIn(ExperimentalUnsignedTypes::class)
val <T : Any> VssSignal<T>.datapoint: Datapoint
get() {
val stringValue = value.toString()
return when (value::class) {
return when (dataType) {
String::class -> ValueCase.STRING.createDatapoint(stringValue)
Boolean::class -> ValueCase.BOOL.createDatapoint(stringValue)
Float::class -> ValueCase.FLOAT.createDatapoint(stringValue)
Expand All @@ -54,6 +56,8 @@ val <T : Any> VssSignal<T>.datapoint: Datapoint
IntArray::class -> ValueCase.INT32_ARRAY.createDatapoint(stringValue)
BooleanArray::class -> ValueCase.BOOL_ARRAY.createDatapoint(stringValue)
LongArray::class -> ValueCase.INT64_ARRAY.createDatapoint(stringValue)
Chrylo marked this conversation as resolved.
Show resolved Hide resolved
FloatArray::class -> ValueCase.FLOAT_ARRAY.createDatapoint(stringValue)
UIntArray::class -> ValueCase.UINT32_ARRAY.createDatapoint(stringValue)

else -> throw IllegalArgumentException("Could not create datapoint for the value class: ${value::class}!")
Chrylo marked this conversation as resolved.
Show resolved Hide resolved
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ import org.eclipse.kuksa.vsscore.model.VssSignal
import org.eclipse.kuksa.vsscore.model.findHeritageLine
import org.eclipse.kuksa.vsscore.model.heritage
import org.eclipse.kuksa.vsscore.model.variableName
import kotlin.reflect.full.memberProperties
import kotlin.reflect.full.declaredMemberProperties

/**
* Creates a copy of the [VssNode] where the whole [VssNode.findHeritageLine] is replaced
Expand Down Expand Up @@ -136,11 +136,12 @@ fun <T : Any> VssSignal<T>.copy(datapoint: Datapoint): VssSignal<T> {
* Calls the generated copy method of the data class for the [VssSignal] and returns a new copy with the new [value].
*
* @throws [IllegalArgumentException] if the copied types do not match.
* @throws [NoSuchElementException] if no copy method was found for the class.
* @throws [NoSuchElementException] if no copy method nor [valuePropertyName] was found for the class.
*/
fun <T : Any> VssSignal<T>.copy(value: T): VssSignal<T> {
val memberProperties = VssSignal::class.memberProperties
val firstPropertyName = memberProperties.first().name
@JvmOverloads
fun <T : Any> VssSignal<T>.copy(value: T, valuePropertyName: String = "value"): VssSignal<T> {
val memberProperties = VssSignal::class.declaredMemberProperties
val firstPropertyName = memberProperties.first { it.name == valuePropertyName }.name
val valueMap = mapOf(firstPropertyName to value)

return [email protected](valueMap)
Expand Down Expand Up @@ -179,7 +180,7 @@ fun <T : VssNode> T.copy(
// region Operators

/**
* Convenience operator for [deepCopy] with a [VssNode]. It will return the [VssNode] with the updated
* Convenience operator for [deepCopy] with a [VssNode]. It will return the parent [VssNode] with the updated child
* [VssNode].
*
* @throws [IllegalArgumentException] if the copied types do not match.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ import org.eclipse.kuksa.connectivity.databroker.request.SubscribeRequest
import org.eclipse.kuksa.connectivity.databroker.request.UpdateRequest
import org.eclipse.kuksa.connectivity.databroker.request.VssNodeFetchRequest
import org.eclipse.kuksa.connectivity.databroker.request.VssNodeSubscribeRequest
import org.eclipse.kuksa.connectivity.databroker.request.VssNodeUpdateRequest
import org.eclipse.kuksa.mocking.FriendlyVssNodeListener
import org.eclipse.kuksa.proto.v1.KuksaValV1
import org.eclipse.kuksa.proto.v1.Types
Expand Down Expand Up @@ -152,22 +153,36 @@ class DataBrokerConnectionTest : BehaviorSpec({
and("A VssNode") {
val vssDriver = VssDriver()

`when`("Fetching the node") {
and("A default HeartRate") {
val newHeartRateValue = 60
val datapoint = Types.Datapoint.newBuilder().setUint32(newHeartRateValue).build()
val defaultUpdateRequest = UpdateRequest(vssDriver.heartRate.vssPath, datapoint)

and("The initial value is different from the default for a child") {
val newHeartRateValue = 60
val datapoint = Types.Datapoint.newBuilder().setUint32(newHeartRateValue).build()
val updateRequest = UpdateRequest(vssDriver.heartRate.vssPath, datapoint)
dataBrokerConnection.update(defaultUpdateRequest)

dataBrokerConnection.update(updateRequest)
`when`("Fetching the node") {

val fetchRequest = VssNodeFetchRequest(vssDriver)
val updatedDriver = dataBrokerConnection.fetch(fetchRequest)
and("The initial value is different from the default for a child") {
val fetchRequest = VssNodeFetchRequest(vssDriver)
val updatedDriver = dataBrokerConnection.fetch(fetchRequest)

then("Every child node has been updated with the correct value") {
val heartRate = updatedDriver.heartRate
then("Every child node has been updated with the correct value") {
val heartRate = updatedDriver.heartRate

heartRate.value shouldBe newHeartRateValue
heartRate.value shouldBe newHeartRateValue
}
}
}

`when`("Updating the node with an invalid value") {
val invalidHeartRate = VssDriver.VssHeartRate(-5) // UInt on DataBroker side
val vssNodeUpdateRequest = VssNodeUpdateRequest(invalidHeartRate)
dataBrokerConnection.update(vssNodeUpdateRequest)

val fetchRequest = VssNodeFetchRequest(invalidHeartRate)
val fetchedVssHeartRate = dataBrokerConnection.fetch(fetchRequest)
then("the updated value should be ignored") {
fetchedVssHeartRate.value shouldBe 60
Chrylo marked this conversation as resolved.
Show resolved Hide resolved
}
}
}
Expand Down Expand Up @@ -202,10 +217,10 @@ class DataBrokerConnectionTest : BehaviorSpec({
}
}

and("Any subscribed node was changed") {
and("Any subscribed uInt node was changed") {
val newHeartRateValue = 50
val datapoint = Types.Datapoint.newBuilder().setUint32(newHeartRateValue).build()
val updateRequest = UpdateRequest(vssDriver.heartRate.vssPath, datapoint)
val newVssHeartRate = VssDriver.VssHeartRate(newHeartRateValue)
val updateRequest = VssNodeUpdateRequest(newVssHeartRate)

dataBrokerConnection.update(updateRequest)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,9 @@ data class VssDriver @JvmOverloads constructor(
data class VssHeartRate @JvmOverloads constructor(
override val `value`: Int = 0,
) : VssSignal<Int> {
override val dataType: KClass<*>
get() = UInt::class

override val comment: String
get() = ""

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,21 @@ class VssNodeCopyTest : BehaviorSpec({
}
}

and("a changed invalid DataPoint") {
val datapoint = Types.Datapoint.newBuilder().setBool(false).build()

`when`("a copy is done") {
val exception = shouldThrow<IllegalArgumentException> {
driverHeartRate.copy(datapoint)
}

then("it should throw an IllegalArgumentException") {
val signalName = driverHeartRate::class.simpleName
exception.message shouldStartWith "$signalName copy parameters do not match"
}
}
}

and("a changed DataPoint") {
val newValue = 50
val datapoint = Types.Datapoint.newBuilder().setInt32(newValue).build()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,9 @@ data class VssPassenger(
override val comment: String = "",
override val value: Int = 80,
) : VssSignal<Int> {
override val dataType: KClass<*>
get() = UInt::class

override val parentClass: KClass<*>
get() = VssPassenger::class
}
Expand Down
2 changes: 1 addition & 1 deletion samples/src/main/java/com/example/sample/JavaActivity.java
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,8 @@
import org.eclipse.kuksa.connectivity.databroker.DataBrokerConnection;
import org.eclipse.kuksa.connectivity.databroker.DataBrokerConnector;
import org.eclipse.kuksa.connectivity.databroker.listener.DisconnectListener;
import org.eclipse.kuksa.connectivity.databroker.listener.VssPathListener;
import org.eclipse.kuksa.connectivity.databroker.listener.VssNodeListener;
import org.eclipse.kuksa.connectivity.databroker.listener.VssPathListener;
import org.eclipse.kuksa.connectivity.databroker.request.FetchRequest;
import org.eclipse.kuksa.connectivity.databroker.request.SubscribeRequest;
import org.eclipse.kuksa.connectivity.databroker.request.UpdateRequest;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -82,11 +82,33 @@ interface VssBranch : VssNode {
* Some [VssNode] may have an additional [value] property. These are children [VssSignal] which do not have other
* children.
*/
interface VssSignal<T : Any> : VssNode {
interface VssSignal<out T : Any> : VssNode {
/**
* A primitive type value.
*/
val value: T

/**
* The VSS data type which is compatible with the data broker. This may differ from the [value] type because
Chrylo marked this conversation as resolved.
Show resolved Hide resolved
* Java compatibility needs to be ensured and inline classes like [UInt] (Kotlin) are not known to Java.
*
* ### Example
* Vehicle.Driver.HeartRate:
* datatype: uint16
*
* generates -->
*
* public data class VssHeartRate (
* override val `value`: Int = 0,
* ) : VssSignal<Int> {
* override val dataType: KClass<*>
* get() = UInt:class
* }
*
* To ensure java compatibility [UInt] is not used here for Kotlin (inline class).
*/
val dataType: KClass<*>
get() = value::class
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,9 @@ data class VssDriver(
override val comment: String = "",
override val value: Int = 100,
) : VssSignal<Int> {
override val dataType: KClass<*>
get() = UInt::class

override val parentClass: KClass<*>
get() = VssDriver::class
}
Expand All @@ -93,6 +96,9 @@ data class VssPassenger(
override val comment: String = "",
override val value: Int = 80,
) : VssSignal<Int> {
override val dataType: KClass<*>
get() = UInt::class

override val parentClass: KClass<*>
get() = VssPassenger::class
}
Expand Down
Loading
Loading