Changing flow stack snapshot tree node labeling (#26)

* Changing flow stack snapshot tree node labeling

* Removing stackObjects node from the hierarchy
This commit is contained in:
mkit 2017-08-17 12:16:10 +01:00 committed by GitHub
parent a3ab62341c
commit 551a26d0a3
6 changed files with 53 additions and 42 deletions

View File

@ -57,7 +57,7 @@ private class FlowStackSnapshotDefaultFactory : FlowStackSnapshotFactory {
*/ */
data class FlowStackSnapshot constructor( data class FlowStackSnapshot constructor(
val timestamp: Long = System.currentTimeMillis(), val timestamp: Long = System.currentTimeMillis(),
val flowClass: Class<*>? = null, val flowClass: String? = null,
val stackFrames: List<Frame> = listOf() val stackFrames: List<Frame> = listOf()
) { ) {
data class Frame( data class Frame(

View File

@ -26,6 +26,7 @@ intellij {
} }
dependencies { dependencies {
compile project(':core')
// For JSON // For JSON
compile "com.fasterxml.jackson.core:jackson-databind:2.8.5" compile "com.fasterxml.jackson.core:jackson-databind:2.8.5"
} }

View File

@ -8,7 +8,6 @@ import com.intellij.ui.components.JBScrollPane
import com.intellij.ui.content.Content import com.intellij.ui.content.Content
import com.intellij.ui.content.ContentFactory import com.intellij.ui.content.ContentFactory
import com.intellij.ui.treeStructure.Tree import com.intellij.ui.treeStructure.Tree
import net.corda.ideaplugin.module.CordaModuleType
import java.awt.* import java.awt.*
import java.awt.event.MouseAdapter import java.awt.event.MouseAdapter
import java.awt.event.MouseEvent import java.awt.event.MouseEvent
@ -187,10 +186,10 @@ class CordaFlowToolWindow : ToolWindowFactory {
private class SnapshotTreeRenderer : DefaultTreeCellRenderer() { private class SnapshotTreeRenderer : DefaultTreeCellRenderer() {
override fun getTreeCellRendererComponent(tree: JTree?, value: Any?, sel: Boolean, expanded: Boolean, leaf: Boolean, row: Int, hasFocus: Boolean): Component { override fun getTreeCellRendererComponent(tree: JTree?, value: Any?, sel: Boolean, expanded: Boolean, leaf: Boolean, row: Int, hasFocus: Boolean): Component {
super.getTreeCellRendererComponent(tree, value, sel, expanded, leaf, row, hasFocus) super.getTreeCellRendererComponent(tree, value, sel, expanded, leaf, row, hasFocus)
val descriptor = (value as DefaultMutableTreeNode).userObject as SnapshotDataDescriptor val descriptor = (value as DefaultMutableTreeNode).userObject as Descriptor
icon = descriptor.icon icon = descriptor.icon
if (!descriptor.key.isNullOrEmpty()) { if (!descriptor.label.isNullOrEmpty()) {
text = "${descriptor.key}: ${descriptor.data.toString()}" text = descriptor.label + if (leaf) ": ${descriptor.value?.toString()}" else ""
} }
return this return this
} }

View File

@ -3,6 +3,7 @@ package net.corda.ideaplugin.toolwindow
import com.fasterxml.jackson.databind.JsonNode import com.fasterxml.jackson.databind.JsonNode
import com.fasterxml.jackson.databind.ObjectMapper import com.fasterxml.jackson.databind.ObjectMapper
import com.intellij.icons.AllIcons import com.intellij.icons.AllIcons
import net.corda.core.flows.FlowStackSnapshot
import java.io.File import java.io.File
import javax.swing.Icon import javax.swing.Icon
import javax.swing.JTree import javax.swing.JTree
@ -10,13 +11,6 @@ import javax.swing.tree.DefaultMutableTreeNode
import javax.swing.tree.DefaultTreeModel import javax.swing.tree.DefaultTreeModel
import javax.swing.tree.MutableTreeNode import javax.swing.tree.MutableTreeNode
/**
* Snapshot tree data descriptor. It is used as userObject in the [DefaultMutableTreeNode] class.
*/
class SnapshotDataDescriptor(val data: Any?, val icon: Icon, val key: String? = null) {
override fun toString(): String = data?.toString() ?: "null"
}
/** /**
* Manager class for flow snapshots. It is responsible for parsing data read from a snapshot file and constructing * Manager class for flow snapshots. It is responsible for parsing data read from a snapshot file and constructing
* tree model from it. * tree model from it.
@ -27,7 +21,7 @@ class FlowSnapshotTreeDataManager(tree: JTree) {
} }
// Root node for the snapshot hierarchy, which is an empty node. // Root node for the snapshot hierarchy, which is an empty node.
private val root = DefaultMutableTreeNode(SnapshotDataDescriptor(null, AllIcons.Json.Object)) private val root = DefaultMutableTreeNode(Descriptor())
// Snapshot tree model // Snapshot tree model
private val snapshotModel = DefaultTreeModel(root) private val snapshotModel = DefaultTreeModel(root)
@ -62,8 +56,8 @@ class FlowSnapshotTreeDataManager(tree: JTree) {
* the model is updated accordingly. * the model is updated accordingly.
*/ */
fun addNodeToSnapshotModel(snapshotFile: File) { fun addNodeToSnapshotModel(snapshotFile: File) {
val insertionIndex = -(root.childNodes().map { val insertionIndex = -(root.childNodes.map {
(it.userObject as SnapshotDataDescriptor).key it.file?.name
}.binarySearch(extractFileName(snapshotFile))) - 1 }.binarySearch(extractFileName(snapshotFile))) - 1
insertNodeToSnapshotModel(snapshotFile, insertionIndex) insertNodeToSnapshotModel(snapshotFile, insertionIndex)
} }
@ -72,8 +66,9 @@ class FlowSnapshotTreeDataManager(tree: JTree) {
* Removes the snapshot file from the snapshot hierarchy. The model is also updated after this operation. * Removes the snapshot file from the snapshot hierarchy. The model is also updated after this operation.
*/ */
fun removeNodeFromSnapshotModel(snapshotFile: File) { fun removeNodeFromSnapshotModel(snapshotFile: File) {
val node = root.childNodes().find { val snapshotFileName = extractFileName(snapshotFile)
(it.userObject as SnapshotDataDescriptor).data == extractFileName(snapshotFile) val node = root.childNodes.find {
it.file?.name == snapshotFileName
} as MutableTreeNode? } as MutableTreeNode?
if (node != null) { if (node != null) {
snapshotModel.removeNodeFromParent(node) snapshotModel.removeNodeFromParent(node)
@ -83,7 +78,23 @@ class FlowSnapshotTreeDataManager(tree: JTree) {
} }
private fun insertNodeToSnapshotModel(snapshotFile: File, insertionIndex: Int = -1) { private fun insertNodeToSnapshotModel(snapshotFile: File, insertionIndex: Int = -1) {
buildChildrenModel(mapper.readTree(snapshotFile), root, extractFileName(snapshotFile), insertionIndex) val fileNode = DefaultMutableTreeNode(Descriptor(snapshotFile, AllIcons.FileTypes.Custom, snapshotFile.name))
val snapshot = mapper.readValue(snapshotFile, FlowStackSnapshot::class.java)
fileNode.add(DefaultMutableTreeNode(Descriptor(snapshot.timestamp, AllIcons.Debugger.Db_primitive, "timestamp")))
fileNode.add(DefaultMutableTreeNode(Descriptor(snapshot.flowClass, AllIcons.Debugger.Db_primitive, "flowClass")))
val framesNode = DefaultMutableTreeNode(Descriptor(icon = AllIcons.Debugger.Db_array, label = "stackFrames"))
fileNode.add(framesNode)
snapshot.stackFrames.forEach {
val ste = it.stackTraceElement!!
val label = "${ste.className}.${ste.methodName}(line:${ste.lineNumber}) - ${ste.fileName}"
val frameNode = DefaultMutableTreeNode(Descriptor(icon = AllIcons.Debugger.StackFrame, label = label))
framesNode.add(frameNode)
it.stackObjects.mapIndexed { index: Int, stackItem: Any? ->
buildChildrenModel(mapper.convertValue(stackItem, JsonNode::class.java), frameNode, index.toString())
}
}
addToModelAndRefresh(snapshotModel, fileNode, root, insertionIndex)
} }
private fun extractFileName(file: File): String { private fun extractFileName(file: File): String {
@ -92,22 +103,20 @@ class FlowSnapshotTreeDataManager(tree: JTree) {
private fun buildChildrenModel( private fun buildChildrenModel(
node: JsonNode?, node: JsonNode?,
parent: DefaultMutableTreeNode, key: String? = null, parent: DefaultMutableTreeNode, label: String? = null) {
insertionIndex: Int = -1) {
val child: DefaultMutableTreeNode val child: DefaultMutableTreeNode
if (node == null || !node.isContainerNode) { if (node == null || !node.isContainerNode) {
child = DefaultMutableTreeNode(SnapshotDataDescriptor(node, AllIcons.Debugger.Db_primitive, key)) parent.add(DefaultMutableTreeNode(Descriptor(node, AllIcons.Debugger.Db_primitive, label)))
addToModelAndRefresh(snapshotModel, child, parent, insertionIndex)
} else { } else {
if (node.isArray) { if (node.isArray) {
child = DefaultMutableTreeNode(SnapshotDataDescriptor(key, AllIcons.Debugger.Db_array)) child = DefaultMutableTreeNode(Descriptor(icon = AllIcons.Debugger.Db_array, label = label))
addToModelAndRefresh(snapshotModel, child, parent, insertionIndex) parent.add(child)
node.mapIndexed { index: Int, item: JsonNode? -> node.mapIndexed { index: Int, item: JsonNode? ->
buildChildrenModel(item, child, index.toString()) buildChildrenModel(item, child, index.toString())
} }
} else { } else {
child = DefaultMutableTreeNode(SnapshotDataDescriptor(key, AllIcons.Json.Object)) child = DefaultMutableTreeNode(Descriptor(icon = AllIcons.Json.Object, label = label))
addToModelAndRefresh(snapshotModel, child, parent, insertionIndex) parent.add(child)
node.fields().forEach { node.fields().forEach {
buildChildrenModel(it.value, child, it.key) buildChildrenModel(it.value, child, it.key)
} }
@ -115,3 +124,5 @@ class FlowSnapshotTreeDataManager(tree: JTree) {
} }
} }
} }
class Descriptor(val value:Any? = null, val icon: Icon? = null, val label:String? = null)

View File

@ -39,7 +39,7 @@ class FlowTreeDataManager(val tree: JTree, val snapshotModel: FlowSnapshotTreeDa
* Builds the flow directory hierarchy with the root being associated with the passed [flowsDirectory]. * Builds the flow directory hierarchy with the root being associated with the passed [flowsDirectory].
* If the parameter is missing the function rebuilds current hierarchy and reloads (refreshes) current model. * If the parameter is missing the function rebuilds current hierarchy and reloads (refreshes) current model.
*/ */
fun loadFlows(flowsDirectory: File? = root.userObjectAsFile()) { fun loadFlows(flowsDirectory: File? = root.file) {
root.userObject = flowsDirectory ?: return root.userObject = flowsDirectory ?: return
root.removeAllChildren() root.removeAllChildren()
@ -81,8 +81,8 @@ class FlowTreeDataManager(val tree: JTree, val snapshotModel: FlowSnapshotTreeDa
} }
private fun isSelected(dir: File?): Boolean { private fun isSelected(dir: File?): Boolean {
val node = tree.selectedNode() val node = tree.selectedNode
return node != null && dir == node.userObjectAsFile() return node != null && dir == node.file
} }
private fun startWatching(dir: File) { private fun startWatching(dir: File) {
@ -100,18 +100,18 @@ class FlowTreeDataManager(val tree: JTree, val snapshotModel: FlowSnapshotTreeDa
val parent = dir.parentFile val parent = dir.parentFile
if (parent.name == FLOWS_DIRECTORY) { if (parent.name == FLOWS_DIRECTORY) {
// if a date directory has been added this means that that its parent is [FLOWS_DIRECTORY] // if a date directory has been added this means that that its parent is [FLOWS_DIRECTORY]
insertDateDirectory(dir, findInsertionIndex(root.childNodes(), dir.name)) insertDateDirectory(dir, findInsertionIndex(root.childNodes, dir.name))
} else if (parent.parentFile.name == FLOWS_DIRECTORY) { } else if (parent.parentFile.name == FLOWS_DIRECTORY) {
// if a flow directory has been added this means that that the parent of its parent is [FLOWS_DIRECTORY] // if a flow directory has been added this means that that the parent of its parent is [FLOWS_DIRECTORY]
val parentNode = root.childNodes().findByFile(parent) ?: return val parentNode = root.childNodes.findByFile(parent) ?: return
flowModel.insertNodeInto(DefaultMutableTreeNode(dir), parentNode, findInsertionIndex(parentNode.childNodes(), dir.name)) flowModel.insertNodeInto(DefaultMutableTreeNode(dir), parentNode, findInsertionIndex(parentNode.childNodes, dir.name))
startWatching(dir) startWatching(dir)
} }
} }
private fun removeNodeFromFlowModel(dir: File) { private fun removeNodeFromFlowModel(dir: File) {
val selectedNode = tree.selectedNode() val selectedNode = tree.selectedNode
if (selectedNode != null && selectedNode.userObjectAsFile() == dir) { if (selectedNode != null && selectedNode.file == dir) {
// Reload flows if the [dir] is currently selected // Reload flows if the [dir] is currently selected
loadFlows() loadFlows()
return return
@ -123,10 +123,10 @@ class FlowTreeDataManager(val tree: JTree, val snapshotModel: FlowSnapshotTreeDa
parentNode = root parentNode = root
} else if (parent.parentFile.name == FLOWS_DIRECTORY) { } else if (parent.parentFile.name == FLOWS_DIRECTORY) {
// if a flow directory has been added this means that that the parent of its parent is [FLOWS_DIRECTORY] // if a flow directory has been added this means that that the parent of its parent is [FLOWS_DIRECTORY]
parentNode = root.childNodes().findByFile(parent) parentNode = root.childNodes.findByFile(parent)
} }
if (parentNode != null) { if (parentNode != null) {
val node = parentNode.childNodes().findByFile(dir) ?: return val node = parentNode.childNodes.findByFile(dir) ?: return
flowModel.removeNodeFromParent(node) flowModel.removeNodeFromParent(node)
} }
} }
@ -206,10 +206,10 @@ class FlowTreeDataManager(val tree: JTree, val snapshotModel: FlowSnapshotTreeDa
} }
} }
internal fun DefaultMutableTreeNode.userObjectAsFile() = userObject as? File internal val DefaultMutableTreeNode.file get() = userObject as? File
internal fun DefaultMutableTreeNode.childNodes() = children().toList().mapNotNull { it as? DefaultMutableTreeNode } internal val DefaultMutableTreeNode.childNodes get() = children().toList().mapNotNull { it as? DefaultMutableTreeNode }
private fun List<DefaultMutableTreeNode>.findByFile(file: File) = find { it.userObjectAsFile() == file } private val JTree.selectedNode get() = lastSelectedPathComponent as? DefaultMutableTreeNode
private fun JTree.selectedNode() = lastSelectedPathComponent as? DefaultMutableTreeNode private fun List<DefaultMutableTreeNode>.findByFile(file: File) = find { it.file == file }
internal fun addToModelAndRefresh(model: DefaultTreeModel, internal fun addToModelAndRefresh(model: DefaultTreeModel,
child: DefaultMutableTreeNode, child: DefaultMutableTreeNode,

View File

@ -70,7 +70,7 @@ class FlowStackSnapshotFactoryImpl : FlowStackSnapshotFactory {
Frame(element, listOf()) Frame(element, listOf())
} }
} }
return FlowStackSnapshot(flowClass = flowClass, stackFrames = frames) return FlowStackSnapshot(flowClass = flowClass.name, stackFrames = frames)
} }
private fun getInstrumentedAnnotation(element: StackTraceElement): Instrumented? { private fun getInstrumentedAnnotation(element: StackTraceElement): Instrumented? {