GROOVY-7893: LexerFrame could be made more general purpose and made accessible from GroovyConsole (closes #375)
diff --git a/src/main/org/codehaus/groovy/antlr/LexerFrame.java b/src/main/org/codehaus/groovy/antlr/LexerFrame.java
index 0a1cb2b..046fd17 100644
--- a/src/main/org/codehaus/groovy/antlr/LexerFrame.java
+++ b/src/main/org/codehaus/groovy/antlr/LexerFrame.java
@@ -20,8 +20,12 @@
 
 import antlr.CharScanner;
 import antlr.Token;
+import org.codehaus.groovy.antlr.java.JavaLexer;
+import org.codehaus.groovy.antlr.java.JavaTokenTypes;
 import org.codehaus.groovy.antlr.parser.GroovyLexer;
 import org.codehaus.groovy.antlr.parser.GroovyTokenTypes;
+import org.codehaus.groovy.runtime.IOGroovyMethods;
+import org.codehaus.groovy.runtime.ResourceGroovyMethods;
 
 import javax.swing.*;
 import javax.swing.border.Border;
@@ -32,9 +36,9 @@
 import java.awt.event.MouseAdapter;
 import java.awt.event.MouseEvent;
 import java.io.File;
-import java.io.FileInputStream;
 import java.io.FileReader;
-import java.io.InputStream;
+import java.io.Reader;
+import java.io.StringReader;
 import java.lang.reflect.Constructor;
 import java.lang.reflect.Field;
 import java.util.Hashtable;
@@ -53,30 +57,60 @@
     private final Class lexerClass;
     private final Hashtable tokens = new Hashtable();
 
+    /**
+     * Constructor used when invoking as a standalone application
+     *
+     * @param lexerClass      the lexer class to use
+     * @param tokenTypesClass the lexer token types class
+     */
     public LexerFrame(Class lexerClass, Class tokenTypesClass) {
+        this(lexerClass, tokenTypesClass, null);
+    }
+
+    /**
+     * Constructor used when invoking for a specific file
+     *
+     * @param lexerClass      the lexer class to use
+     * @param tokenTypesClass the lexer token types class
+     */
+    public LexerFrame(Class lexerClass, Class tokenTypesClass, Reader reader) {
         super("Token Steam Viewer");
         this.lexerClass = lexerClass;
         try {
-            jbInit();
+            jbInit(reader);
             setSize(500, 500);
             listTokens(tokenTypesClass);
 
-            final JPopupMenu popup = new JPopupMenu();
-            popup.add(loadFileAction);
+            if (reader == null) {
+                final JPopupMenu popup = new JPopupMenu();
+                popup.add(loadFileAction);
+                jbutton.setSize(30, 30);
+                jbutton.addMouseListener(new MouseAdapter() {
+                    public void mouseReleased(MouseEvent e) {
+                        //if(e.isPopupTrigger())
+                        popup.show(scriptPane, e.getX(), e.getY());
+                    }
+                });
+            } else {
+                safeScanScript(reader);
+            }
 
-            jbutton.setSize(30, 30);
-            jbutton.addMouseListener(new MouseAdapter() {
-                public void mouseReleased(MouseEvent e) {
-                    //if(e.isPopupTrigger())
-                    popup.show(scriptPane, e.getX(), e.getY());
-                }
-            });
-            setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
+            setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE);
         } catch (Exception e) {
             e.printStackTrace();
         }
     }
 
+    /**
+     * Creates a Groovy language LexerFrame for the given script text
+     *
+     * @param scriptText the Groovy source file to parse/render
+     * @return the new frame rending the parsed tokens
+     */
+    public static LexerFrame groovyScriptFactory(String scriptText) {
+        return new LexerFrame(GroovyLexer.class, GroovyTokenTypes.class, new StringReader(scriptText));
+    }
+
     private void listTokens(Class tokenTypes) throws Exception {
         for (Field field : tokenTypes.getDeclaredFields()) {
             tokens.put(field.get(null), field.getName());
@@ -98,28 +132,40 @@
         }
     }
 
-    private Action loadFileAction = new AbstractAction("Open File...") {
+    private final Action loadFileAction = new AbstractAction("Open File...") {
         public void actionPerformed(ActionEvent ae) {
             final JFileChooser jfc = new JFileChooser();
             final int response = jfc.showOpenDialog(LexerFrame.this);
             if (response != JFileChooser.APPROVE_OPTION) {
                 return;
             }
-            try {
-                scanScript(jfc.getSelectedFile());
-            } catch (final Exception ex) {
-                ex.printStackTrace();
-            }
+            safeScanScript(jfc.getSelectedFile());
         }
     };
 
-    private void scanScript(final File file) throws Exception {
-        scriptPane.read(new FileReader(file), null);
+    private void safeScanScript(File file) {
+        try {
+            scanScript(new StringReader(ResourceGroovyMethods.getText(file)));
+        } catch (final Exception ex) {
+            ex.printStackTrace();
+        }
+    }
+
+    private void safeScanScript(Reader reader) {
+        try {
+            scanScript(reader instanceof StringReader ? (StringReader) reader : new StringReader(IOGroovyMethods.getText(reader)));
+        } catch (final Exception ex) {
+            ex.printStackTrace();
+        }
+    }
+
+    private void scanScript(final StringReader reader) throws Exception {
+        scriptPane.read(reader, null);
+        reader.reset();
 
         // create lexer
-        final Constructor constructor = lexerClass.getConstructor(InputStream.class);
-        final FileInputStream fileInputStream = new FileInputStream(file);
-        final CharScanner lexer = (CharScanner) constructor.newInstance(fileInputStream);
+        final Constructor constructor = lexerClass.getConstructor(Reader.class);
+        final CharScanner lexer = (CharScanner) constructor.newInstance(reader);
 
         tokenPane.setEditable(true);
         tokenPane.setText("");
@@ -142,14 +188,14 @@
                 line = token.getLine();
             }
             insertComponent(tokenButton);
-            if (token.getType() == Token.EOF_TYPE){
+            if (token.getType() == Token.EOF_TYPE) {
                 break;
             }
         }
 
         tokenPane.setEditable(false);
         tokenPane.setCaretPosition(0);
-        fileInputStream.close();
+        reader.close();
     }
 
     private void insertComponent(JComponent comp) {
@@ -166,8 +212,8 @@
         tokenPane.insertComponent(comp);
     }
 
-    private void jbInit() throws Exception {
-        final Border border1 = BorderFactory.createEmptyBorder();
+    private void jbInit(Reader reader) throws Exception {
+        final Border border = BorderFactory.createEmptyBorder();
         jSplitPane1.setOrientation(JSplitPane.VERTICAL_SPLIT);
         tokenPane.setEditable(false);
         tokenPane.setText("");
@@ -175,11 +221,13 @@
         scriptPane.setEditable(false);
         scriptPane.setMargin(new Insets(5, 5, 5, 5));
         scriptPane.setText("");
-        jScrollPane1.setBorder(border1);
-        jScrollPane2.setBorder(border1);
+        jScrollPane1.setBorder(border);
+        jScrollPane2.setBorder(border);
         jSplitPane1.setMinimumSize(new Dimension(800, 600));
         mainPanel.add(jSplitPane1, BorderLayout.CENTER);
-        mainPanel.add(jbutton, BorderLayout.NORTH);
+        if (reader == null) {
+            mainPanel.add(jbutton, BorderLayout.NORTH);
+        }
         this.getContentPane().add(mainPanel);
         jSplitPane1.add(jScrollPane1, JSplitPane.LEFT);
         jScrollPane1.getViewport().add(tokenPane, null);
@@ -197,7 +245,21 @@
         } catch (Exception ignore) {
             // Ignore
         }
-        new LexerFrame(GroovyLexer.class, GroovyTokenTypes.class).setVisible(true);
+        LexerFrame lexerFrame = null;
+        if (args.length == 0) {
+            lexerFrame = new LexerFrame(GroovyLexer.class, GroovyTokenTypes.class);
+        } else if (args.length > 1) {
+            System.err.println("usage: java LexerFrame [filename.ext]");
+            System.exit(1);
+        } else {
+            String filename = args[0];
+            if (filename.endsWith(".java")) {
+                lexerFrame = new LexerFrame(JavaLexer.class, JavaTokenTypes.class, new FileReader(filename));
+            } else {
+                lexerFrame = new LexerFrame(GroovyLexer.class, GroovyTokenTypes.class, new FileReader(filename));
+            }
+        }
+        lexerFrame.setVisible(true);
     }
 
     private static class HScrollableTextPane extends JTextPane {
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 40348ca..6c3ce54 100644
--- a/subprojects/groovy-console/src/main/groovy/groovy/ui/Console.groovy
+++ b/subprojects/groovy-console/src/main/groovy/groovy/ui/Console.groovy
@@ -22,6 +22,7 @@
 import groovy.inspect.swingui.AstBrowser
 import groovy.swing.SwingBuilder
 import groovy.ui.text.FindReplaceUtility
+import org.codehaus.groovy.antlr.LexerFrame
 import org.codehaus.groovy.control.messages.SimpleMessage
 import org.codehaus.groovy.tools.shell.util.MessageSource
 
@@ -883,6 +884,11 @@
         new AstBrowser(inputArea, rootElement, shell.getClassLoader()).run({ inputArea.getText() } )
     }
 
+    void inspectTokens(EventObject evt = null) {
+        def lf = LexerFrame.groovyScriptFactory(inputArea.getText())
+        lf.visible = true
+    }
+
     void largerFont(EventObject evt = null) {
         updateFontSize(inputArea.font.size + 2)
     }
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 277561d..86d450b 100644
--- a/subprojects/groovy-console/src/main/groovy/groovy/ui/ConsoleActions.groovy
+++ b/subprojects/groovy-console/src/main/groovy/groovy/ui/ConsoleActions.groovy
@@ -255,6 +255,13 @@
     accelerator: shortcut('T'),
 )
 
+inspectTokensAction = action(
+    name: 'Inspect Tokens',
+    closure: controller.&inspectTokens,
+    mnemonic: 'T',
+    accelerator: shortcut('K'),
+)
+
 captureStdOutAction = action(
     name: 'Capture Standard Output',
     closure: controller.&captureStdOut,
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 d951b45..b667a75 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
@@ -91,6 +91,7 @@
         menuItem(inspectLastAction)
         menuItem(inspectVariablesAction)
         menuItem(inspectAstAction)
+        menuItem(inspectTokensAction)
     }
 
     menu(text: 'Help', mnemonic: 'H') {
diff --git a/subprojects/groovy-console/src/main/groovy/groovy/ui/view/MacOSXMenuBar.groovy b/subprojects/groovy-console/src/main/groovy/groovy/ui/view/MacOSXMenuBar.groovy
index 9753f7b..1461f7b 100644
--- a/subprojects/groovy-console/src/main/groovy/groovy/ui/view/MacOSXMenuBar.groovy
+++ b/subprojects/groovy-console/src/main/groovy/groovy/ui/view/MacOSXMenuBar.groovy
@@ -124,6 +124,7 @@
         menuItem(inspectLastAction, icon:null)
         menuItem(inspectVariablesAction, icon:null)
         menuItem(inspectAstAction, icon:null)
+        menuItem(inspectTokensAction, icon:null)
     }
 }