Skip to content

Commit

Permalink
Merge pull request #73 from boschglobal/fix-62
Browse files Browse the repository at this point in the history
Fix parent class of generated VSS specifications
  • Loading branch information
wba2hi authored Feb 8, 2024
2 parents b7c0fcc + 674ba7d commit 26118f7
Show file tree
Hide file tree
Showing 5 changed files with 84 additions and 10 deletions.
2 changes: 1 addition & 1 deletion gradle/libs.versions.toml
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ datastore = "1.0.0"
constraintlayoutCompose = "1.0.1"
datastorePreferences = "1.0.0"
kotlin = "1.9.0"
kotlinpoet = "1.14.2"
kotlinpoet = "1.16.0"
kotlinxSerializationJson = "1.6.0"
runtimeLivedata = "1.5.3"
symbolProcessingApi = "1.9.10-1.0.13"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,16 @@ val VssSpecification.parentKey: String
return keys[keys.size - 2]
}

/**
* Similar to the [variableName] but for the parent and does not lowercase the [name] wherever necessary.
*/
val VssSpecification.parentClassName: String
get() {
if (parentKey.isEmpty()) return ""

return (classNamePrefix + parentKey).toCamelCase.replaceFirstChar { it.uppercase() }
}

/**
* Iterates through all nested children which also may have children and aggregates them into one big collection.
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ import com.squareup.kotlinpoet.ksp.toClassName
import com.squareup.kotlinpoet.ksp.writeTo
import org.eclipse.kuksa.vsscore.annotation.VssDefinition
import org.eclipse.kuksa.vsscore.model.VssNode
import org.eclipse.kuksa.vsscore.model.parentClassName
import org.eclipse.kuksa.vssprocessor.parser.YamlDefinitionParser
import org.eclipse.kuksa.vssprocessor.spec.VssPath
import org.eclipse.kuksa.vssprocessor.spec.VssSpecificationSpecModel
Expand Down Expand Up @@ -115,6 +116,7 @@ class VssDefinitionProcessor(

logger.info("Ambiguous specifications - Generate nested classes: $duplicateSpecificationNames")

val generatedFilesVssPathToClassName = mutableMapOf<String, String>()
for ((vssPath, specModel) in vssPathToSpecification) {
// Every duplicate is produced as a nested class - No separate file should be generated
if (duplicateSpecificationNames.contains(vssPath.leaf)) {
Expand All @@ -128,11 +130,53 @@ class VssDefinitionProcessor(
duplicateSpecificationNames,
)

val file = FileSpec.builder(PACKAGE_NAME, classSpec.name!!)
val className = classSpec.name ?: throw NoSuchFieldException("Class spec $classSpec has no name field!")
val fileSpecBuilder = FileSpec.builder(PACKAGE_NAME, className)

val parentImport = buildParentImport(specModel, generatedFilesVssPathToClassName)
if (parentImport.isNotEmpty()) {
fileSpecBuilder.addImport(PACKAGE_NAME, parentImport)
}

val file = fileSpecBuilder
.addType(classSpec)
.build()

file.writeTo(codeGenerator, false)
generatedFilesVssPathToClassName[vssPath.path] = className
}
}

// Uses a map of vssPaths to ClassNames which are validated if it contains a parent of the given specModel.
// If the actual parent is a sub class (Driver) in another class file (e.g. Vehicle) then this method returns
// a sub import e.g. "Vehicle.Driver". Otherwise just "Vehicle" is returned.
private fun buildParentImport(
specModel: VssSpecificationSpecModel,
parentVssPathToClassName: Map<String, String>,
): String {
var availableParentVssPath = specModel.vssPath
var parentSpecClassName: String? = null

// Iterate up from the parent until the actual file name = class name was found. This indicates
// that the actual parent is a sub class in this file.
while (availableParentVssPath.contains(".")) {
availableParentVssPath = availableParentVssPath.substringBeforeLast(".")

parentSpecClassName = parentVssPathToClassName[availableParentVssPath]
if (parentSpecClassName != null) break
}

if (parentSpecClassName == null) {
logger.info("Could not create import string for: ${specModel.vssPath} - No parent was found")
return ""
}

val parentClassName = specModel.parentClassName

return if (parentSpecClassName != parentClassName) {
"$parentSpecClassName.$parentClassName" // Sub class in another file
} else {
parentClassName // Main class = File name
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ import org.eclipse.kuksa.vsscore.model.VssProperty
import org.eclipse.kuksa.vsscore.model.VssSpecification
import org.eclipse.kuksa.vsscore.model.className
import org.eclipse.kuksa.vsscore.model.name
import org.eclipse.kuksa.vsscore.model.parentClassName
import org.eclipse.kuksa.vsscore.model.parentKey
import org.eclipse.kuksa.vsscore.model.variableName
import kotlin.reflect.KClass
Expand Down Expand Up @@ -152,6 +153,7 @@ internal class VssSpecificationSpecModel(
relevantRelatedSpecifications,
nestedClasses,
)

nestedChildSpecs.add(childSpec)
}

Expand Down Expand Up @@ -274,13 +276,14 @@ internal class VssSpecificationSpecModel(
}

fun createParentSpec(memberName: String, memberType: TypeName): PropertySpec {
val parentClass = if (parentClassName.isNotEmpty()) "$parentClassName::class" else "null"
return PropertySpec
.builder(memberName, memberType)
.mutable(false)
.addModifiers(KModifier.OVERRIDE)
.getter(
FunSpec.getterBuilder()
.addStatement("return %L", "$className::class")
.addStatement("return %L", parentClass)
.build(),
)
.build()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -150,28 +150,45 @@ class VssSpecificationSpecModelTest : BehaviorSpec({
}
}
and("related specifications") {
val vehicleSpeedSpecModel = VssSpecificationSpecModel(datatype = "float", vssPath = "Vehicle.Speed")
val relatedSpecifications = listOf(
VssSpecificationSpecModel(vssPath = "Vehicle.SmartphoneProjection"),
VssSpecificationSpecModel(datatype = "boolean", vssPath = "Vehicle.IsBrokenDown"),
VssSpecificationSpecModel(datatype = "float", vssPath = "Vehicle.Speed"),
vehicleSpeedSpecModel,
VssSpecificationSpecModel(datatype = "string[]", vssPath = "Vehicle.SupportedMode"),
VssSpecificationSpecModel(datatype = "boolean[]", vssPath = "Vehicle.AreSeatsHeated"),
VssSpecificationSpecModel(datatype = "invalid", vssPath = "Vehicle.Invalid"),
)

`when`("creating a child class spec with children") {
val classSpec = specModel.createClassSpec("test", relatedSpecifications)
`when`("creating a class spec with children") {
val rootClassSpec = specModel.createClassSpec("test", relatedSpecifications)

then("it should contain the child properties") {
val isBrokenDownPropertySpec = classSpec.propertySpecs.find { it.name == "isBrokenDown" }
val childrenPropertySpec = classSpec.propertySpecs.find { it.name == "children" }
val isBrokenDownPropertySpec = rootClassSpec.propertySpecs.find { it.name == "isBrokenDown" }
val childrenPropertySpec = rootClassSpec.propertySpecs.find { it.name == "children" }

classSpec.name shouldBe "VssVehicle"
classSpec.propertySpecs.size shouldBe 13 // 8 interface props + 5 children
rootClassSpec.name shouldBe "VssVehicle"
rootClassSpec.propertySpecs.size shouldBe 13 // 8 interface props + 5 children

isBrokenDownPropertySpec shouldNotBe null
childrenPropertySpec?.getter.toString() shouldContain "smartphoneProjection, isBrokenDown"
}

then("it should have no parent") {
val parentPropertySpec = rootClassSpec.propertySpecs.find { it.name == "parentClass" }

parentPropertySpec?.getter.toString() shouldContain "null"
}

and("a child class spec is created") {
val vehicleSpeedClassSpec = vehicleSpeedSpecModel.createClassSpec("test")

then("it should have the root class as parent") {
val parentPropertySpec = vehicleSpeedClassSpec.propertySpecs.find { it.name == "parentClass" }

parentPropertySpec?.getter.toString() shouldContain "VssVehicle"
}
}
}
and("nested specifications") {
val nestedSpecifications = listOf("Speed")
Expand Down

0 comments on commit 26118f7

Please sign in to comment.