You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@groovy.apache.org by jw...@apache.org on 2017/01/22 19:14:52 UTC

groovy git commit: GROOVY-5471: Add "indy" option to Groovy Console (and AstBrowser) (closes #475)

Repository: groovy
Updated Branches:
  refs/heads/master 048b6586b -> 90fe6d277


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/master
Commit: 90fe6d2777c27ecc1149bee1d1e32359fa33e3c4
Parents: 048b658
Author: John Wagenleitner <jw...@apache.org>
Authored: Sun Jan 22 11:02:50 2017 -0800
Committer: John Wagenleitner <jw...@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)
+            }
+        }
+    }
+
 }