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 all 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)
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
/*
* 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_CHILDREN = "children"

enum class VssDataKey {
UUID,
TYPE,
DESCRIPTION,
COMMENT,
DATATYPE,
UNIT,
MIN,
MAX,
;

val key = name.lowercase()

companion object {
fun findByKey(key: String): VssDataKey? {
return entries.find { it.key == key }
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -22,29 +22,25 @@ 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_CHILDREN
import org.eclipse.kuksa.vssprocessor.parser.ROOT_KEY_VEHICLE
import org.eclipse.kuksa.vssprocessor.parser.VssDataKey
import org.eclipse.kuksa.vssprocessor.parser.VssDataKey.COMMENT
import org.eclipse.kuksa.vssprocessor.parser.VssDataKey.DATATYPE
import org.eclipse.kuksa.vssprocessor.parser.VssDataKey.DESCRIPTION
import org.eclipse.kuksa.vssprocessor.parser.VssDataKey.MAX
import org.eclipse.kuksa.vssprocessor.parser.VssDataKey.MIN
import org.eclipse.kuksa.vssprocessor.parser.VssDataKey.TYPE
import org.eclipse.kuksa.vssprocessor.parser.VssDataKey.UNIT
import org.eclipse.kuksa.vssprocessor.parser.VssDataKey.UUID
import org.eclipse.kuksa.vssprocessor.parser.VssParser
import org.eclipse.kuksa.vssprocessor.parser.json.extension.get
import org.eclipse.kuksa.vssprocessor.spec.VssNodePropertiesBuilder
import org.eclipse.kuksa.vssprocessor.spec.VssNodeSpecModel
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 @@ -77,11 +73,12 @@ internal class JsonVssParser : VssParser {
val parsedSpecModel = parseSpecModel(vssPath, jsonObject)
parsedSpecModels += parsedSpecModel

if (jsonObject.has(KEY_DATA_CHILDREN)) {
val childrenJsonElement = jsonObject.getAsJsonObject(KEY_DATA_CHILDREN)
if (jsonObject.has(KEY_CHILDREN)) {
val childrenJsonElement = jsonObject.getAsJsonObject(KEY_CHILDREN)

val filteredKeys = childrenJsonElement.asMap().keys
.filter { key -> !dataKeys.contains(key) }
.filter { key -> key != KEY_CHILDREN }
.filter { key -> VssDataKey.findByKey(key) == null }

filteredKeys.forEach { key ->
val childJsonElement = childrenJsonElement.getAsJsonObject(key)
Expand All @@ -98,16 +95,28 @@ internal class JsonVssParser : VssParser {
vssPath: String,
jsonObject: JsonObject,
): VssNodeSpecModel {
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
?: throw JsonParseException("Could not parse '$KEY_DATA_TYPE' for '$vssPath'")

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 uuid = jsonObject.get(UUID)?.asString
?: throw JsonParseException("Could not parse '${UUID.key}' for '$vssPath'")

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

val description = jsonObject.get(DESCRIPTION)?.asString ?: ""
val datatype = jsonObject.get(DATATYPE)?.asString ?: ""
val comment = jsonObject.get(COMMENT)?.asString ?: ""
val unit = jsonObject.get(UNIT)?.asString ?: ""
val min = jsonObject.get(MIN)?.asString ?: ""
val max = jsonObject.get(MAX)?.asString ?: ""

val vssNodeProperties = VssNodePropertiesBuilder(uuid, type)
.withDescription(description)
wba2hi marked this conversation as resolved.
Show resolved Hide resolved
.withComment(comment)
.withDataType(datatype)
.withUnit(unit)
.withMin(min, datatype)
.withMax(max, datatype)
.build()

return VssNodeSpecModel(vssPath, vssNodeProperties)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
/*
* 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.json.extension

import com.google.gson.JsonElement
import com.google.gson.JsonObject
import org.eclipse.kuksa.vssprocessor.parser.VssDataKey

internal fun JsonObject.has(vssDataKey: VssDataKey): Boolean {
return has(vssDataKey.key)
}

internal fun JsonObject.getAsJsonObject(vssDataKey: VssDataKey): JsonObject {
return getAsJsonObject(vssDataKey.key)
}

internal fun JsonObject.get(vssDataKey: VssDataKey): JsonElement? {
return get(vssDataKey.key)
}
Original file line number Diff line number Diff line change
Expand Up @@ -19,38 +19,50 @@

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.VssDataKey
import org.eclipse.kuksa.vssprocessor.parser.VssDataKey.COMMENT
import org.eclipse.kuksa.vssprocessor.parser.VssDataKey.DATATYPE
import org.eclipse.kuksa.vssprocessor.parser.VssDataKey.DESCRIPTION
import org.eclipse.kuksa.vssprocessor.parser.VssDataKey.MAX
import org.eclipse.kuksa.vssprocessor.parser.VssDataKey.MIN
import org.eclipse.kuksa.vssprocessor.parser.VssDataKey.TYPE
import org.eclipse.kuksa.vssprocessor.parser.VssDataKey.UNIT
import org.eclipse.kuksa.vssprocessor.parser.VssDataKey.UUID
import org.eclipse.kuksa.vssprocessor.parser.VssParser
import org.eclipse.kuksa.vssprocessor.spec.VssNodePropertiesBuilder
import org.eclipse.kuksa.vssprocessor.spec.VssNodeSpecModel
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 +74,49 @@ 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 uuid = fetchValue(UUID, yamlElementJoined, delimiter).ifEmpty {
throw FileParseException("Could not parse '${UUID.key}' for '$vssPath'")
}

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

if (vssNodeSpec.uuid.isEmpty()) return null
val type = fetchValue(TYPE, yamlElementJoined, delimiter).ifEmpty {
throw FileParseException("Could not parse '${TYPE.key}' for '$vssPath'")
}

return vssNodeSpec
val description = fetchValue(DESCRIPTION, yamlElementJoined, delimiter)
val comment = fetchValue(COMMENT, yamlElementJoined, delimiter)
val datatype = fetchValue(DATATYPE, yamlElementJoined, delimiter)
val unit = fetchValue(UNIT, yamlElementJoined, delimiter)
val min = fetchValue(MIN, yamlElementJoined, delimiter)
val max = fetchValue(MAX, yamlElementJoined, delimiter)

val vssNodeProperties = VssNodePropertiesBuilder(uuid, type)
.withDescription(description)
.withComment(comment)
.withDataType(datatype)
.withUnit(unit)
.withMin(min, datatype)
.withMax(max, datatype)
.build()

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(
dataKey: VssDataKey,
yamlElementJoined: String,
delimiter: Char,
): String {
// Also parse the delimiter to not confuse type != datatype
return yamlElementJoined
.substringAfter("$delimiter${dataKey.key}: ")
.substringBefore(delimiter)
}
Loading