Skip to content

Commit c51b223

Browse files
committed
merge "feature/data-structure-refactor" (+2 squashed commits)
1 parent 9af4e57 commit c51b223

File tree

15 files changed

+312
-195
lines changed

15 files changed

+312
-195
lines changed

build.gradle.kts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,4 +43,4 @@ publishing {
4343
url = uri("path/to/standalone/plugin/project")
4444
}
4545
}
46-
}
46+
}

src/main/kotlin/com/github/ivancarras/graphfity/plugin/main/GraphfityPlugin.kt

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,9 @@ class GraphfityPlugin : Plugin<Project> {
88
override fun apply(project: Project) {
99
val extension = project.extensions.create("graphfityExtension", GraphfityPluginExtension::class.java)
1010
project.tasks.create("graphfity", GraphfityTask::class.java) {
11-
it.graphImagePath.set(extension.graphImagePath)
12-
it.projectRootName.set(extension.projectRootName)
13-
it.nodeTypesPath.set(extension.nodeTypesPath)
11+
it.graphImagePathProperty.set(extension.graphImagePath)
12+
it.projectRootNameProperty.set(extension.projectRootName)
13+
it.nodeTypesPathProperty.set(extension.nodeTypesPath)
1414
}
1515
}
16-
}
16+
}

src/main/kotlin/com/github/ivancarras/graphfity/plugin/main/GraphfityPluginExtension.kt

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,8 @@ abstract class GraphfityPluginExtension {
1515
}
1616

1717
companion object {
18-
private const val DEFAULT_GRAPH_IMAGE_PATH = "gradle/dependency-graph/"
18+
private const val DEFAULT_GRAPH_IMAGE_PATH = "./graphfity/"
1919
private const val DEFAULT_PROJECT_ROOT_NAME = ":app"
20-
private const val DEFAULT_NODE_TYPES_PATH = "src/main/resources"
20+
private const val DEFAULT_NODE_TYPES_PATH = "./graphfity/"
2121
}
22-
}
22+
}
Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
1+
package com.github.ivancarras.graphfity.plugin.mapper
2+
3+
import com.github.ivancarras.graphfity.plugin.model.ProjectGraph
4+
import com.github.ivancarras.graphfity.plugin.model.ProjectModuleData
5+
import com.github.ivancarras.graphfity.plugin.model.ProjectModuleData.NodeType
6+
import com.github.ivancarras.graphfity.plugin.model.ProjectModuleNode
7+
import org.gradle.api.Project
8+
import org.gradle.api.artifacts.ProjectDependency
9+
import org.gradle.util.GradleVersion
10+
11+
private const val ROOT_LEVEL = 0
12+
13+
fun Project.toRootNode(nodeTypes: Set<NodeType>): ProjectModuleNode {
14+
val rootProjectModuleData = buildProjectModuleData(
15+
path = path,
16+
nodeTypes = nodeTypes,
17+
level = ROOT_LEVEL,
18+
) ?: throw IllegalArgumentException("Root project path does not match any node type")
19+
return ProjectModuleNode(id = rootProjectModuleData.path, data = rootProjectModuleData)
20+
}
21+
22+
fun Project.toProjectGraph(nodeTypes: Set<NodeType>): ProjectGraph {
23+
val adjacencyList = ProjectGraph()
24+
val rootNode = project.toRootNode(nodeTypes)
25+
adjacencyList.addNode(rootNode)
26+
addChildNodesForProjectDependencies(
27+
project = project,
28+
parentNode = rootNode,
29+
adjacencyList = adjacencyList,
30+
nodeTypes = nodeTypes,
31+
parentLevel = rootNode.data.level,
32+
)
33+
return adjacencyList
34+
}
35+
36+
37+
private fun addChildNodesForProjectDependencies(
38+
parentNode: ProjectModuleNode,
39+
project: Project,
40+
nodeTypes: Set<NodeType>,
41+
adjacencyList: ProjectGraph,
42+
parentLevel: Int,
43+
) {
44+
project.configurations
45+
.forEach { config ->
46+
config.dependencies
47+
.withType(ProjectDependency::class.java)
48+
.mapToProject(project)
49+
.filterNot { it.path == project.path }
50+
.forEach { childProject ->
51+
val childLevel = parentLevel + 1
52+
buildProjectModuleData(
53+
path = childProject.path,
54+
nodeTypes = nodeTypes,
55+
level = childLevel,
56+
)?.let { dependencyProjectNodeData ->
57+
val childNode =
58+
ProjectModuleNode(id = dependencyProjectNodeData.path, data = dependencyProjectNodeData)
59+
val isChildNodePresent = adjacencyList.contains(id = childNode.id)
60+
if (!isChildNodePresent) {
61+
adjacencyList.addNode(childNode)
62+
}
63+
adjacencyList.addDirectedEdge(source = parentNode, destination = childNode)
64+
65+
// To rank correctly the child level, if we find a new dependency with a higher level
66+
// that the previously defined we update the node level
67+
val isNeededToUpdateChildNodeLevel = isChildNodePresent && childLevel >
68+
(adjacencyList.nodes.find { it.id == childNode.id }?.data?.level ?: Int.MAX_VALUE)
69+
if (isNeededToUpdateChildNodeLevel) {
70+
adjacencyList.updateNode(childNode)
71+
}
72+
73+
if (!isChildNodePresent) {
74+
addChildNodesForProjectDependencies(
75+
parentNode = childNode,
76+
project = childProject,
77+
adjacencyList = adjacencyList,
78+
nodeTypes = nodeTypes,
79+
parentLevel = childNode.data.level,
80+
)
81+
}
82+
}
83+
}
84+
}
85+
}
86+
87+
88+
@Suppress("deprecation")
89+
private fun Iterable<ProjectDependency>.mapToProject(project: Project): List<Project> = map {
90+
// https://docs.gradle.org/8.11/release-notes.html
91+
// https://github.com/gradle/gradle/issues/30992
92+
// path attribute starts to be supported on Gradle 8.11 so we need to check the version before using it
93+
if (GradleVersion.current() > GradleVersion.version("8.11")) {
94+
project.project(it.path)
95+
} else {
96+
it.dependencyProject
97+
}
98+
}
99+
100+
fun buildProjectModuleData(
101+
path: String,
102+
nodeTypes: Set<NodeType>,
103+
level: Int,
104+
): ProjectModuleData? =
105+
nodeTypes.find { nodeType ->
106+
nodeType.regex.toRegex().matches(path) && nodeType.isEnabled
107+
}?.let { nodeType ->
108+
ProjectModuleData(path = path, nodeType = nodeType, level = level)
109+
}
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
package com.github.ivancarras.graphfity.plugin.mapper
2+
3+
import com.github.ivancarras.graphfity.plugin.model.ProjectGraph
4+
import com.github.ivancarras.graphfity.plugin.model.ProjectModuleData
5+
import com.github.ivancarras.graphfity.plugin.model.ProjectModuleEdge
6+
import com.github.ivancarras.graphfity.plugin.model.datastructures.Node
7+
8+
private const val RANK_SEP = 1.2
9+
10+
fun ProjectGraph.toDot(): String = graphDot {
11+
nodesDot(this@toDot)
12+
edgesDot(this@toDot)
13+
ranksDot(this@toDot)
14+
}
15+
16+
private fun StringBuilder.nodesDot(
17+
adjacencyList: ProjectGraph,
18+
) {
19+
adjacencyList.nodes.forEach { appendLine(it.toDot()) }
20+
}
21+
22+
private fun StringBuilder.edgesDot(
23+
adjacencyList: ProjectGraph,
24+
) {
25+
adjacencyList.edges.forEach { appendLine(it.toDot()) }
26+
}
27+
28+
private fun StringBuilder.ranksDot(
29+
adjacencyList: ProjectGraph,
30+
) {
31+
adjacencyList.nodes.groupBy { it.data.level }.forEach {
32+
append(" {rank = same;")
33+
it.value.forEach { node ->
34+
append(" \"${node.data.path}\";")
35+
}
36+
appendLine("}")
37+
}
38+
}
39+
40+
private fun graphDot(content: StringBuilder.() -> Unit): String = buildString {
41+
appendLine("digraph {")
42+
appendLine(" graph [ranksep=$RANK_SEP];")
43+
content()
44+
append("}")
45+
}
46+
47+
private fun Node<ProjectModuleData>.toDot(): String = buildString {
48+
appendLine(" node [style=filled, shape=${data.nodeType.shape} fillcolor=\"${data.nodeType.fillColor}\"];")
49+
append(" \"${data.path}\"")
50+
}
51+
52+
private fun ProjectModuleEdge.toDot(): String = buildString {
53+
append(" \"${source.data.path}\" -> \"${destination.data.path}\";")
54+
}

src/main/kotlin/com/github/ivancarras/graphfity/plugin/model/NodeData.kt

Lines changed: 0 additions & 3 deletions
This file was deleted.

src/main/kotlin/com/github/ivancarras/graphfity/plugin/model/NodeType.kt

Lines changed: 0 additions & 9 deletions
This file was deleted.
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
package com.github.ivancarras.graphfity.plugin.model
2+
3+
import com.github.ivancarras.graphfity.plugin.model.datastructures.AdjacencyList
4+
import com.github.ivancarras.graphfity.plugin.model.datastructures.Edge
5+
import com.github.ivancarras.graphfity.plugin.model.datastructures.Node
6+
7+
class ProjectGraph : AdjacencyList<ProjectModuleData>() {
8+
9+
override fun addDirectedEdge(source: Node<ProjectModuleData>, destination: Node<ProjectModuleData>) {
10+
val edge = Edge(source = source, destination = destination)
11+
if (adjacencyMap[source]?.contains(edge) == false) {
12+
super.addDirectedEdge(source = source, destination = destination)
13+
}
14+
}
15+
16+
fun updateNode(node: Node<ProjectModuleData>) {
17+
val updatedAdjacencyMap = ProjectGraph().adjacencyMap
18+
adjacencyMap.forEach { entry ->
19+
val updatedKey = if (entry.key.id == node.id) {
20+
node
21+
} else {
22+
entry.key
23+
}
24+
val updatedValue = ArrayList(entry.value.map { edge ->
25+
val updatedSource = if (edge.source.id == node.id) {
26+
node
27+
} else {
28+
edge.source
29+
}
30+
val updatedDestination = if (edge.destination.id == node.id) {
31+
node
32+
} else {
33+
edge.destination
34+
}
35+
Edge(source = updatedSource, destination = updatedDestination)
36+
})
37+
updatedAdjacencyMap[updatedKey] = updatedValue
38+
}
39+
adjacencyMap.clear()
40+
adjacencyMap.putAll(updatedAdjacencyMap)
41+
}
42+
43+
fun contains(id: String): Boolean =
44+
nodes.any { it.id == id }
45+
}
46+
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
package com.github.ivancarras.graphfity.plugin.model
2+
3+
data class ProjectModuleData(val path: String, val nodeType: NodeType, val level: Int) {
4+
data class NodeType(
5+
val name: String,
6+
val regex: String,
7+
val isEnabled: Boolean,
8+
val shape: String,
9+
val fillColor: String,
10+
)
11+
}
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
package com.github.ivancarras.graphfity.plugin.model
2+
3+
import com.github.ivancarras.graphfity.plugin.model.datastructures.Edge
4+
5+
typealias ProjectModuleEdge = Edge<ProjectModuleData>

0 commit comments

Comments
 (0)