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 12:56:27 UTC
[groovy] 01/02: 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 GROOVY_4_0_X
in repository https://gitbox.apache.org/repos/asf/groovy.git
commit 4d7fefaf9f61d25be3751ce31e1a6e1ce5576dd2
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() {