Skip to content

Commit

Permalink
chore: Add operators for generated VSS models
Browse files Browse the repository at this point in the history
These add a lot of convenience to the usage of manipulating values
inside these models. No one has to remember the correct copy method
now.
  • Loading branch information
Chrylo committed Oct 23, 2023
1 parent 9400913 commit f6698a3
Show file tree
Hide file tree
Showing 7 changed files with 215 additions and 58 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ import org.eclipse.kuksa.testapp.databroker.model.ConnectionInfo
import org.eclipse.kuksa.testapp.databroker.viewmodel.ConnectionViewModel
import org.eclipse.kuksa.testapp.databroker.viewmodel.ConnectionViewModel.*
import org.eclipse.kuksa.testapp.extension.compose.Headline
import org.eclipse.kuksa.testapp.extension.compose.rememberCountdown
import org.eclipse.kuksa.testapp.extension.compose.RememberCountdown
import org.eclipse.kuksa.testapp.extension.fetchFileName
import org.eclipse.kuksa.testapp.preferences.ConnectionInfoRepository

Expand Down Expand Up @@ -216,7 +216,7 @@ fun DataBrokerConnection(viewModel: ConnectionViewModel) {
onClick = { },
modifier = Modifier.requiredWidth(MinimumButtonWidth),
) {
val timeout by rememberCountdown(initialMillis = viewModel.connectionTimeoutMillis)
val timeout by RememberCountdown(initialMillis = viewModel.connectionTimeoutMillis)

@Suppress("MagicNumber") // To seconds
val timeoutSeconds = timeout / 1000
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ import kotlinx.coroutines.delay
import kotlinx.coroutines.isActive

@Composable
fun rememberCountdown(
fun RememberCountdown(
initialMillis: Long,
step: Long = 1000,
): MutableState<Long> {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -165,7 +165,7 @@ class DataBrokerConnection internal constructor(
initialSubscriptionUpdates[vssPath] = true
val isInitialSubscriptionComplete = initialSubscriptionUpdates.values.all { it }
if (isInitialSubscriptionComplete) {
Log.d(TAG, "Initial update for subscribed property complete: $vssPath - $updatedValue")
Log.d(TAG, "Update for subscribed property complete: $vssPath - $updatedValue")
observer.onSpecificationChanged(updatedVssSpecification)
}
}
Expand Down Expand Up @@ -252,14 +252,14 @@ class DataBrokerConnection internal constructor(
*/
suspend fun update(
property: Property,
updatedDatapoint: Datapoint,
datapoint: Datapoint,
): SetResponse {
Log.d(TAG, "updateProperty() called with: updatedProperty = $property")
Log.d(TAG, "updateProperty() called with: updatedProperty = $property, datapoint: $datapoint")
return withContext(dispatcher) {
val blockingStub = VALGrpc.newBlockingStub(managedChannel)
val dataEntry = Types.DataEntry.newBuilder()
.setPath(property.vssPath)
.setValue(updatedDatapoint)
.setValue(datapoint)
.build()
val entryUpdate = KuksaValV1.EntryUpdate.newBuilder()
.setEntry(dataEntry)
Expand All @@ -280,8 +280,8 @@ class DataBrokerConnection internal constructor(
/**
* Only a [VssProperty] can be updated because they have an actual value. When provided with any parent
* [VssSpecification] then this [update] method will find all [VssProperty] children and updates them instead.
* Compared to [update] with only one [Property] and [Datapoint], here multiple [SetResponse] will be returned
* because a [VssSpecification] may consists of multiple values which may need to be updated.
* Compared to [update] with only one [Property] and [Datapoint], here multiple [SetResponse] will be returned
* because a [VssSpecification] may consists of multiple values which may need to be updated.
*
* @throws DataBrokerException in case the connection to the DataBroker is no longer active
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,18 +38,26 @@ import org.eclipse.kuksa.vsscore.model.variableName
* ```
*
* A deep copy is necessary for a nested history tree with at least two generations. The VssWindowChildLockEngaged
* is replaced inside VssCabin where this again is replaced inside VssVehicle.
*
* @param generation the generation to start copying with starting from the [VssSpecification] to [deepCopy]
* @return a copy where every heir in the given [changedHeritageLine] is replaced with a another copy
* is replaced inside VssCabin where this again is replaced inside VssVehicle. Use the [generation] to start copying
* from the [VssSpecification] to the [deepCopy]. Returns a copy where every heir in the given [changedHeritage] is
* replaced with a another copy
*/
fun <T : VssSpecification> T.deepCopy(changedHeritageLine: List<VssSpecification>, generation: Int = 0): T {
if (generation == changedHeritageLine.size) { // Reached the end, use the changed VssProperty
@Suppress("performance:SpreadOperator")
fun <T : VssSpecification> T.deepCopy(generation: Int = 0, vararg changedHeritage: VssSpecification): T {
if (generation == changedHeritage.size) { // Reached the end, use the changed VssProperty
return this
}

val childSpecification = changedHeritageLine[generation]
val childCopy = childSpecification.deepCopy(changedHeritageLine, generation + 1)
// Create the missing link between this (VssSpecification) and the given property (VssSpecifications inbetween)
var heritageLine = changedHeritage
if (changedHeritage.size == 1) {
heritageLine = findHeritageLine(changedHeritage.first(), true)
.toTypedArray()
.ifEmpty { changedHeritage }
}

val childSpecification = heritageLine[generation]
val childCopy = childSpecification.deepCopy(generation + 1, *heritageLine)
val parameterNameToChild = mapOf(childSpecification.variableName to childCopy)

return copy(parameterNameToChild)
Expand All @@ -58,12 +66,10 @@ fun <T : VssSpecification> T.deepCopy(changedHeritageLine: List<VssSpecification
/**
* Creates a copy of a [VssProperty] where the [VssProperty.value] is changed to the given [Datapoint].
*
* @param datapoint the [Datapoint.value_] is converted to the correct datatype depending on the [VssProperty.value]
* @return a copy of the [VssProperty] with the updated [VssProperty.value]
*
* @throws [NoSuchElementException] if the class has no "copy" method
* @throws [IllegalArgumentException] if the copied types do not match
*/
@Suppress("UNCHECKED_CAST")
fun <T : Any> VssProperty<T>.copy(datapoint: Datapoint): VssProperty<T> {
with(datapoint) {
val value: Any = when (valueCase) {
Expand Down Expand Up @@ -106,21 +112,26 @@ fun <T : Any> VssProperty<T>.copy(datapoint: Datapoint): VssProperty<T> {
null -> throw NoSuchFieldException("Could not convert available value: $value to type: ${value::class}")
}

val valueMap = mapOf("value" to value)
return this@copy.copy(valueMap)
// Value must be T
return this@copy.copy(value as T)
}
}

/**
* Calls the generated copy method of the data class for the [VssProperty] and returns a new copy with the new [value].
*/
fun <T : Any> VssProperty<T>.copy(value: T): VssProperty<T> {
val valueMap = mapOf("value" to value)
return this@copy.copy(valueMap)
}

/**
* Creates a copy of the [VssSpecification] where the heir with a matching [vssPath] is replaced with the
* [updatedValue].
*
* @param vssPath which is used to find the correct heir in the [VssSpecification]
* @param updatedValue which will be updated inside the matching [VssProperty]
* @param consideredHeritage the heritage of the [VssSpecification] which is considered for searching. The default
* will always generate the up to date heritage of the current [VssSpecification]. For performance reason it may make
* sense to cache the input and reuse the [Collection] here.
* @return a copy where the heir with the matching [vssPath] is replaced with a another copy
*
* @throws [NoSuchElementException] if the class has no "copy" method
* @throws [IllegalArgumentException] if the copied types do not match
Expand All @@ -137,17 +148,38 @@ fun <T : VssSpecification> T.copy(
.find { it.vssPath == vssPath } ?: return this

val updatedVssProperty = vssProperty.copy(updatedValue)
val relevantChildren = findHeritageLine(vssProperty).toMutableList()

// Replace the last specification (Property) with the changed one
val updatedVssSpecification: T = if (relevantChildren.isNotEmpty()) {
relevantChildren.removeLast()
relevantChildren.add(updatedVssProperty)
// Same property with no heirs, no deep copy is needed
if (this.vssPath == updatedVssProperty.vssPath) return updatedVssProperty as T

this.deepCopy(relevantChildren)
} else {
updatedVssProperty as T // The property must be T since no children are available
}
return deepCopy(0, updatedVssProperty)
}

return updatedVssSpecification
/**
* Convenience operator for [copy] with a value [T].
*/
operator fun <T : Any> VssProperty<T>.plusAssign(value: T) {
copy(value)
}

/**
* Convenience operator for [copy] with a value [T].
*/
operator fun <T : Any> VssProperty<T>.plus(value: T): VssProperty<T> {
return copy(value)
}

/**
* Convenience operator for [copy] with a [Boolean] value which will be inverted.
*/
operator fun VssProperty<Boolean>.not(): VssProperty<Boolean> {
return copy(!value)
}

/**
* Convenience operator for [deepCopy] with a [VssProperty]. It will return the [VssSpecification] with the updated
* [VssProperty].
*/
operator fun <T : VssSpecification, V : Any> T.plus(property: VssProperty<V>): T {
return deepCopy(0, property)
}
Original file line number Diff line number Diff line change
Expand Up @@ -25,26 +25,92 @@ import org.eclipse.kuksa.vsscore.model.VssProperty
import org.eclipse.kuksa.vsscore.model.VssSpecification
import kotlin.reflect.KClass

data class VssVehicle(
val driver: VssDriver = VssDriver(),
val passenger: VssPassenger = VssPassenger(),
val body: VssBody = VssBody(),
override val uuid: String = "Vehicle",
override val vssPath: String = "Vehicle",
override val description: String = "High-level vehicle data.",
override val type: String = "branch",
override val comment: String = "",
) : VssSpecification {
override val children: Set<VssSpecification>
get() = setOf(driver, passenger, body)
}

data class VssBody(
override val uuid: String = "Body",
override val vssPath: String = "Vehicle.Body",
override val description: String = "All body components.",
override val type: String = "branch",
override val comment: String = "",
) : VssSpecification {
override val parentClass: KClass<*>
get() = VssVehicle::class
}

data class VssDriver(
val heartRate: VssHeartRate = VssHeartRate(),
val isEyesOnRoad: VssIsEyesOnRoad = VssIsEyesOnRoad(),
override val uuid: String = "Driver",
override val vssPath: String = "Vehicle.Driver",
override val description: String = "Driver data.",
override val type: String = "branch",
override val comment: String = "",
) : VssSpecification {
override val children: Set<VssSpecification>
get() = setOf(heartRate)
get() = setOf(heartRate, isEyesOnRoad)
override val parentClass: KClass<*>
get() = VssVehicle::class

data class VssHeartRate(
override val uuid: String = "Driver HeartRate",
override val vssPath: String = "Vehicle.Driver.HeartRate",
override val description: String = "Heart rate of the driver.",
override val type: String = "sensor",
override val comment: String = "",
override val value: Int = 100,
) : VssProperty<Int> {
override val parentClass: KClass<*>
get() = VssDriver::class
}

data class VssIsEyesOnRoad(
override val uuid: String = "Driver IsEyesOnRoad",
override val vssPath: String = "Vehicle.Driver.IsEyesOnRoad",
override val description: String = "Has driver the eyes on road or not?",
override val type: String = "sensor",
override val comment: String = "",
override val value: Boolean = true,
) : VssProperty<Boolean> {
override val parentClass: KClass<*>
get() = VssDriver::class
}
}

data class VssHeartRate(
override val uuid: String = "HeartRate",
override val vssPath: String = "Vehicle.Driver.HeartRate",
override val description: String = "Heart rate of the driver.",
override val type: String = "sensor",
data class VssPassenger(
val heartRate: VssHeartRate = VssHeartRate(),
override val uuid: String = "Passenger",
override val vssPath: String = "Vehicle.Passenger",
override val description: String = "Passenger data",
override val type: String = "branch",
override val comment: String = "",
override val value: Int = 100,
) : VssProperty<Int> {
override val parentClass: KClass<*>?
get() = VssDriver::class
) : VssSpecification {
override val children: Set<VssSpecification>
get() = setOf(heartRate)
override val parentClass: KClass<*>
get() = VssVehicle::class

data class VssHeartRate(
override val uuid: String = "Passenger HeartRate",
override val vssPath: String = "Vehicle.Passenger.HeartRate",
override val description: String = "Heart rate of the Passenger.",
override val type: String = "sensor",
override val comment: String = "",
override val value: Int = 80,
) : VssProperty<Int> {
override val parentClass: KClass<*>
get() = VssPassenger::class
}
}
Loading

0 comments on commit f6698a3

Please sign in to comment.