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

feature: Add Support for unit, min and max attributes of Specification #107

Merged
merged 6 commits into from
Mar 25, 2024
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,7 @@ class VssModelGeneratorProcessor(
}

val vssPathToVssNodeElement = simpleNodeElements
.distinctBy { it.uuid }
.distinctBy { it.vssPath }
.associateBy({ VssPath(it.vssPath) }, { it })

generateModelFiles(vssPathToVssNodeElement)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
/*
* Copyright (c) 2023 - 2024 Contributors to the Eclipse Foundation
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
* SPDX-License-Identifier: Apache-2.0
*
*/

package org.eclipse.kuksa.vssprocessor.parser

/**
* Will be thrown when an error occurred while parsing a File.
*/
class FileParseException(
override val message: String? = null,
override val cause: Throwable? = null,
) : Exception(message, cause)
wba2hi marked this conversation as resolved.
Show resolved Hide resolved
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
/*
* Copyright (c) 2023 - 2024 Contributors to the Eclipse Foundation
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
* SPDX-License-Identifier: Apache-2.0
*
*/

package org.eclipse.kuksa.vssprocessor.parser

const val ROOT_KEY_VEHICLE = "Vehicle"

const val KEY_DATA_DESCRIPTION = "description"
const val KEY_DATA_TYPE = "type"
const val KEY_DATA_UUID = "uuid"
const val KEY_DATA_COMMENT = "comment"
const val KEY_DATA_DATATYPE = "datatype"
const val KEY_DATA_UNIT = "unit"
const val KEY_DATA_MIN = "min"
const val KEY_DATA_MAX = "max"
const val KEY_DATA_CHILDREN = "children"
wba2hi marked this conversation as resolved.
Show resolved Hide resolved

val VSS_DATA_KEYS = listOf(
KEY_DATA_DESCRIPTION,
KEY_DATA_TYPE,
KEY_DATA_UUID,
KEY_DATA_COMMENT,
KEY_DATA_UNIT,
KEY_DATA_DATATYPE,
KEY_DATA_MIN,
KEY_DATA_MAX,
KEY_DATA_CHILDREN,
)
Original file line number Diff line number Diff line change
Expand Up @@ -22,29 +22,26 @@ package org.eclipse.kuksa.vssprocessor.parser.json
import com.google.gson.Gson
import com.google.gson.JsonObject
import com.google.gson.JsonParseException
import org.eclipse.kuksa.vssprocessor.parser.KEY_DATA_CHILDREN
import org.eclipse.kuksa.vssprocessor.parser.KEY_DATA_COMMENT
import org.eclipse.kuksa.vssprocessor.parser.KEY_DATA_DATATYPE
import org.eclipse.kuksa.vssprocessor.parser.KEY_DATA_DESCRIPTION
import org.eclipse.kuksa.vssprocessor.parser.KEY_DATA_MAX
import org.eclipse.kuksa.vssprocessor.parser.KEY_DATA_MIN
import org.eclipse.kuksa.vssprocessor.parser.KEY_DATA_TYPE
import org.eclipse.kuksa.vssprocessor.parser.KEY_DATA_UNIT
import org.eclipse.kuksa.vssprocessor.parser.KEY_DATA_UUID
import org.eclipse.kuksa.vssprocessor.parser.ROOT_KEY_VEHICLE
import org.eclipse.kuksa.vssprocessor.parser.VSS_DATA_KEYS
import org.eclipse.kuksa.vssprocessor.parser.VssParser
import org.eclipse.kuksa.vssprocessor.spec.VssDataType
import org.eclipse.kuksa.vssprocessor.spec.VssNodeProperty
import org.eclipse.kuksa.vssprocessor.spec.VssNodeSpecModel
import org.eclipse.kuksa.vssprocessor.spec.VssSignalProperty
import java.io.File
import java.io.IOException

private const val ROOT_KEY_VEHICLE = "Vehicle"

private const val KEY_DATA_DESCRIPTION = "description"
private const val KEY_DATA_TYPE = "type"
private const val KEY_DATA_UUID = "uuid"
private const val KEY_DATA_COMMENT = "comment"
private const val KEY_DATA_DATATYPE = "datatype"
private const val KEY_DATA_CHILDREN = "children"

internal class JsonVssParser : VssParser {
private val dataKeys = listOf(
KEY_DATA_DESCRIPTION,
KEY_DATA_TYPE,
KEY_DATA_UUID,
KEY_DATA_COMMENT,
KEY_DATA_DATATYPE,
KEY_DATA_CHILDREN,
)

override fun parseNodes(vssFile: File): List<VssNodeSpecModel> {
val vssNodeSpecModels = mutableListOf<VssNodeSpecModel>()
Expand Down Expand Up @@ -81,7 +78,7 @@ internal class JsonVssParser : VssParser {
val childrenJsonElement = jsonObject.getAsJsonObject(KEY_DATA_CHILDREN)

val filteredKeys = childrenJsonElement.asMap().keys
.filter { key -> !dataKeys.contains(key) }
.filter { key -> !VSS_DATA_KEYS.contains(key) }

filteredKeys.forEach { key ->
val childJsonElement = childrenJsonElement.getAsJsonObject(key)
Expand All @@ -98,16 +95,33 @@ internal class JsonVssParser : VssParser {
vssPath: String,
jsonObject: JsonObject,
): VssNodeSpecModel {
val uuid = jsonObject.get(KEY_DATA_UUID).asString
val uuid = jsonObject.get(KEY_DATA_UUID)?.asString
?: throw JsonParseException("Could not parse '$KEY_DATA_UUID' for '$vssPath'")

val type = jsonObject.get(KEY_DATA_TYPE).asString
val type = jsonObject.get(KEY_DATA_TYPE)?.asString
?: throw JsonParseException("Could not parse '$KEY_DATA_TYPE' for '$vssPath'")

val description = jsonObject.get(KEY_DATA_DESCRIPTION).asString ?: ""
val description = jsonObject.get(KEY_DATA_DESCRIPTION)?.asString ?: ""
val datatype = jsonObject.get(KEY_DATA_DATATYPE)?.asString ?: ""
val comment = jsonObject.get(KEY_DATA_COMMENT)?.asString ?: ""

return VssNodeSpecModel(uuid, vssPath, description, type, comment, datatype)
val unit = jsonObject.get(KEY_DATA_UNIT)?.asString ?: ""
val min = jsonObject.get(KEY_DATA_MIN)?.asString ?: ""
val max = jsonObject.get(KEY_DATA_MAX)?.asString ?: ""

val vssDataType = VssDataType.find(datatype)
val valueDataType = vssDataType.valueDataType

val vssNodeProperties = mutableSetOf(
VssNodeProperty(vssPath, KEY_DATA_UUID, uuid, String::class),
VssNodeProperty(vssPath, KEY_DATA_TYPE, type, String::class),
VssNodeProperty(vssPath, KEY_DATA_DESCRIPTION, description, String::class),
VssNodeProperty(vssPath, KEY_DATA_COMMENT, comment, String::class),
VssSignalProperty(vssPath, KEY_DATA_DATATYPE, datatype, valueDataType),
VssSignalProperty(vssPath, KEY_DATA_UNIT, unit, String::class),
VssSignalProperty(vssPath, KEY_DATA_MIN, min, valueDataType),
VssSignalProperty(vssPath, KEY_DATA_MAX, max, valueDataType),
)

return VssNodeSpecModel(vssPath, vssNodeProperties)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -19,38 +19,51 @@

package org.eclipse.kuksa.vssprocessor.parser.yaml

import org.eclipse.kuksa.vsscore.model.VssNode
import org.eclipse.kuksa.vssprocessor.parser.FileParseException
import org.eclipse.kuksa.vssprocessor.parser.KEY_DATA_COMMENT
import org.eclipse.kuksa.vssprocessor.parser.KEY_DATA_DATATYPE
import org.eclipse.kuksa.vssprocessor.parser.KEY_DATA_DESCRIPTION
import org.eclipse.kuksa.vssprocessor.parser.KEY_DATA_MAX
import org.eclipse.kuksa.vssprocessor.parser.KEY_DATA_MIN
import org.eclipse.kuksa.vssprocessor.parser.KEY_DATA_TYPE
import org.eclipse.kuksa.vssprocessor.parser.KEY_DATA_UNIT
import org.eclipse.kuksa.vssprocessor.parser.KEY_DATA_UUID
import org.eclipse.kuksa.vssprocessor.parser.VssParser
import org.eclipse.kuksa.vssprocessor.spec.VssDataType
import org.eclipse.kuksa.vssprocessor.spec.VssNodeProperty
import org.eclipse.kuksa.vssprocessor.spec.VssNodeSpecModel
import org.eclipse.kuksa.vssprocessor.spec.VssSignalProperty
import java.io.File
import kotlin.reflect.KMutableProperty
import kotlin.reflect.KProperty
import kotlin.reflect.full.memberProperties
import java.io.IOException

internal class YamlVssParser(private val elementDelimiter: String = "") : VssParser {
override fun parseNodes(vssFile: File): List<VssNodeSpecModel> {
val vssNodeElements = mutableListOf<VssNodeSpecModel>()
vssFile.useLines { lines ->
val yamlAttributes = mutableListOf<String>()
for (line in lines.toList()) {
val trimmedLine = line.trim()
if (trimmedLine == elementDelimiter) { // A new element will follow after the delimiter
parseYamlElement(yamlAttributes)?.let { element ->
vssNodeElements.add(element)
try {
vssFile.useLines { lines ->
val yamlAttributes = mutableListOf<String>()
for (line in lines.toList()) {
val trimmedLine = line.trim()
if (trimmedLine == elementDelimiter) { // A new element will follow after the delimiter
parseYamlElement(yamlAttributes).let { element ->
vssNodeElements.add(element)
}

yamlAttributes.clear()

continue
}

yamlAttributes.clear()

continue
yamlAttributes.add(trimmedLine)
}

yamlAttributes.add(trimmedLine)
}

// Add the last element because no empty line will follow
parseYamlElement(yamlAttributes)?.let { element ->
vssNodeElements.add(element)
// Add the last element because no empty line will follow
parseYamlElement(yamlAttributes).let { element ->
vssNodeElements.add(element)
}
}
} catch (e: FileParseException) {
throw IOException("Invalid VSS File: '${vssFile.path}'", e)
}

return vssNodeElements
Expand All @@ -62,64 +75,57 @@ internal class YamlVssParser(private val elementDelimiter: String = "") : VssPar
// description: Antilock Braking System signals.
// type: branch
// uuid: 219270ef27c4531f874bbda63743b330
private fun parseYamlElement(yamlElement: List<String>, delimiter: Char = ';'): VssNodeSpecModel? {
val elementVssPath = yamlElement.first().substringBefore(":")
private fun parseYamlElement(yamlElement: List<String>, delimiter: Char = ';'): VssNodeSpecModel {
val vssPath = yamlElement.first().substringBefore(":")

val yamlElementJoined = yamlElement
.joinToString(separator = delimiter.toString())
.substringAfter(delimiter) // Remove vssPath (already parsed)
.prependIndent(delimiter.toString()) // So the parsing is consistent for the first element
val members = VssNodeSpecModel::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

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

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

val vssNodeSpec = VssNodeSpecModel()
vssNodeSpec.setFields(fieldsToSet)

if (vssNodeSpec.uuid.isEmpty()) return null

return vssNodeSpec
val uuid = fetchValue(KEY_DATA_UUID, yamlElementJoined, delimiter)
?: throw FileParseException("Could not parse '$KEY_DATA_UUID' for '$vssPath'")

val type = fetchValue(KEY_DATA_TYPE, yamlElementJoined, delimiter)
?: throw FileParseException("Could not parse '$KEY_DATA_TYPE' for '$vssPath'")

val description = fetchValue(KEY_DATA_DESCRIPTION, yamlElementJoined, delimiter) ?: ""
val datatype = fetchValue(KEY_DATA_DATATYPE, yamlElementJoined, delimiter) ?: ""
val comment = fetchValue(KEY_DATA_COMMENT, yamlElementJoined, delimiter) ?: ""
val unit = fetchValue(KEY_DATA_UNIT, yamlElementJoined, delimiter) ?: ""
val min = fetchValue(KEY_DATA_MIN, yamlElementJoined, delimiter) ?: ""
val max = fetchValue(KEY_DATA_MAX, yamlElementJoined, delimiter) ?: ""

val vssDataType = VssDataType.find(datatype)
val valueDataType = vssDataType.valueDataType

val vssNodeProperties = mutableSetOf(
VssNodeProperty(vssPath, KEY_DATA_UUID, uuid, String::class),
VssNodeProperty(vssPath, KEY_DATA_TYPE, type, String::class),
VssNodeProperty(vssPath, KEY_DATA_DESCRIPTION, description, String::class),
VssNodeProperty(vssPath, KEY_DATA_COMMENT, comment, String::class),
VssSignalProperty(vssPath, KEY_DATA_DATATYPE, datatype, valueDataType),
VssSignalProperty(vssPath, KEY_DATA_UNIT, unit, String::class),
VssSignalProperty(vssPath, KEY_DATA_MIN, min, valueDataType),
VssSignalProperty(vssPath, KEY_DATA_MAX, max, valueDataType),
wba2hi marked this conversation as resolved.
Show resolved Hide resolved
)

return VssNodeSpecModel(vssPath, vssNodeProperties)
}
}

/**
* @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
*/
private fun VssNode.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)
}
private fun fetchValue(
nodeName: String,
yamlElementJoined: String,
delimiter: Char,
): String? {
// Also parse the delimiter to not confuse type != datatype
val value = yamlElementJoined
.substringAfter("$delimiter$nodeName: ")
.substringBefore(delimiter)

return value.ifEmpty { return null }
}
Loading