Skip to content

Commit

Permalink
Finish Json output and add more documentation to finalize the PR
Browse files Browse the repository at this point in the history
  • Loading branch information
konradweiss committed Feb 12, 2024
1 parent 8ffb9d9 commit 85c99c7
Show file tree
Hide file tree
Showing 2 changed files with 94 additions and 45 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -223,7 +223,7 @@ class Application : Callable<Int> {
private var inferNodes: Boolean = false

@CommandLine.Option(
names = ["--schema-markdown", "--schema"],
names = ["--schema-markdown"],
description = ["Print the CPGs nodes and edges that they can have."]
)
private var schemaMarkdown: Boolean = false
Expand Down Expand Up @@ -572,7 +572,8 @@ class Application : Callable<Int> {
if (schemaMarkdown || schemaJson) {
if (schemaMarkdown) {
printSchema(mutuallyExclusiveParameters.files, Schema.Format.MARKDOWN)
} else {
}
if (schemaJson) {
printSchema(mutuallyExclusiveParameters.files, Schema.Format.JSON)
}
return EXIT_SUCCESS
Expand Down
134 changes: 91 additions & 43 deletions cpg-neo4j/src/main/kotlin/de/fraunhofer/aisec/cpg_vis_neo4j/Schema.kt
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
*/
package de.fraunhofer.aisec.cpg_vis_neo4j

import com.fasterxml.jackson.databind.ObjectMapper
import de.fraunhofer.aisec.cpg.graph.Node
import de.fraunhofer.aisec.cpg.helpers.neo4j.CpgCompositeConverter
import java.io.File
Expand All @@ -38,6 +39,41 @@ import org.neo4j.ogm.metadata.MetaData

class Schema {

/**
* Schema definition for node entities that is persisted to the neo4j database
*/
data class SchemaNode(
val name: String,
val labels: Set<String>,
val childLabels: Set<String>,
val relationships: Set<SchemaRelationship>,
val properties: Set<SchemaProperty>
)

/**
* Definition of a CPG relationship with relationship label, the targeted node and whether
* the node has multiple or a single relationship edge of the declared type.
*/
data class SchemaRelationship(
val label: String,
val targetNode: String,
val multiplicity: Char
)

/**
* Key-value pair defining a node property and if the property was inherited from a parent node
* entity type or newly introduced in this node entity.
*/
data class SchemaProperty(
val name: String,
val valueType: String,
val inherited: Boolean,
)


/**
* Output format of the CPG Schema description.
*/
enum class Format {
MARKDOWN,
JSON
Expand Down Expand Up @@ -110,6 +146,9 @@ class Schema {
*/
private val relationshipFields: MutableMap<Pair<ClassInfo, String>, FieldInfo> = mutableMapOf()

/**
* Extracts information on the nodes and edges that can be persisted to the neo4 database over the OGM.
*/
fun extractSchema() {
val meta = MetaData(Node.javaClass.packageName)
val nodeClassInfo =
Expand All @@ -133,7 +172,6 @@ class Schema {
}

// node in neo4j

entities.forEach { classInfo ->
val key = meta.schema.findNode(classInfo.neo4jName())
allRels[classInfo.neo4jName() ?: classInfo.underlyingClass.simpleName] =
Expand All @@ -156,6 +194,7 @@ class Schema {
relationPair.filter { rel -> fields.any { it.name == rel.first } }.toSet()
}

// Extracting the key-value pairs that are persisted as node properties
entity.propertyFields().forEach { property ->
val persistedField =
if (
Expand Down Expand Up @@ -188,7 +227,7 @@ class Schema {
entityRoots.forEach {
inheritedRels[it.neo4jName() ?: it.underlyingClass.simpleName] = mutableSetOf()
}
entityRoots.forEach { buildInheritedFields(it) }
entityRoots.forEach { extractFieldInformationFromHierarchy(it) }

allRels.forEach {
childrensRels[it.key] =
Expand All @@ -199,7 +238,10 @@ class Schema {
println()
}

private fun buildInheritedFields(classInfo: ClassInfo) {
/**
* Extracts the field information for every entity and relationship.
*/
private fun extractFieldInformationFromHierarchy(classInfo: ClassInfo) {
val fields: MutableSet<Pair<String, String>> = mutableSetOf()
inherentRels[classInfo.neo4jName() ?: classInfo.underlyingClass.simpleName]?.let {
fields.addAll(it)
Expand All @@ -210,10 +252,13 @@ class Schema {

hierarchy[classInfo]?.second?.forEach {
inheritedRels[it.neo4jName() ?: it.underlyingClass.simpleName] = fields
buildInheritedFields(it)
extractFieldInformationFromHierarchy(it)
}
}

/**
* Complements the hierarchy and relationship information for abstract classes to not have empty entities.
*/
private fun completeSchema(
relCanHave: MutableMap<String, Set<Pair<String, String>>>,
hierarchy: MutableMap<ClassInfo, Pair<ClassInfo?, List<ClassInfo>>>,
Expand All @@ -238,19 +283,26 @@ class Schema {
}
}

/**
* Depending on the specified output format the Neo4j Schema for the CPG is printed to the specified file.
*/
fun printToFile(fileName: String, format: Format) {
val fileExtension = if (Format.MARKDOWN == format) ".md" else ".json"
val file =
File(if (fileName.endsWith(fileExtension)) fileName else fileName + fileExtension)
file.parentFile.mkdirs()
file.createNewFile()
file.printWriter().use { out ->
out.println(header)
val entityRoots: MutableList<ClassInfo> =
hierarchy.filter { it.value.first == null }.map { it.key }.toMutableList()
entityRoots.forEach {
if (format == Format.MARKDOWN) printEntitiesToMarkdown(it, out)
else printEntitiesToJson(it, out)
if (format == Format.MARKDOWN) {
out.println(header)
entityRoots.forEach { printEntitiesToMarkdown(it, out) }
} else {
entityRoots.forEach {
val objectMapper = ObjectMapper()
objectMapper.writeValue(out, entitiesToJson(it))
}
}
}
}
Expand All @@ -267,7 +319,6 @@ class Schema {

out.println("## $entityLabel<a id=\"${toAnchorLink("e${entityLabel}")}\"></a>")

// Todo print entity description
if (hierarchy[classInfo]?.first != null) {
out.print("**Labels**:")

Expand Down Expand Up @@ -374,36 +425,31 @@ class Schema {
*
* Generates links between the boxes.
*/
private fun printEntitiesToJson(classInfo: ClassInfo, out: PrintWriter) {
private fun entitiesToJson(classInfo: ClassInfo): MutableList<SchemaNode> {
val entityLabel = toLabel(classInfo)
// Todo {
out.println("name: $entityLabel")

val labels: MutableSet<String> = mutableSetOf()
if (hierarchy[classInfo]?.first != null) {
out.print("labels: ")

hierarchy[classInfo]?.first?.let { getHierarchy(it).forEach { out.print(toLabel(it)) } }
out.print(entityLabel)
hierarchy[classInfo]?.first?.let {
getHierarchy(it).forEach { labels.add(toLabel(it)) }
}
labels.add(entityLabel)
}
val childLabels: MutableSet<String> = mutableSetOf()
if (hierarchy[classInfo]?.second?.isNotEmpty() == true) {
out.println("childLabels: ")

hierarchy[classInfo]?.second?.let {
if (it.isNotEmpty()) {
it.forEach { classInfo -> out.print(toLabel(classInfo)) }
it.forEach { classInfo -> childLabels.add(toLabel(classInfo)) }
}
}
}

val relationships: MutableSet<SchemaRelationship> = mutableSetOf<SchemaRelationship>()
if (inherentRels.isNotEmpty() && inheritedRels.isNotEmpty()) {
out.println("relationships:")

removeLabelDuplicates(inherentRels[entityLabel])?.forEach {
out.print("[${it.second} " + toLabel(classInfo))
}

if (inheritedRels[entityLabel]?.isNotEmpty() == true) {
out.println("Inherited Relationships")
removeLabelDuplicates(inheritedRels[entityLabel])?.forEach { inherited ->
var current = classInfo
var baseClass: ClassInfo? = null
Expand All @@ -415,36 +461,33 @@ class Schema {
}
hierarchy[current]?.first?.let { current = it }
}
out.println("${inherited.second} ${toLabel(baseClass)}")
baseClass?.let { relationshipsToJson(it, inherited) }
}
}

removeLabelDuplicates(inherentRels[entityLabel])?.forEach {
printRelationshipsToJson(classInfo, it, out)
relationships.add(relationshipsToJson(classInfo, it))
}
}

val properties: MutableSet<SchemaProperty> = mutableSetOf<SchemaProperty>()
if (inherentProperties.isNotEmpty() && inheritedProperties.isNotEmpty()) {
out.println("properties:")

removeLabelDuplicates(inherentProperties[entityLabel])?.forEach {
out.println("name: ${it.second}")
out.println("valueType: ${it.first}")
out.println("inherited: false")
properties.add(SchemaProperty(it.second, it.first, false))
}
if (inheritedProperties[entityLabel]?.isNotEmpty() == true) {
out.println("Inherited Properties")
removeLabelDuplicates(inheritedProperties[entityLabel])?.forEach {
out.println("name: ${it.second}")
out.println("valueType: ${it.first}")
out.println("inherited: true")
properties.add(SchemaProperty(it.second, it.first, true))
}
}
}
val entityNodes =
hierarchy[classInfo]?.second?.flatMap { entitiesToJson(it) }?.toMutableList()
?: mutableListOf<Schema.SchemaNode>()
entityNodes.add(0, SchemaNode(entityLabel, labels, childLabels, relationships, properties))

// Todo {

hierarchy[classInfo]?.second?.forEach { printEntitiesToJson(it, out) }
return entityNodes
}

private fun removeLabelDuplicates(
Expand All @@ -465,6 +508,9 @@ class Schema {
return classInfo.neo4jName() ?: classInfo.underlyingClass.simpleName
}

/**
* Creates a unique markdown anchor to make navigation unambiguous.
*/
private fun toAnchorLink(entityName: String): String {
return toConcatName(entityName).lowercase(Locale.getDefault())
}
Expand Down Expand Up @@ -493,6 +539,10 @@ class Schema {
return inheritance
}

/**
* By specifying a field that constitutes a relationship, this function returns information on the
* multiplicity and the target class entity.
*/
private fun getTargetInfo(fInfo: FieldInfo): Pair<Boolean, ClassInfo?> {
val type = fInfo.field.genericType
relationshipFields
Expand Down Expand Up @@ -555,15 +605,13 @@ class Schema {
closeMermaid(out)
}

private fun printRelationshipsToJson(
private fun relationshipsToJson(
classInfo: ClassInfo,
relationshipLabel: Pair<String, String>,
out: PrintWriter
) {
relationshipLabel: Pair<String, String>
): SchemaRelationship {
val fieldInfo: FieldInfo = classInfo.getFieldInfo(relationshipLabel.first)
val targetInfo = getTargetInfo(fieldInfo)
val multiplicity = if (targetInfo.first) "*" else "¹"
out.println(relationshipLabel.second)
out.println("$${relationshipLabel.second}${multiplicity}${toLabel(classInfo)}")
val multiplicity = if (targetInfo.first) '*' else '1'
return SchemaRelationship(relationshipLabel.second, toLabel(classInfo), multiplicity)
}
}

0 comments on commit 85c99c7

Please sign in to comment.