diff --git a/core/src/main/kotlin/net/corda/core/flows/FlowStackSnapshot.kt b/core/src/main/kotlin/net/corda/core/flows/FlowStackSnapshot.kt index c1866adcc8..5ab6afd37c 100644 --- a/core/src/main/kotlin/net/corda/core/flows/FlowStackSnapshot.kt +++ b/core/src/main/kotlin/net/corda/core/flows/FlowStackSnapshot.kt @@ -57,7 +57,7 @@ private class FlowStackSnapshotDefaultFactory : FlowStackSnapshotFactory { */ data class FlowStackSnapshot constructor( val timestamp: Long = System.currentTimeMillis(), - val flowClass: Class<*>? = null, + val flowClass: String? = null, val stackFrames: List = listOf() ) { data class Frame( diff --git a/experimental/intellij-plugin/build.gradle b/experimental/intellij-plugin/build.gradle index 083a2e9dfe..1174bb03d3 100644 --- a/experimental/intellij-plugin/build.gradle +++ b/experimental/intellij-plugin/build.gradle @@ -26,6 +26,7 @@ intellij { } dependencies { + compile project(':core') // For JSON compile "com.fasterxml.jackson.core:jackson-databind:2.8.5" } diff --git a/experimental/intellij-plugin/src/main/kotlin/net/corda/ideaplugin/toolwindow/CordaFlowToolWindow.kt b/experimental/intellij-plugin/src/main/kotlin/net/corda/ideaplugin/toolwindow/CordaFlowToolWindow.kt index d87eddcd14..1e6fb1d9a0 100644 --- a/experimental/intellij-plugin/src/main/kotlin/net/corda/ideaplugin/toolwindow/CordaFlowToolWindow.kt +++ b/experimental/intellij-plugin/src/main/kotlin/net/corda/ideaplugin/toolwindow/CordaFlowToolWindow.kt @@ -8,7 +8,6 @@ import com.intellij.ui.components.JBScrollPane import com.intellij.ui.content.Content import com.intellij.ui.content.ContentFactory import com.intellij.ui.treeStructure.Tree -import net.corda.ideaplugin.module.CordaModuleType import java.awt.* import java.awt.event.MouseAdapter import java.awt.event.MouseEvent @@ -187,10 +186,10 @@ class CordaFlowToolWindow : ToolWindowFactory { private class SnapshotTreeRenderer : DefaultTreeCellRenderer() { 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) - val descriptor = (value as DefaultMutableTreeNode).userObject as SnapshotDataDescriptor + val descriptor = (value as DefaultMutableTreeNode).userObject as Descriptor icon = descriptor.icon - if (!descriptor.key.isNullOrEmpty()) { - text = "${descriptor.key}: ${descriptor.data.toString()}" + if (!descriptor.label.isNullOrEmpty()) { + text = descriptor.label + if (leaf) ": ${descriptor.value?.toString()}" else "" } return this } diff --git a/experimental/intellij-plugin/src/main/kotlin/net/corda/ideaplugin/toolwindow/FlowSnapshotTreeDataManager.kt b/experimental/intellij-plugin/src/main/kotlin/net/corda/ideaplugin/toolwindow/FlowSnapshotTreeDataManager.kt index 90953d01ce..d4d6f4ae05 100644 --- a/experimental/intellij-plugin/src/main/kotlin/net/corda/ideaplugin/toolwindow/FlowSnapshotTreeDataManager.kt +++ b/experimental/intellij-plugin/src/main/kotlin/net/corda/ideaplugin/toolwindow/FlowSnapshotTreeDataManager.kt @@ -3,6 +3,7 @@ package net.corda.ideaplugin.toolwindow import com.fasterxml.jackson.databind.JsonNode import com.fasterxml.jackson.databind.ObjectMapper import com.intellij.icons.AllIcons +import net.corda.core.flows.FlowStackSnapshot import java.io.File import javax.swing.Icon import javax.swing.JTree @@ -10,13 +11,6 @@ import javax.swing.tree.DefaultMutableTreeNode import javax.swing.tree.DefaultTreeModel 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 * tree model from it. @@ -27,7 +21,7 @@ class FlowSnapshotTreeDataManager(tree: JTree) { } // 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 private val snapshotModel = DefaultTreeModel(root) @@ -62,8 +56,8 @@ class FlowSnapshotTreeDataManager(tree: JTree) { * the model is updated accordingly. */ fun addNodeToSnapshotModel(snapshotFile: File) { - val insertionIndex = -(root.childNodes().map { - (it.userObject as SnapshotDataDescriptor).key + val insertionIndex = -(root.childNodes.map { + it.file?.name }.binarySearch(extractFileName(snapshotFile))) - 1 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. */ fun removeNodeFromSnapshotModel(snapshotFile: File) { - val node = root.childNodes().find { - (it.userObject as SnapshotDataDescriptor).data == extractFileName(snapshotFile) + val snapshotFileName = extractFileName(snapshotFile) + val node = root.childNodes.find { + it.file?.name == snapshotFileName } as MutableTreeNode? if (node != null) { snapshotModel.removeNodeFromParent(node) @@ -83,7 +78,23 @@ class FlowSnapshotTreeDataManager(tree: JTree) { } 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 { @@ -92,26 +103,26 @@ class FlowSnapshotTreeDataManager(tree: JTree) { private fun buildChildrenModel( node: JsonNode?, - parent: DefaultMutableTreeNode, key: String? = null, - insertionIndex: Int = -1) { + parent: DefaultMutableTreeNode, label: String? = null) { val child: DefaultMutableTreeNode if (node == null || !node.isContainerNode) { - child = DefaultMutableTreeNode(SnapshotDataDescriptor(node, AllIcons.Debugger.Db_primitive, key)) - addToModelAndRefresh(snapshotModel, child, parent, insertionIndex) + parent.add(DefaultMutableTreeNode(Descriptor(node, AllIcons.Debugger.Db_primitive, label))) } else { if (node.isArray) { - child = DefaultMutableTreeNode(SnapshotDataDescriptor(key, AllIcons.Debugger.Db_array)) - addToModelAndRefresh(snapshotModel, child, parent, insertionIndex) + child = DefaultMutableTreeNode(Descriptor(icon = AllIcons.Debugger.Db_array, label = label)) + parent.add(child) node.mapIndexed { index: Int, item: JsonNode? -> buildChildrenModel(item, child, index.toString()) } } else { - child = DefaultMutableTreeNode(SnapshotDataDescriptor(key, AllIcons.Json.Object)) - addToModelAndRefresh(snapshotModel, child, parent, insertionIndex) + child = DefaultMutableTreeNode(Descriptor(icon = AllIcons.Json.Object, label = label)) + parent.add(child) node.fields().forEach { buildChildrenModel(it.value, child, it.key) } } } } -} \ No newline at end of file +} + +class Descriptor(val value:Any? = null, val icon: Icon? = null, val label:String? = null) \ No newline at end of file diff --git a/experimental/intellij-plugin/src/main/kotlin/net/corda/ideaplugin/toolwindow/FlowTreeDataManager.kt b/experimental/intellij-plugin/src/main/kotlin/net/corda/ideaplugin/toolwindow/FlowTreeDataManager.kt index 82054a0b60..9dfaaf0a2e 100644 --- a/experimental/intellij-plugin/src/main/kotlin/net/corda/ideaplugin/toolwindow/FlowTreeDataManager.kt +++ b/experimental/intellij-plugin/src/main/kotlin/net/corda/ideaplugin/toolwindow/FlowTreeDataManager.kt @@ -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]. * 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.removeAllChildren() @@ -81,8 +81,8 @@ class FlowTreeDataManager(val tree: JTree, val snapshotModel: FlowSnapshotTreeDa } private fun isSelected(dir: File?): Boolean { - val node = tree.selectedNode() - return node != null && dir == node.userObjectAsFile() + val node = tree.selectedNode + return node != null && dir == node.file } private fun startWatching(dir: File) { @@ -100,18 +100,18 @@ class FlowTreeDataManager(val tree: JTree, val snapshotModel: FlowSnapshotTreeDa val parent = dir.parentFile if (parent.name == 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) { // 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 - flowModel.insertNodeInto(DefaultMutableTreeNode(dir), parentNode, findInsertionIndex(parentNode.childNodes(), dir.name)) + val parentNode = root.childNodes.findByFile(parent) ?: return + flowModel.insertNodeInto(DefaultMutableTreeNode(dir), parentNode, findInsertionIndex(parentNode.childNodes, dir.name)) startWatching(dir) } } private fun removeNodeFromFlowModel(dir: File) { - val selectedNode = tree.selectedNode() - if (selectedNode != null && selectedNode.userObjectAsFile() == dir) { + val selectedNode = tree.selectedNode + if (selectedNode != null && selectedNode.file == dir) { // Reload flows if the [dir] is currently selected loadFlows() return @@ -123,10 +123,10 @@ class FlowTreeDataManager(val tree: JTree, val snapshotModel: FlowSnapshotTreeDa parentNode = root } 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] - parentNode = root.childNodes().findByFile(parent) + parentNode = root.childNodes.findByFile(parent) } if (parentNode != null) { - val node = parentNode.childNodes().findByFile(dir) ?: return + val node = parentNode.childNodes.findByFile(dir) ?: return flowModel.removeNodeFromParent(node) } } @@ -206,10 +206,10 @@ class FlowTreeDataManager(val tree: JTree, val snapshotModel: FlowSnapshotTreeDa } } -internal fun DefaultMutableTreeNode.userObjectAsFile() = userObject as? File -internal fun DefaultMutableTreeNode.childNodes() = children().toList().mapNotNull { it as? DefaultMutableTreeNode } -private fun List.findByFile(file: File) = find { it.userObjectAsFile() == file } -private fun JTree.selectedNode() = lastSelectedPathComponent as? DefaultMutableTreeNode +internal val DefaultMutableTreeNode.file get() = userObject as? File +internal val DefaultMutableTreeNode.childNodes get() = children().toList().mapNotNull { it as? DefaultMutableTreeNode } +private val JTree.selectedNode get() = lastSelectedPathComponent as? DefaultMutableTreeNode +private fun List.findByFile(file: File) = find { it.file == file } internal fun addToModelAndRefresh(model: DefaultTreeModel, child: DefaultMutableTreeNode, diff --git a/test-utils/src/main/kotlin/net/corda/testing/FlowStackSnapshot.kt b/test-utils/src/main/kotlin/net/corda/testing/FlowStackSnapshot.kt index 13ec465284..1423ffdec8 100644 --- a/test-utils/src/main/kotlin/net/corda/testing/FlowStackSnapshot.kt +++ b/test-utils/src/main/kotlin/net/corda/testing/FlowStackSnapshot.kt @@ -70,7 +70,7 @@ class FlowStackSnapshotFactoryImpl : FlowStackSnapshotFactory { Frame(element, listOf()) } } - return FlowStackSnapshot(flowClass = flowClass, stackFrames = frames) + return FlowStackSnapshot(flowClass = flowClass.name, stackFrames = frames) } private fun getInstrumentedAnnotation(element: StackTraceElement): Instrumented? {