Skip to content

Commit

Permalink
feature(VssProcessor): Add dataType to VssSignal
Browse files Browse the repository at this point in the history
VssNodeSpecModel:
- Adds now an experimental annotation for some data types so warnings
  are properly ignored.
- The generation code of the VssSignal now iterates through the member
  properties of the interface and generates the correct
  implementation.

The VssHeartRate for integration tests is currently the only
VssSignal which has a different dataType (UInt) than the value type
(Int).
  • Loading branch information
Chrylo committed Mar 18, 2024
1 parent 9bd7e1a commit 461eda0
Show file tree
Hide file tree
Showing 10 changed files with 189 additions and 88 deletions.
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 @@ -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)
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}!")
}
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 @@ -92,8 +92,8 @@ fun <T : Any> VssSignal<T>.copy(datapoint: Datapoint): VssSignal<T> {
BOOL -> bool
INT32 -> int32
INT64 -> int64
UINT32 -> uint32.toUInt()
UINT64 -> uint64.toULong()
UINT32 -> uint32
UINT64 -> uint64
FLOAT -> float
DOUBLE -> double
STRING_ARRAY -> stringArray.valuesList
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 this@copy.copy(valueMap)
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 @@ -78,16 +78,16 @@ class VssNodeCopyTest : BehaviorSpec({
}

and("a changed invalid DataPoint") {
val newValue = 50
val datapoint = Types.Datapoint.newBuilder().setUint32(newValue).build()
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") {
exception.message shouldStartWith "argument type mismatch"
val signalName = driverHeartRate::class.simpleName
exception.message shouldStartWith "$signalName copy parameters do not match"
}
}
}
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
* 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

0 comments on commit 461eda0

Please sign in to comment.