Skip to content

Commit

Permalink
chore: Extract YamlParser
Browse files Browse the repository at this point in the history
- Some cleanup + renaming
  • Loading branch information
Chrylo committed Sep 25, 2023
1 parent 597e719 commit 2d6ee49
Show file tree
Hide file tree
Showing 9 changed files with 137 additions and 128 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,7 @@ import org.eclipse.kuksa.testapp.databroker.viewmodel.TopAppBarViewModel
import org.eclipse.kuksa.testapp.databroker.viewmodel.TopAppBarViewModel.DataBrokerMode
import org.eclipse.kuksa.testapp.databroker.viewmodel.VSSPropertiesViewModel
import org.eclipse.kuksa.testapp.databroker.viewmodel.VssSpecificationsViewModel
import org.eclipse.kuksa.testapp.extension.view.LargeDropdownMenu
import org.eclipse.kuksa.testapp.extension.view.LazyDropdownMenu
import org.eclipse.kuksa.testapp.extension.view.SimpleExposedDropdownMenuBox
import org.eclipse.kuksa.testapp.model.ConnectionInfo
import org.eclipse.kuksa.testapp.ui.theme.KuksaAppAndroidTheme
Expand Down Expand Up @@ -413,7 +413,7 @@ fun DataBrokerSpecifications(viewModel: VssSpecificationsViewModel) {
Headline(name = "Specifications")

var selectedIndex by remember { mutableStateOf(0) }
LargeDropdownMenu(
LazyDropdownMenu(
modifier = Modifier
.padding(start = DefaultEdgePadding, end = DefaultEdgePadding),
items = viewModel.specifications,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,7 @@ fun <T> SimpleExposedDropdownMenuBox(
}

@Composable
fun LargeDropdownMenuItem(
fun LazyDropdownMenuItem(
text: String,
selected: Boolean,
enabled: Boolean,
Expand Down Expand Up @@ -116,19 +116,19 @@ fun LargeDropdownMenuItem(

@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun <T> LargeDropdownMenu(
fun <T> LazyDropdownMenu(
modifier: Modifier = Modifier,
enabled: Boolean = true,
label: String = "",
items: List<T>,
selectedIndex: Int = -1,
onItemSelected: (index: Int, item: T) -> Unit,
itemToString: (T) -> String = { it.toString() },
drawItem: @Composable (T, Boolean, Boolean, () -> Unit) -> Unit = { item, selected, itemEnabled, onClick ->
LargeDropdownMenuItem(
nextItem: @Composable (T, Boolean, () -> Unit) -> Unit = { item, selected, onClick ->
LazyDropdownMenuItem(
text = itemToString(item),
selected = selected,
enabled = itemEnabled,
enabled = true,
onClick = onClick,
)
},
Expand Down Expand Up @@ -165,6 +165,7 @@ fun <T> LargeDropdownMenu(
shape = RoundedCornerShape(12.dp),
) {
val listState = rememberLazyListState()

if (selectedIndex >= 0) {
LaunchedEffect("ScrollToSelected") {
listState.scrollToItem(index = selectedIndex)
Expand All @@ -174,7 +175,7 @@ fun <T> LargeDropdownMenu(
LazyColumn(modifier = Modifier.fillMaxWidth(), state = listState) {
itemsIndexed(items) { index, item ->
val selectedItem = index == selectedIndex
drawItem(item, selectedItem, true) {
nextItem(item, selectedItem) {
onItemSelected(index, item)
expanded = false
}
Expand Down
4 changes: 3 additions & 1 deletion config/detekt/config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ build:

style:
MagicNumber:
# rule is to sensitive (e.g. you can't define a for loop without using const for from, to and stepSize)
# Rule is to sensitive (e.g. you can't define a for loop without using const for from, to and stepSize)
ignoreRanges: true
ignoreEnums: true
ignorePropertyDeclaration: true # Compose
Expand All @@ -18,10 +18,12 @@ style:
active: false
ReturnCount:
excludeGuardClauses: true
# It is fine to have TO-DO statements
ForbiddenComment:
active: false

exceptions:
# Sometimes you want to bundle multiple exceptions to be caught the same way
TooGenericExceptionCaught:
active: false

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -116,7 +116,7 @@ class DataBrokerConnection internal constructor(
@JvmOverloads
fun <T : VssSpecification> subscribe(
specification: T,
fields: List<Types.Field> = listOf(Types.Field.FIELD_VALUE, Types.Field.FIELD_METADATA),
fields: List<Types.Field> = listOf(Types.Field.FIELD_VALUE),
observer: VssSpecificationObserver<T>,
) {
val vssPathToVssProperty = specification.heritage
Expand Down Expand Up @@ -195,7 +195,7 @@ class DataBrokerConnection internal constructor(
@JvmOverloads
suspend fun <T : VssSpecification> fetchSpecification(
specification: T,
fields: List<Types.Field> = listOf(Types.Field.FIELD_VALUE, Types.Field.FIELD_METADATA),
fields: List<Types.Field> = listOf(Types.Field.FIELD_VALUE),
): T {
return withContext(dispatcher) {
try {
Expand Down
21 changes: 0 additions & 21 deletions vss-processor/proguard-rules.pro

This file was deleted.

4 changes: 0 additions & 4 deletions vss-processor/src/main/AndroidManifest.xml

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
package org.eclipse.kuksa.vssprocessor

import org.eclipse.kuksa.vssprocessor.VssDefinitionProcessor.VssSpecificationElement
import java.io.File
import kotlin.reflect.KMutableProperty
import kotlin.reflect.KProperty
import kotlin.reflect.full.memberProperties

internal interface VssDefinitionParser {
/**
* @param definitionFile to parse [VssSpecificationElement] with
*/
fun parseSpecifications(definitionFile: File): List<VssSpecificationElement>
}

/**
* @param fields to set via reflection. Pair<PropertyName, anyValue>.
* @param remapNames which can be used if the propertyName does not match with the input name
*/
fun VssSpecificationElement.setFields(
fields: List<Pair<String, Any?>>,
remapNames: Map<String, String> = emptyMap(),
) {
val nameToProperty = this::class.memberProperties.associateBy(KProperty<*>::name)

val remappedFields = fields.toMutableList()
remapNames.forEach { (propertyName, newName) ->
val find = fields.find { it.first == propertyName } ?: return@forEach
remappedFields.remove(find)
remappedFields.add(Pair(find.first, newName))
}

remappedFields.forEach { (propertyName, propertyValue) ->
nameToProperty[propertyName]
.takeIf { it is KMutableProperty<*> }
?.let { it as KMutableProperty<*> }
?.setter?.call(this, propertyValue)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -55,22 +55,24 @@ import org.eclipse.kuksa.vsscore.model.className
import org.eclipse.kuksa.vsscore.model.name
import org.eclipse.kuksa.vsscore.model.parentVssPath
import org.eclipse.kuksa.vsscore.model.variableName
import java.io.BufferedReader
import java.io.File
import java.io.InputStreamReader
import kotlin.reflect.KClass
import kotlin.reflect.KMutableProperty
import kotlin.reflect.KProperty
import kotlin.reflect.KProperty1
import kotlin.reflect.full.declaredMemberProperties
import kotlin.reflect.full.memberProperties

/**
* Generates a [VssNode] for every specification listed in the input file depending on the [VssDefinitionParser].
* These nodes are a usable kotlin data class reflection of the specification.
*
* @param codeGenerator to generate class files with
* @param logger to log output with
*/
class VssDefinitionProcessor(
private val codeGenerator: CodeGenerator,
private val logger: KSPLogger,
) : SymbolProcessor {
private val visitor = VssDefinitionVisitor()
private val yamlParser = YamlParser()
private val yamlParser = YamlDefinitionParser()

override fun process(resolver: Resolver): List<KSAnnotated> {
val symbols = resolver.getSymbolsWithAnnotation(VssDefinition::class.qualifiedName.toString())
Expand Down Expand Up @@ -376,92 +378,6 @@ class VssDefinitionProcessor(
}
}

class YamlParser {
fun parseSpecifications(definitionFile: File): List<VssSpecificationElement> {
val specificationElements = mutableListOf<VssSpecificationElement>()
val vssDefinitionStream = definitionFile.inputStream()
val bufferedReader = BufferedReader(InputStreamReader(vssDefinitionStream))

val yamlAttributes = mutableListOf<String>()
while (bufferedReader.ready()) {
val line = bufferedReader.readLine().trim()
if (line.isEmpty()) {
val specificationElement = parseYamlElement(yamlAttributes)
specificationElements.add(specificationElement)

yamlAttributes.clear()

continue
}

yamlAttributes.add(line)
}

bufferedReader.close()
vssDefinitionStream.close()

return specificationElements
}

// Example .yaml element:
//
// Vehicle.ADAS.ABS:
// description: Antilock Braking System signals.
// type: branch
// uuid: 219270ef27c4531f874bbda63743b330
private fun parseYamlElement(yamlElement: List<String>): VssSpecificationElement {
val elementVssPath = yamlElement.first().substringBefore(":")

val yamlElementJoined = yamlElement
.joinToString(separator = ";")
.substringAfter(";") // Remove vssPath (already parsed)
.prependIndent(";") // So the parsing is consistent for the first element
val members = VssSpecificationElement::class.memberProperties
val fieldsToSet = mutableListOf<Pair<String, Any?>>()

// The VSSPath is an exception because it is parsed from the top level name.
val vssPathFieldInfo = Pair("vssPath", elementVssPath)
fieldsToSet.add(vssPathFieldInfo)

// Parse (example: "description: Antilock Braking System signals.") into name + value for all .yaml lines
for (member in members) {
val memberName = member.name
if (!yamlElementJoined.contains(memberName)) continue

val memberValue = yamlElementJoined
.substringAfter(";$memberName: ") // Also parse "," to not confuse type != datatype
.substringBefore(";")

val fieldInfo = Pair(memberName, memberValue)
fieldsToSet.add(fieldInfo)
}

val vssSpecificationMember = VssSpecificationElement()
vssSpecificationMember.setFields(fieldsToSet)

return vssSpecificationMember
}

private fun Any.setFields(
fields: MutableList<Pair<String, Any?>>,
remapNames: Map<String, String> = emptyMap(),
) {
val nameToProperty = this::class.memberProperties.associateBy(KProperty<*>::name)
remapNames.forEach { (propertyName, newName) ->
val find = fields.find { it.first == propertyName } ?: return@forEach
fields.remove(find)
fields.add(Pair(find.first, newName))
}

fields.forEach { (propertyName, propertyValue) ->
nameToProperty[propertyName]
.takeIf { it is KMutableProperty<*> }
?.let { it as KMutableProperty<*> }
?.setter?.call(this, propertyValue)
}
}
}

data class VssPath(val path: String) {
val leaf: String
get() = path.substringAfterLast(".")
Expand Down Expand Up @@ -537,6 +453,9 @@ class VssDefinitionProcessor(
}
}

/**
* Provides the environment for the [VssDefinitionProcessor].
*/
class VssDefinitionProcessorProvider : SymbolProcessorProvider {
override fun create(
environment: SymbolProcessorEnvironment,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
package org.eclipse.kuksa.vssprocessor

import java.io.BufferedReader
import java.io.File
import java.io.InputStreamReader
import kotlin.reflect.full.memberProperties

internal class YamlDefinitionParser : VssDefinitionParser {
override fun parseSpecifications(definitionFile: File): List<VssDefinitionProcessor.VssSpecificationElement> {
val specificationElements = mutableListOf<VssDefinitionProcessor.VssSpecificationElement>()
val vssDefinitionStream = definitionFile.inputStream()
val bufferedReader = BufferedReader(InputStreamReader(vssDefinitionStream))

val yamlAttributes = mutableListOf<String>()
while (bufferedReader.ready()) {
val line = bufferedReader.readLine().trim()
if (line.isEmpty()) {
val specificationElement = parseYamlElement(yamlAttributes)
specificationElements.add(specificationElement)

yamlAttributes.clear()

continue
}

yamlAttributes.add(line)
}

bufferedReader.close()
vssDefinitionStream.close()

return specificationElements
}

// Example .yaml element:
//
// Vehicle.ADAS.ABS:
// description: Antilock Braking System signals.
// type: branch
// uuid: 219270ef27c4531f874bbda63743b330
private fun parseYamlElement(yamlElement: List<String>): VssDefinitionProcessor.VssSpecificationElement {
val elementVssPath = yamlElement.first().substringBefore(":")

val yamlElementJoined = yamlElement
.joinToString(separator = ";")
.substringAfter(";") // Remove vssPath (already parsed)
.prependIndent(";") // So the parsing is consistent for the first element
val members = VssDefinitionProcessor.VssSpecificationElement::class.memberProperties
val fieldsToSet = mutableListOf<Pair<String, Any?>>()

// The VSSPath is an exception because it is parsed from the top level name.
val vssPathFieldInfo = Pair("vssPath", elementVssPath)
fieldsToSet.add(vssPathFieldInfo)

// Parse (example: "description: Antilock Braking System signals.") into name + value for all .yaml lines
for (member in members) {
val memberName = member.name
if (!yamlElementJoined.contains(memberName)) continue

val memberValue = yamlElementJoined
.substringAfter(";$memberName: ") // Also parse "," to not confuse type != datatype
.substringBefore(";")

val fieldInfo = Pair(memberName, memberValue)
fieldsToSet.add(fieldInfo)
}

val vssSpecificationMember = VssDefinitionProcessor.VssSpecificationElement()
vssSpecificationMember.setFields(fieldsToSet)

return vssSpecificationMember
}
}

0 comments on commit 2d6ee49

Please sign in to comment.