You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@groovy.apache.org by pa...@apache.org on 2022/07/08 00:30:07 UTC

[groovy] branch master updated: GROOVY-10684: Support opening the Object browser from within the AST browser

This is an automated email from the ASF dual-hosted git repository.

paulk pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/groovy.git


The following commit(s) were added to refs/heads/master by this push:
     new d39e644938 GROOVY-10684: Support opening the Object browser from within the AST browser
d39e644938 is described below

commit d39e644938f0152c4791a8fbb99f9b7d6bd107a6
Author: Paul King <pa...@asert.com.au>
AuthorDate: Fri Jul 8 01:39:22 2022 +1000

    GROOVY-10684: Support opening the Object browser from within the AST browser
---
 .../src/main/groovy/groovy/console/TextNode.groovy |   4 +-
 .../groovy/groovy/console/TextTreeNodeMaker.groovy |   6 +-
 .../groovy/groovy/console/ui/AstBrowser.groovy     | 110 +++++++++------------
 .../console/ui/ScriptToTreeNodeAdapter.groovy      |  20 ++--
 .../console/ui/ScriptToTreeNodeAdapterTest.groovy  |   2 +-
 5 files changed, 67 insertions(+), 75 deletions(-)

diff --git a/subprojects/groovy-console/src/main/groovy/groovy/console/TextNode.groovy b/subprojects/groovy-console/src/main/groovy/groovy/console/TextNode.groovy
index b61a748051..8621cf5284 100644
--- a/subprojects/groovy-console/src/main/groovy/groovy/console/TextNode.groovy
+++ b/subprojects/groovy-console/src/main/groovy/groovy/console/TextNode.groovy
@@ -26,7 +26,7 @@ import groovy.transform.CompileStatic
 @CompileStatic
 class TextNode {
     Object userObject
-    List<List<String>> properties
+    List<List<?>> properties
     TextNode parent
     List children
 
@@ -35,7 +35,7 @@ class TextNode {
         children = new ArrayList<TextNode>()
     }
 
-    TextNode(Object userObject, List<List<String>> properties) {
+    TextNode(Object userObject, List<List<?>> properties) {
         this(userObject)
         this.properties = properties
     }
diff --git a/subprojects/groovy-console/src/main/groovy/groovy/console/TextTreeNodeMaker.groovy b/subprojects/groovy-console/src/main/groovy/groovy/console/TextTreeNodeMaker.groovy
index 3bc9ef0468..9b6cd46ada 100644
--- a/subprojects/groovy-console/src/main/groovy/groovy/console/TextTreeNodeMaker.groovy
+++ b/subprojects/groovy-console/src/main/groovy/groovy/console/TextTreeNodeMaker.groovy
@@ -17,7 +17,7 @@
  *  under the License.
  */
 /**
- * A factory class for plain text nodes for use in the AST tree made by ASTBrowser
+ * A factory class for text nodes for use in the AST tree made by ASTBrowser
  */
 package groovy.console
 
@@ -30,7 +30,7 @@ class TextTreeNodeMaker implements AstBrowserNodeMaker<TextNode> {
         new TextNode(userObject)
     }
 
-    TextNode makeNodeWithProperties(Object userObject, List<List<String>> properties) {
+    TextNode makeNodeWithProperties(Object userObject, List<List<?>> properties) {
         new TextNode(userObject, properties)
     }
-}
\ No newline at end of file
+}
diff --git a/subprojects/groovy-console/src/main/groovy/groovy/console/ui/AstBrowser.groovy b/subprojects/groovy-console/src/main/groovy/groovy/console/ui/AstBrowser.groovy
index df323db0ce..7f9a9a4055 100644
--- a/subprojects/groovy-console/src/main/groovy/groovy/console/ui/AstBrowser.groovy
+++ b/subprojects/groovy-console/src/main/groovy/groovy/console/ui/AstBrowser.groovy
@@ -32,7 +32,6 @@ import org.objectweb.asm.util.ASMifier
 import org.objectweb.asm.util.TraceClassVisitor
 
 import javax.swing.Action
-import javax.swing.JFrame
 import javax.swing.JSplitPane
 import javax.swing.KeyStroke
 import javax.swing.UIManager
@@ -43,10 +42,11 @@ import javax.swing.tree.DefaultMutableTreeNode
 import javax.swing.tree.DefaultTreeModel
 import javax.swing.tree.TreeNode
 import javax.swing.tree.TreeSelectionModel
-import java.awt.BorderLayout
 import java.awt.Cursor
 import java.awt.Font
 import java.awt.event.KeyEvent
+import java.awt.event.MouseAdapter
+import java.awt.event.MouseEvent
 import java.util.prefs.Preferences
 import java.util.regex.Pattern
 
@@ -56,6 +56,7 @@ import static java.awt.GridBagConstraints.NONE
 import static java.awt.GridBagConstraints.NORTHEAST
 import static java.awt.GridBagConstraints.NORTHWEST
 import static java.awt.GridBagConstraints.WEST
+import static javax.swing.ListSelectionModel.SINGLE_SELECTION
 
 /**
  * This object is a GUI for looking at the AST that Groovy generates.
@@ -193,13 +194,23 @@ class AstBrowser {
                                     model: new DefaultTreeModel(new DefaultMutableTreeNode('Loading...'))) {}
                         },
                         rightComponent: scrollPane {
-                            propertyTable = table {
+                            propertyTable = table(selectionMode: SINGLE_SELECTION) {
                                 tableModel(list: [[:]]) {
                                     propertyColumn(header: 'Name', propertyName: 'name')
-                                    propertyColumn(header: 'Value', propertyName: 'value')
+                                    propertyColumn(header: 'Value  (double-click to browse)', propertyName: 'value')
                                     propertyColumn(header: 'Type', propertyName: 'type')
+                                    propertyColumn(header: 'Raw', propertyName: 'raw')
                                 }
                             }
+                            propertyTable.columnModel.getColumn(3).with {
+                                minWidth = 0
+                                maxWidth = 0
+                                width = 0
+                            }
+                            propertyTable.addMouseListener(makeClickAdapter(propertyTable, 3) { row ->
+                                'Browsing ' + jTree.lastSelectedPathComponent.userObject + ": " + propertyTable.model.getValueAt(row, 0)
+                            })
+                            propertyTable.setDefaultEditor(Object, null)
                         }
                 ) {}
                 mainSplitter = splitPane(
@@ -225,51 +236,11 @@ class AstBrowser {
         jTree.addTreeSelectionListener({ TreeSelectionEvent e ->
 
             propertyTable.model.rows.clear()
-            propertyTable.columnModel.getColumn(1).cellRenderer = new ButtonOrDefaultRenderer()
-            propertyTable.columnModel.getColumn(1).cellEditor = new ButtonOrTextEditor()
             TreeNode node = jTree.lastSelectedPathComponent
             if (node instanceof TreeNodeWithProperties) {
-                def titleSuffix = node.properties.find { it[0] == 'text' }?.get(1)
-                for (it in node.properties) {
-                    def propList = it
-                    if (propList[2] == "ListHashMap" && propList[1] != 'null' && propList[1] != '[:]') {
-                        //If the class is a ListHashMap, make it accessible in a new frame through a button
-                        def kvPairs = propList[1].substring(1, propList[1].length() - 1).tokenize(',')
-                        def kvFirst = kvPairs.get(0)
-                        def btnPanel = swing.button(
-                                text: "Key/value pairs: [" + kvFirst.substring(0, Math.min(25, kvFirst.size())) + "...]",
-                                actionPerformed: {
-                                    def mapTable
-                                    String title = titleSuffix ? propList[0] + " (" + titleSuffix + ")" : propList[0]
-                                    def props = swing.frame(title: title, defaultCloseOperation: JFrame.DISPOSE_ON_CLOSE,
-                                            show: true, locationRelativeTo: null) {
-                                        lookAndFeel 'system'
-                                        borderLayout(vgap: 5)
-                                        panel(constraints: BorderLayout.CENTER) {
-                                            borderLayout()
-                                            scrollPane {
-                                                mapTable = swing.table {
-                                                    tableModel(list: [[:]]) {
-                                                        propertyColumn(header: 'Name', propertyName: 'name')
-                                                        propertyColumn(header: 'Value', propertyName: 'value')
-                                                    }
-                                                }
-                                            }
-                                        }
-                                    }
-                                    mapTable.model.rows.clear()
-                                    kvPairs.each {
-                                        def kv = it.tokenize(':')
-                                        if (kv)
-                                            mapTable.model.rows << ["name": kv[0], "value": kv[1]]
-                                    }
-                                    props.pack()
-                                })
-                        propertyTable.model.rows << ["name": propList[0], "value": btnPanel, "type": propList[2]]
-                        btnPanel.updateUI()
-                    } else {
-                        propertyTable.model.rows << ["name": it[0], "value": it[1], "type": it[2]]
-                    }
+                def titleSuffix = node.properties.find { list -> list[0] == 'text' }?.get(1)
+                for (list in node.properties) {
+                    propertyTable.model.rows << [name: list[0], value: list[1], type: list[2], raw: list[3]]
                 }
 
                 if (inputArea && rootElement) {
@@ -356,6 +327,22 @@ class AstBrowser {
         return sw.toString()
     }
 
+    def makeClickAdapter(table, int valueCol, Closure pathClosure) {
+        new MouseAdapter() {
+            void mouseClicked(MouseEvent e) {
+                if (e.clickCount == 2) {
+                    def selectedRow = table.selectedRow
+                    if (selectedRow != -1) {
+                        def value = table.model.getValueAt(selectedRow, valueCol)
+                        if (value != null) {
+                            ObjectBrowser.inspect(value, pathClosure(selectedRow))
+                        }
+                    }
+                }
+            }
+        }
+    }
+
     private void showSource(view, String source, boolean showOnlyMethodCode, boolean isMethodNameAndMethodDescriptorAvailable, getPatternStr) {
         swing.doLater {
             view.textEditor.text = source
@@ -401,7 +388,7 @@ class AstBrowser {
 
     void showAbout(EventObject evt) {
         def pane = swing.optionPane()
-        def version = GroovySystem.getVersion()
+        def version = GroovySystem.version
         pane.setMessage('An interactive GUI to explore AST capabilities\nVersion ' + version)
         def dialog = pane.createDialog(frame, 'About Groovy AST Browser')
         dialog.pack()
@@ -439,8 +426,7 @@ class AstBrowser {
     }
 
     void decompile(phaseId, source) {
-
-        decompiledSource.textEditor.setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR))
+        decompiledSource.textEditor.cursor = Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR)
         decompiledSource.textEditor.text = 'Loading...'
 
         swing.doOutside {
@@ -449,14 +435,14 @@ class AstBrowser {
                 String result = new AstNodeToScriptAdapter().compileToScript(source, phaseId, classLoader, showScriptFreeForm, showScriptClass, config)
                 swing.doLater {
                     decompiledSource.textEditor.text = result
-                    decompiledSource.textEditor.setCaretPosition(0)
-                    decompiledSource.textEditor.setCursor(Cursor.defaultCursor)
+                    decompiledSource.textEditor.caretPosition = 0
+                    decompiledSource.textEditor.cursor = Cursor.defaultCursor
                 }
             } catch (Throwable t) {
                 swing.doLater {
                     decompiledSource.textEditor.text = t.getMessage()
-                    decompiledSource.textEditor.setCaretPosition(0)
-                    decompiledSource.textEditor.setCursor(Cursor.defaultCursor)
+                    decompiledSource.textEditor.caretPosition = 0
+                    decompiledSource.textEditor.cursor = Cursor.defaultCursor
                 }
                 throw t
             }
@@ -479,13 +465,13 @@ class AstBrowser {
                 classLoader.clearBytecodeTable()
                 def result = adapter.compile(script, compilePhase)
                 swing.doLater {
-                    model.setRoot(result)
+                    model.root = result
                     model.reload()
-                    jTree.setCursor(Cursor.defaultCursor)
+                    jTree.cursor = Cursor.defaultCursor
                 }
             } catch (Throwable t) {
                 swing.doLater {
-                    jTree.setCursor(Cursor.defaultCursor)
+                    jTree.cursor = Cursor.defaultCursor
                 }
                 throw t
             }
@@ -582,20 +568,20 @@ enum CompilePhaseAdapter {
 @CompileStatic
 class TreeNodeWithProperties extends DefaultMutableTreeNode {
 
-    List<List<String>> properties
+    List<List<?>> properties
 
     /**
      * Creates a tree node and attaches properties to it.
      * @param userObject same as a DefaultMutableTreeNode requires
      * @param properties a list of String lists
      */
-    TreeNodeWithProperties(userObject, List<List<String>> properties) {
+    TreeNodeWithProperties(userObject, List<List<?>> properties) {
         super(userObject)
         this.properties = properties
     }
 
     String getPropertyValue(String name) {
-        def match = properties.find { n, v, t -> name == n }
+        def match = properties.find { List list -> name == list[0] }
         return match != null ? match[1] : null
     }
 
@@ -615,7 +601,7 @@ class TreeNodeWithProperties extends DefaultMutableTreeNode {
 interface AstBrowserNodeMaker<T> {
     T makeNode(Object userObject)
 
-    T makeNodeWithProperties(Object userObject, List<List<String>> properties)
+    T makeNodeWithProperties(Object userObject, List<List<?>> properties)
 }
 
 /**
@@ -627,7 +613,7 @@ class SwingTreeNodeMaker implements AstBrowserNodeMaker<DefaultMutableTreeNode>
         new DefaultMutableTreeNode(userObject)
     }
 
-    DefaultMutableTreeNode makeNodeWithProperties(Object userObject, List<List<String>> properties) {
+    DefaultMutableTreeNode makeNodeWithProperties(Object userObject, List<List<?>> properties) {
         new TreeNodeWithProperties(userObject, properties)
     }
 }
diff --git a/subprojects/groovy-console/src/main/groovy/groovy/console/ui/ScriptToTreeNodeAdapter.groovy b/subprojects/groovy-console/src/main/groovy/groovy/console/ui/ScriptToTreeNodeAdapter.groovy
index 411af2c354..17f91de468 100644
--- a/subprojects/groovy-console/src/main/groovy/groovy/console/ui/ScriptToTreeNodeAdapter.groovy
+++ b/subprojects/groovy-console/src/main/groovy/groovy/console/ui/ScriptToTreeNodeAdapter.groovy
@@ -195,45 +195,51 @@ class ScriptToTreeNodeAdapter {
     }
 
     def make(node) {
-        nodeMaker.makeNodeWithProperties(getStringForm(node), getPropertyTable(node))
+        makeWithTable(node, getPropertyTable(node))
     }
 
     def make(MethodNode node) {
         def table = getPropertyTable(node)
         extendMethodNodePropertyTable(table, node)
+        makeWithTable(node, table)
+    }
 
+    private makeWithTable(node, List<List<?>> table) {
         nodeMaker.makeNodeWithProperties(getStringForm(node), table)
     }
 
     /**
      * Extends the method node property table by adding custom properties.
      */
-    void extendMethodNodePropertyTable(List<List<String>> table, MethodNode node) {
-        table << ['descriptor', BytecodeHelper.getMethodDescriptor(node), 'String']
+    void extendMethodNodePropertyTable(List<List<?>> table, MethodNode node) {
+        def descriptor = BytecodeHelper.getMethodDescriptor(node)
+        table << ['descriptor', descriptor, 'String', descriptor]
     }
 
     /**
      * Creates the property table for the node so that the properties view can display nicely.
      */
-    private List<List<String>> getPropertyTable(node) {
+    private List<List<?>> getPropertyTable(node) {
         node.metaClass.properties.findResults { mp ->
             if (mp instanceof MetaBeanProperty && mp.getter) {
                 String name = mp.name, type = mp.type.simpleName
+                Object valueAsString = null
                 Object value = null
                 try {
                     // multiple assignment statements cannot be cast to VariableExpression so
                     // instead reference the value through the leftExpression property, which is the same
                     if (node instanceof DeclarationExpression && (name == 'variableExpression' || name == 'tupleExpression')) {
-                        value = toString(node.leftExpression)
+                        value = node.leftExpression
                     } else {
-                        value = toString(mp.getProperty(node))
+                        value = mp.getProperty(node)
                     }
+                    valueAsString = toString(value)
                 } catch (GroovyBugError ignore) {
                     // compiler throws error if it thinks a field is being accessed
                     // before it is set under certain conditions. It wasn't designed
                     // to be walked reflectively like this.
                 }
-                [name, value, type]
+                [name, valueAsString, type, value]
             }
         }.sort { list -> list[0] } // sort by name
     }
diff --git a/subprojects/groovy-console/src/test/groovy/groovy/console/ui/ScriptToTreeNodeAdapterTest.groovy b/subprojects/groovy-console/src/test/groovy/groovy/console/ui/ScriptToTreeNodeAdapterTest.groovy
index 6538e9e48a..20e3ee5b56 100644
--- a/subprojects/groovy-console/src/test/groovy/groovy/console/ui/ScriptToTreeNodeAdapterTest.groovy
+++ b/subprojects/groovy-console/src/test/groovy/groovy/console/ui/ScriptToTreeNodeAdapterTest.groovy
@@ -514,7 +514,7 @@ final class ScriptToTreeNodeAdapterTest extends GroovyTestCase {
         def methods = classNodeTest.children().find { it.toString() == 'Methods' }
         def methodNodeTest = methods.children().find { it.toString() == 'MethodNode - test' }
 
-        assert methodNodeTest.properties.any { name, value, type -> name == 'descriptor' && value == '()V' && type == 'String' }
+        assert methodNodeTest.properties.any { name, value, type, _ -> name == 'descriptor' && value == '()V' && type == 'String' }
     }
 
     void testScriptWithAdapterThatLoadsGeneratedClosureClass() {