Repository: groovy Updated Branches: refs/heads/parrot a183a0cf3 -> aaf704fdc
GROOVY-5471: Add "indy" option to Groovy Console (and AstBrowser) (closes #475) Project: http://git-wip-us.apache.org/repos/asf/groovy/repo Commit: http://git-wip-us.apache.org/repos/asf/groovy/commit/90fe6d27 Tree: http://git-wip-us.apache.org/repos/asf/groovy/tree/90fe6d27 Diff: http://git-wip-us.apache.org/repos/asf/groovy/diff/90fe6d27 Branch: refs/heads/parrot Commit: 90fe6d2777c27ecc1149bee1d1e32359fa33e3c4 Parents: 048b658 Author: John Wagenleitner <jwagenleit...@apache.org> Authored: Sun Jan 22 11:02:50 2017 -0800 Committer: John Wagenleitner <jwagenleit...@apache.org> Committed: Sun Jan 22 11:02:50 2017 -0800 ---------------------------------------------------------------------- .../groovy/inspect/swingui/AstBrowser.groovy | 50 ++++++++++++++++---- .../swingui/ScriptToTreeNodeAdapter.groovy | 10 +++- .../src/main/groovy/groovy/ui/Console.groovy | 45 +++++++++++++++++- .../main/groovy/groovy/ui/ConsoleActions.groovy | 7 +++ .../groovy/groovy/ui/view/BasicMenuBar.groovy | 1 + .../main/resources/groovy/ui/Console.properties | 2 + .../src/spec/doc/groovy-console.adoc | 3 ++ .../swingui/ScriptToTreeNodeAdapterTest.groovy | 17 +++++++ .../groovy/swing/SwingBuilderConsoleTest.groovy | 48 +++++++++++++++++++ 9 files changed, 171 insertions(+), 12 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/groovy/blob/90fe6d27/subprojects/groovy-console/src/main/groovy/groovy/inspect/swingui/AstBrowser.groovy ---------------------------------------------------------------------- diff --git a/subprojects/groovy-console/src/main/groovy/groovy/inspect/swingui/AstBrowser.groovy b/subprojects/groovy-console/src/main/groovy/groovy/inspect/swingui/AstBrowser.groovy index faabed1..55d7647 100644 --- a/subprojects/groovy-console/src/main/groovy/groovy/inspect/swingui/AstBrowser.groovy +++ b/subprojects/groovy-console/src/main/groovy/groovy/inspect/swingui/AstBrowser.groovy @@ -27,6 +27,7 @@ import org.codehaus.groovy.control.SourceUnit import org.objectweb.asm.ClassReader import org.objectweb.asm.util.TraceClassVisitor +import javax.swing.Action import javax.swing.JFrame import javax.swing.JSplitPane import javax.swing.KeyStroke @@ -60,10 +61,13 @@ import static java.awt.GridBagConstraints.WEST class AstBrowser { + private static final String BYTECODE_MSG_SELECT_NODE = '// Please select a class node in the tree view.' + private inputArea, rootElement, decompiledSource, jTree, propertyTable, splitterPane, mainSplitter, bytecodeView - boolean showScriptFreeForm, showScriptClass, showClosureClasses, showTreeView + boolean showScriptFreeForm, showScriptClass, showClosureClasses, showTreeView, showIndyBytecode GeneratedBytecodeAwareGroovyClassLoader classLoader def prefs = new AstBrowserUiPreferences() + Action refreshAction AstBrowser(inputArea, rootElement, classLoader) { this.inputArea = inputArea @@ -102,13 +106,14 @@ class AstBrowser { showScriptClass = prefs.showScriptClass showClosureClasses = prefs.showClosureClasses showTreeView = prefs.showTreeView + showIndyBytecode = prefs.showIndyBytecode frame = swing.frame(title: 'Groovy AST Browser' + (name ? " - $name" : ''), location: prefs.frameLocation, size: prefs.frameSize, iconImage: swing.imageIcon(groovy.ui.Console.ICON_PATH).image, defaultCloseOperation: WindowConstants.DISPOSE_ON_CLOSE, - windowClosing: { event -> prefs.save(frame, splitterPane, mainSplitter, showScriptFreeForm, showScriptClass, showClosureClasses, phasePicker.selectedItem, showTreeView) }) { + windowClosing: { event -> prefs.save(frame, splitterPane, mainSplitter, showScriptFreeForm, showScriptClass, showClosureClasses, phasePicker.selectedItem, showTreeView, showIndyBytecode) }) { menuBar { menu(text: 'Show Script', mnemonic: 'S') { @@ -128,12 +133,16 @@ class AstBrowser { action(name: 'Tree View', closure: this.&showTreeView, mnemonic: 'T') } + checkBoxMenuItem(selected: showIndyBytecode) { + action(name: 'Generate Indy Bytecode', closure: this.&showIndyBytecode, + mnemonic: 'I') + } } menu(text: 'View', mnemonic: 'V') { menuItem {action(name: 'Larger Font', closure: this.&largerFont, mnemonic: 'L', accelerator: shortcut('shift L'))} menuItem {action(name: 'Smaller Font', closure: this.&smallerFont, mnemonic: 'S', accelerator: shortcut('shift S'))} menuItem { - action(name: 'Refresh', closure: { + refreshAction = action(name: 'Refresh', closure: { decompile(phasePicker.selectedItem.phaseId, script()) compile(jTree, script(), phasePicker.selectedItem.phaseId) }, mnemonic: 'R', accelerator: KeyStroke.getKeyStroke(KeyEvent.VK_F5, 0)) @@ -151,7 +160,7 @@ class AstBrowser { selectedItem: prefs.selectedPhase, actionPerformed: { // reset text to the default as the phase change removes the focus from the class node - bytecodeView.textEditor.text = '// Please select a class node in the tree view.' + bytecodeView.textEditor.text = BYTECODE_MSG_SELECT_NODE decompile(phasePicker.selectedItem.phaseId, script()) compile(jTree, script(), phasePicker.selectedItem.phaseId) @@ -185,14 +194,14 @@ class AstBrowser { topComponent: splitterPane, bottomComponent: tabbedPane { widget(decompiledSource = new groovy.ui.ConsoleTextEditor(editable: false, showLineNumbers: false), title:'Source') - widget(bytecodeView = new groovy.ui.ConsoleTextEditor(editable: false, showLineNumbers: false), title:'Bytecode') + widget(bytecodeView = new groovy.ui.ConsoleTextEditor(editable: false, showLineNumbers: false), title:getByteCodeTitle()) }, constraints: gbc(gridx: 0, gridy: 2, gridwidth: 3, gridheight: 1, weightx: 1.0, weighty: 1.0, anchor: NORTHWEST, fill: BOTH, insets: [2, 2, 2, 2])) { } } } - bytecodeView.textEditor.text = '// Please select a class node in the tree view.' + bytecodeView.textEditor.text = BYTECODE_MSG_SELECT_NODE propertyTable.model.rows.clear() //for some reason this suppress an empty row @@ -383,6 +392,28 @@ class AstBrowser { } } + void showIndyBytecode(EventObject evt = null) { + showIndyBytecode = evt.source.selected + bytecodeView.textEditor.text = BYTECODE_MSG_SELECT_NODE + refreshAction.actionPerformed(null) + updateByteCodeTabTitle() + } + + private void updateByteCodeTabTitle() { + def tabPane = mainSplitter.bottomComponent + int tabCount = tabPane.getTabCount() + for (int i = 0; i < tabCount; i++) { + if (bytecodeView.is(tabPane.getComponentAt(i))) { + tabPane.setTitleAt(i, getByteCodeTitle()) + break + } + } + } + + private String getByteCodeTitle() { + 'Bytecode' + (showIndyBytecode ? ' (Indy)' : '') + } + void decompile(phaseId, source) { decompiledSource.textEditor.setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR)) @@ -422,7 +453,7 @@ class AstBrowser { def nodeMaker = new SwingTreeNodeMaker() def adapter = new ScriptToTreeNodeAdapter(classLoader, showScriptFreeForm, showScriptClass, showClosureClasses, nodeMaker) classLoader.clearBytecodeTable() - def result = adapter.compile(script, compilePhase) + def result = adapter.compile(script, compilePhase, showIndyBytecode) swing.doLater { model.setRoot(result) model.reload() @@ -451,6 +482,7 @@ class AstBrowserUiPreferences { final boolean showTreeView final boolean showScriptClass final boolean showClosureClasses + final boolean showIndyBytecode int decompiledSourceFontSize final CompilePhaseAdapter selectedPhase @@ -470,13 +502,14 @@ class AstBrowserUiPreferences { showScriptClass = prefs.getBoolean('showScriptClass', true) showClosureClasses = prefs.getBoolean('showClosureClasses', false) showTreeView = prefs.getBoolean('showTreeView', true) + showIndyBytecode = prefs.getBoolean('showIndyBytecode', false) int phase = prefs.getInt('compilerPhase', Phases.SEMANTIC_ANALYSIS) selectedPhase = CompilePhaseAdapter.values().find { it.phaseId == phase } } - def save(frame, vSplitter, hSplitter, scriptFreeFormPref, scriptClassPref, closureClassesPref, CompilePhaseAdapter phase, showTreeView) { + def save(frame, vSplitter, hSplitter, scriptFreeFormPref, scriptClassPref, closureClassesPref, CompilePhaseAdapter phase, showTreeView, showIndyBytecode) { Preferences prefs = Preferences.userNodeForPackage(AstBrowserUiPreferences) prefs.putInt('decompiledFontSize', decompiledSourceFontSize as int) prefs.putInt('frameX', frame.location.x as int) @@ -489,6 +522,7 @@ class AstBrowserUiPreferences { prefs.putBoolean('showScriptClass', scriptClassPref) prefs.putBoolean('showClosureClasses', closureClassesPref) prefs.putBoolean('showTreeView', showTreeView) + prefs.putBoolean('showIndyBytecode', showIndyBytecode) prefs.putInt('compilerPhase', phase.phaseId) } } http://git-wip-us.apache.org/repos/asf/groovy/blob/90fe6d27/subprojects/groovy-console/src/main/groovy/groovy/inspect/swingui/ScriptToTreeNodeAdapter.groovy ---------------------------------------------------------------------- diff --git a/subprojects/groovy-console/src/main/groovy/groovy/inspect/swingui/ScriptToTreeNodeAdapter.groovy b/subprojects/groovy-console/src/main/groovy/groovy/inspect/swingui/ScriptToTreeNodeAdapter.groovy index e39e1b0..a3ab9a6 100644 --- a/subprojects/groovy-console/src/main/groovy/groovy/inspect/swingui/ScriptToTreeNodeAdapter.groovy +++ b/subprojects/groovy-console/src/main/groovy/groovy/inspect/swingui/ScriptToTreeNodeAdapter.groovy @@ -98,11 +98,17 @@ class ScriptToTreeNodeAdapter { * a Groovy script in String form * @param compilePhase * the int based CompilePhase to compile it to. + * @param indy + * if {@code true} InvokeDynamic (Indy) bytecode is generated */ - def compile(String script, int compilePhase) { + def compile(String script, int compilePhase, boolean indy=false) { def scriptName = 'script' + System.currentTimeMillis() + '.groovy' GroovyCodeSource codeSource = new GroovyCodeSource(script, scriptName, '/groovy/script') - CompilationUnit cu = new CompilationUnit(CompilerConfiguration.DEFAULT, codeSource.codeSource, classLoader) + CompilerConfiguration cc = new CompilerConfiguration(CompilerConfiguration.DEFAULT) + if (indy) { + cc.getOptimizationOptions().put(CompilerConfiguration.INVOKEDYNAMIC, true) + } + CompilationUnit cu = new CompilationUnit(cc, codeSource.codeSource, classLoader) cu.setClassgenCallback(classLoader.createCollector(cu, null)) TreeNodeBuildingNodeOperation operation = new TreeNodeBuildingNodeOperation(this, showScriptFreeForm, showScriptClass, showClosureClasses) http://git-wip-us.apache.org/repos/asf/groovy/blob/90fe6d27/subprojects/groovy-console/src/main/groovy/groovy/ui/Console.groovy ---------------------------------------------------------------------- diff --git a/subprojects/groovy-console/src/main/groovy/groovy/ui/Console.groovy b/subprojects/groovy-console/src/main/groovy/groovy/ui/Console.groovy index 6c3ce54..9db505b 100644 --- a/subprojects/groovy-console/src/main/groovy/groovy/ui/Console.groovy +++ b/subprojects/groovy-console/src/main/groovy/groovy/ui/Console.groovy @@ -125,6 +125,9 @@ class Console implements CaretListener, HyperlinkListener, ComponentListener, Fo boolean saveOnRun = prefs.getBoolean('saveOnRun', false) Action saveOnRunAction + boolean indy = prefs.getBoolean('indy', false) + Action indyAction + //to allow loading classes dynamically when using @Grab (GROOVY-4877, GROOVY-5871) boolean useScriptClassLoaderForScriptExecution = false @@ -206,6 +209,7 @@ class Console implements CaretListener, HyperlinkListener, ComponentListener, Fo h(longOpt: 'help', messages['cli.option.help.description']) V(longOpt: 'version', messages['cli.option.version.description']) pa(longOpt: 'parameters', messages['cli.option.parameters.description']) + i(longOpt: 'indy', messages['cli.option.indy.description']) } OptionAccessor options = cli.parse(args) @@ -233,6 +237,10 @@ class Console implements CaretListener, HyperlinkListener, ComponentListener, Fo def baseConfig = new CompilerConfiguration() baseConfig.setParameters((boolean) options.hasOption("pa")) + if (options.i) { + enableIndy(baseConfig) + } + def console = new Console(Console.class.classLoader?.getRootLoader(), new Binding(), baseConfig) console.useScriptClassLoaderForScriptExecution = true console.run() @@ -260,6 +268,10 @@ class Console implements CaretListener, HyperlinkListener, ComponentListener, Fo Console(ClassLoader parent, Binding binding, CompilerConfiguration baseConfig) { this.baseConfig = baseConfig + indy = indy || isIndyEnabled(baseConfig) + if (indy) { + enableIndy(baseConfig) + } newScript(parent, binding); try { System.setProperty('groovy.full.stacktrace', System.getProperty('groovy.full.stacktrace', @@ -360,6 +372,7 @@ class Console implements CaretListener, HyperlinkListener, ComponentListener, Fo swing.consoleFrame.show() } installInterceptor() + updateTitle() // Title changes based on indy setting swing.doLater inputArea.&requestFocus } @@ -966,6 +979,30 @@ class Console implements CaretListener, HyperlinkListener, ComponentListener, Fo prefs.putBoolean('saveOnRun', saveOnRun) } + void indy(EventObject evt = null) { + indy = evt.source.selected + prefs.putBoolean('indy', indy) + if (indy) { + enableIndy(baseConfig) + } else { + disableIndy(baseConfig) + } + updateTitle() + newScript(shell.classLoader, shell.context) + } + + private static void enableIndy(CompilerConfiguration cc) { + cc.getOptimizationOptions().put(CompilerConfiguration.INVOKEDYNAMIC, true) + } + + private static void disableIndy(CompilerConfiguration cc) { + cc.getOptimizationOptions().remove(CompilerConfiguration.INVOKEDYNAMIC) + } + + private static boolean isIndyEnabled(CompilerConfiguration cc) { + cc.getOptimizationOptions().get(CompilerConfiguration.INVOKEDYNAMIC) + } + void runSelectedScript(EventObject evt = null) { runScriptImpl(true) } @@ -1340,10 +1377,14 @@ class Console implements CaretListener, HyperlinkListener, ComponentListener, Fo void updateTitle() { if (frame.properties.containsKey('title')) { + String title = 'GroovyConsole' + if (indy) { + title += ' (Indy)' + } if (scriptFile != null) { - frame.title = scriptFile.name + (dirty?' * ':'') + ' - GroovyConsole' + frame.title = scriptFile.name + (dirty?' * ':'') + ' - ' + title } else { - frame.title = 'GroovyConsole' + frame.title = title } } } http://git-wip-us.apache.org/repos/asf/groovy/blob/90fe6d27/subprojects/groovy-console/src/main/groovy/groovy/ui/ConsoleActions.groovy ---------------------------------------------------------------------- diff --git a/subprojects/groovy-console/src/main/groovy/groovy/ui/ConsoleActions.groovy b/subprojects/groovy-console/src/main/groovy/groovy/ui/ConsoleActions.groovy index 86d450b..ddb2d3d 100644 --- a/subprojects/groovy-console/src/main/groovy/groovy/ui/ConsoleActions.groovy +++ b/subprojects/groovy-console/src/main/groovy/groovy/ui/ConsoleActions.groovy @@ -400,3 +400,10 @@ selectBlockAction = action( accelerator: shortcut('B'), shortDescription: 'Selects current Word, Line or Block in Script' ) + +indyAction = action( + name: 'Enable Indy Compilation', + closure: controller.&indy, + mnemonic: 'I', + shortDescription: 'Enable InvokeDynamic (Indy) compilation for scripts' +) http://git-wip-us.apache.org/repos/asf/groovy/blob/90fe6d27/subprojects/groovy-console/src/main/groovy/groovy/ui/view/BasicMenuBar.groovy ---------------------------------------------------------------------- diff --git a/subprojects/groovy-console/src/main/groovy/groovy/ui/view/BasicMenuBar.groovy b/subprojects/groovy-console/src/main/groovy/groovy/ui/view/BasicMenuBar.groovy index b667a75..dc169cf 100644 --- a/subprojects/groovy-console/src/main/groovy/groovy/ui/view/BasicMenuBar.groovy +++ b/subprojects/groovy-console/src/main/groovy/groovy/ui/view/BasicMenuBar.groovy @@ -83,6 +83,7 @@ menuBar { checkBoxMenuItem(threadInterruptAction, selected: controller.threadInterrupt) menuItem(interruptAction) menuItem(compileAction) + checkBoxMenuItem(indyAction, selected: controller.indy) separator() menuItem(addClasspathJar) menuItem(addClasspathDir) http://git-wip-us.apache.org/repos/asf/groovy/blob/90fe6d27/subprojects/groovy-console/src/main/resources/groovy/ui/Console.properties ---------------------------------------------------------------------- diff --git a/subprojects/groovy-console/src/main/resources/groovy/ui/Console.properties b/subprojects/groovy-console/src/main/resources/groovy/ui/Console.properties index a9a94ee..0793ebc 100644 --- a/subprojects/groovy-console/src/main/resources/groovy/ui/Console.properties +++ b/subprojects/groovy-console/src/main/resources/groovy/ui/Console.properties @@ -26,4 +26,6 @@ cli.option.classpath.description=Specify where to find the class files - must be cli.option.parameters.description=Generate metadata for reflection on method parameter names (jdk8+ only) +cli.option.indy.description=Enable InvokeDynamic (Indy) compilation for scripts + cli.info.version=GroovyConsole {0} http://git-wip-us.apache.org/repos/asf/groovy/blob/90fe6d27/subprojects/groovy-console/src/spec/doc/groovy-console.adoc ---------------------------------------------------------------------- diff --git a/subprojects/groovy-console/src/spec/doc/groovy-console.adoc b/subprojects/groovy-console/src/spec/doc/groovy-console.adoc index 40b8e3a..c55a266 100644 --- a/subprojects/groovy-console/src/spec/doc/groovy-console.adoc +++ b/subprojects/groovy-console/src/spec/doc/groovy-console.adoc @@ -60,6 +60,7 @@ usage: groovyConsole [options] [filename] -h,--help Display this help message -pa,--parameters Generate metadata for reflection on method parameter names (jdk8+ only) + -i,--indy Enable InvokeDynamic (Indy) compilation for scripts -V,--version Display the version ----------------------------------------------------------------- @@ -137,6 +138,8 @@ being run by adding a new JAR or a directory to the classpath from the `Script` menu * Error hyperlinking from the output area when a compilation error is expected or when an exception is thrown +* You can enable InvokeDynamic (Indy) compilation mode by selecting +`Enable Indy Compilation` from the `Script` menu [[GroovyConsole-EmbeddingtheConsole]] == Embedding the Console http://git-wip-us.apache.org/repos/asf/groovy/blob/90fe6d27/subprojects/groovy-console/src/test/groovy/groovy/inspect/swingui/ScriptToTreeNodeAdapterTest.groovy ---------------------------------------------------------------------- diff --git a/subprojects/groovy-console/src/test/groovy/groovy/inspect/swingui/ScriptToTreeNodeAdapterTest.groovy b/subprojects/groovy-console/src/test/groovy/groovy/inspect/swingui/ScriptToTreeNodeAdapterTest.groovy index 1a6d9c1..371d8b4 100644 --- a/subprojects/groovy-console/src/test/groovy/groovy/inspect/swingui/ScriptToTreeNodeAdapterTest.groovy +++ b/subprojects/groovy-console/src/test/groovy/groovy/inspect/swingui/ScriptToTreeNodeAdapterTest.groovy @@ -608,7 +608,24 @@ class ScriptToTreeNodeAdapterTest extends GroovyTestCase { eq('ExpressionStatement - MethodCallExpression') ], adapter) + } + + void testCompileIndyBytecode() { + ScriptToTreeNodeAdapter adapter = createAdapter(true, false, false) + TreeNode root = adapter.compile(''' + class Test { + void test() {} + } + + ''', Phases.CLASS_GENERATION, true) as TreeNode + + def classNodeTest = root.children().find { it.toString() == 'ClassNode - Test' } + def methods = classNodeTest.children().find { it.toString() == 'Methods' } + def methodNodeTest = methods.children().find { it.toString() == 'MethodNode - test' } + assert classNodeTest + assert methods + assert methodNodeTest } } http://git-wip-us.apache.org/repos/asf/groovy/blob/90fe6d27/subprojects/groovy-console/src/test/groovy/groovy/swing/SwingBuilderConsoleTest.groovy ---------------------------------------------------------------------- diff --git a/subprojects/groovy-console/src/test/groovy/groovy/swing/SwingBuilderConsoleTest.groovy b/subprojects/groovy-console/src/test/groovy/groovy/swing/SwingBuilderConsoleTest.groovy index 9aca8a4..912a0de 100644 --- a/subprojects/groovy-console/src/test/groovy/groovy/swing/SwingBuilderConsoleTest.groovy +++ b/subprojects/groovy-console/src/test/groovy/groovy/swing/SwingBuilderConsoleTest.groovy @@ -22,6 +22,7 @@ import groovy.ui.Console import groovy.ui.ConsoleActions import groovy.ui.view.BasicMenuBar import groovy.ui.view.MacOSXMenuBar +import org.codehaus.groovy.control.CompilerConfiguration import javax.swing.JTextPane import java.awt.event.ActionEvent @@ -565,4 +566,51 @@ class SwingBuilderConsoleTest extends GroovySwingTestCase { } } + void testWithIndyCompilation() { + testInEDT { + SwingUtilities.metaClass.static.invokeLater = { Runnable runnable -> + runnable.run() + } + Thread.metaClass.static.start = { Runnable runnable -> + runnable.run() + } + // in case the static final var has been already initialized + Console.prefs = testPreferences + try { + def swing = new SwingBuilder() + final Console console = new Console() + swing.controller = console + swing.build(new ConsoleActions()) + console.showScriptInOutput = false + console.run() + + String scriptSource = '"foo".concat("bar")' + + def outputDocument = console.outputArea.document + console.inputEditor.textEditor.text = scriptSource + + console.indy(new EventObject([selected: true])) + assert console.prefs.getBoolean('indy', false) + assert console.frame.title == 'GroovyConsole (Indy)' + + console.runScript(new EventObject([:])) + assert console.config.getOptimizationOptions().get(CompilerConfiguration.INVOKEDYNAMIC) + assert outputDocument.getText(0, outputDocument.length) == 'Result: foobar' + + console.indy(new EventObject([selected: false])) + assert !console.prefs.getBoolean('indy', true) + assert console.frame.title == 'GroovyConsole' + + console.outputArea.text = '' + console.runScript(new EventObject([:])) + assert !console.config.getOptimizationOptions().get(CompilerConfiguration.INVOKEDYNAMIC) + assert outputDocument.getText(0, outputDocument.length) == 'Result: foobar' + } finally { + GroovySystem.metaClassRegistry.removeMetaClass(Thread) + GroovySystem.metaClassRegistry.removeMetaClass(SwingUtilities) + GroovySystem.metaClassRegistry.removeMetaClass(Preferences) + } + } + } + }