home | career | drupal | java | mac | mysql | perl | scala | uml | unix

Groovy example source code file (InteractiveShell.java)

This example Groovy source code file (InteractiveShell.java) is included in the DevDaily.com "Java Source Code Warehouse" project. The intent of this project is to help you "Learn Java by Example" TM.

Java - Groovy tags/keywords

binding, closure, exception, inputstream, interactiveshell, interactiveshell, io, ioexception, ioexception, map, object, printstream, printstream, reflection, string, string, util

The Groovy InteractiveShell.java source code

/*
 * Copyright 2003-2007 the original author or authors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package groovy.ui;

import groovy.lang.Binding;
import groovy.lang.Closure;
import groovy.lang.GroovyShell;
import groovy.lang.GroovySystem;

import org.codehaus.groovy.tools.shell.util.MessageSource;

import org.codehaus.groovy.control.CompilationFailedException;
import org.codehaus.groovy.control.SourceUnit;
import org.codehaus.groovy.runtime.InvokerInvocationException;
import org.codehaus.groovy.runtime.DefaultGroovyMethods;
import org.codehaus.groovy.tools.ErrorReporter;

import java.io.IOException;
import java.io.InputStream;
import java.io.PrintStream;
import java.io.Writer;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;

import org.apache.commons.cli.CommandLine;
import org.apache.commons.cli.CommandLineParser;
import org.apache.commons.cli.OptionBuilder;
import org.apache.commons.cli.PosixParser;
import org.apache.commons.cli.Options;
import org.apache.commons.cli.HelpFormatter;

import jline.ConsoleReader;
import jline.SimpleCompletor;

/**
 * A simple interactive shell for evaluating groovy expressions on the command line (aka. groovysh).
 *
 * @author <a href="mailto:james@coredevelopers.net">James Strachan
 * @author <a href="mailto:cpoirier@dreaming.org"   >Chris Poirier
 * @author Yuri Schimke
 * @author Brian McCallistair
 * @author Guillaume Laforge
 * @author Dierk Koenig, include the inspect command, June 2005
 * @author <a href="mailto:jason@planet57.com">Jason Dillon
 *
 * @version $Revision: 21223 $
 */
@Deprecated
public class InteractiveShell
    implements Runnable
{
    private static final String NEW_LINE = System.getProperty("line.separator");
    private static final MessageSource MESSAGES = new MessageSource(InteractiveShell.class);

    private final GroovyShell shell;
    private final InputStream in; // FIXME: This doesn't really need to be a field, but hold on to it for now
    private final PrintStream out;
    private final PrintStream err;
    private final ConsoleReader reader;

    private Object lastResult;
    private Closure beforeExecution;
    private Closure afterExecution;

    /**
     * Entry point when called directly.
     */
    public static void main(final String args[]) {
        try {
            processCommandLineArguments(args);

            final InteractiveShell groovy = new InteractiveShell();
            groovy.run();
        }
        catch (Exception e) {
            System.err.println("FATAL: " + e);
            e.printStackTrace();
            System.exit(1);
        }

        System.exit(0);
    }

    /**
     * Process cli args when the shell is invoked via main().
     *
     * @noinspection AccessStaticViaInstance
     */
    private static void processCommandLineArguments(final String[] args) throws Exception {
        assert args != null;

        //
        // TODO: Let this take a single, optional argument which is a file or URL to run?
        //
        
        Options options = new Options();

        options.addOption(OptionBuilder.withLongOpt("help")
            .withDescription(MESSAGES.getMessage("cli.option.help.description"))
            .create('h'));

        options.addOption(OptionBuilder.withLongOpt("version")
            .withDescription(MESSAGES.getMessage("cli.option.version.description"))
            .create('V'));

        //
        // TODO: Add more options, maybe even add an option to prime the buffer from a URL or File?
        //
        
        //
        // FIXME: This does not currently barf on unsupported options short options, though it does for long ones.
        //        Same problem with commons-cli 1.0 and 1.1
        //
        
        CommandLineParser parser = new PosixParser();
        CommandLine line = parser.parse(options, args, true);
        String[] lineargs = line.getArgs();

        // Puke if there were arguments, we don't support any right now
        if (lineargs.length != 0) {
            System.err.println(MESSAGES.format("cli.info.unexpected_args", new Object[] { DefaultGroovyMethods.join(lineargs, " ") }));
            System.exit(1);
        }

        PrintWriter writer = new PrintWriter(System.out);

        if (line.hasOption('h')) {
            HelpFormatter formatter = new HelpFormatter();
            formatter.printHelp(
                writer,
                80, // width
                "groovysh [options]",
                "",
                options,
                4, // left pad
                4, // desc pad
                "",
                false); // auto usage

            writer.flush();
            System.exit(0);
        }

        if (line.hasOption('V')) {
            writer.println(MESSAGES.format("cli.info.version", new Object[] { GroovySystem.getVersion() }));
            writer.flush();
            System.exit(0);
        }
    }

    /**
     * Default constructor, initializes uses new binding and system streams.
     */
    public InteractiveShell() throws IOException {
        this(System.in, System.out, System.err);
    }

    /**
     * Constructs a new InteractiveShell instance
     *
     * @param in The input stream to use
     * @param out The output stream to use
     * @param err The error stream to use
     */
    public InteractiveShell(final InputStream in, final PrintStream out, final PrintStream err) throws IOException {
        this(null, new Binding(), in, out, err);
    }

    /**
     * Constructs a new InteractiveShell instance
     * 
     * @param binding The binding instance
     * @param in The input stream to use
     * @param out The output stream to use
     * @param err The error stream to use
     */    
    public InteractiveShell(final Binding binding, final InputStream in, final PrintStream out, final PrintStream err) throws IOException {
        this(null, binding, in, out, err);
    }
    
    /**
     * Constructs a new InteractiveShell instance
     * 
     * @param parent The parent ClassLoader
     * @param binding The binding instance
     * @param in The input stream to use
     * @param out The output stream to use
     * @param err The error stream to use
     */
    public InteractiveShell(final ClassLoader parent, final Binding binding, final InputStream in, final PrintStream out, final PrintStream err) throws IOException {
        assert binding != null;
        assert in != null;
        assert out != null;
        assert err != null;

        this.in = in;
        this.out = out;
        this.err = err;

        // Initialize the JLine console input reader
        Writer writer = new OutputStreamWriter(out);
        reader = new ConsoleReader(in, writer);
        reader.setDefaultPrompt("groovy> ");

        // Add some completors to fancy things up
        reader.addCompletor(new CommandNameCompletor());

        if (parent != null) {
            shell = new GroovyShell(parent, binding);
        }
        else {
            shell = new GroovyShell(binding);
        }        

        // Add some default variables to the shell
        Map map = shell.getContext().getVariables();

        //
        // FIXME: Um, is this right?  Only set the "shell" var in the context if its set already?
        //
        
        if (map.get("shell") != null) {
            map.put("shell", shell);
        }
    }    

    //---------------------------------------------------------------------------
    // COMMAND LINE PROCESSING LOOP

    //
    // TODO: Add a general error display handler, and probably add a "ERROR: " prefix to the result for clarity ?
    //       Maybe add one for WARNING's too?
    //
    
    /**
     * Reads commands and statements from input stream and processes them.
     */
    public void run() {
        // Display the startup banner
        out.println(MESSAGES.format("startup_banner.0", new Object[] { GroovySystem.getVersion(), System.getProperty("java.version") }));
        out.println(MESSAGES.getMessage("startup_banner.1"));

        while (true) {
            // Read a code block to evaluate; this will deal with basic error handling
            final String code = read();

            // If we got a null, then quit
            if (code == null) {
                break;
            }

            reset();

            // Evaluate the code block if it was parsed
            if (code.length() > 0) {
                try {
                    if (beforeExecution != null) {
                        beforeExecution.call();
                    }

                    lastResult = shell.evaluate(code);
                    
                    if (afterExecution != null) {
                        afterExecution.call();
                    }

                    // Shows the result of the evaluated code
                    out.print("===> ");
                    out.println(lastResult);
                }
                catch (CompilationFailedException e) {
                    err.println(e);
                }
                catch (Throwable e) {
                    // Unroll invoker exceptions
                    if (e instanceof InvokerInvocationException) {
                        e = e.getCause();
                    }
                    
                    filterAndPrintStackTrace(e);
                }
            }
        }
    }

    /**
     * A closure that is executed before the execution of a given script
     *
     * @param beforeExecution The closure to execute
     */
    public void setBeforeExecution(final Closure beforeExecution) {
        this.beforeExecution = beforeExecution;
    }

    /**
     * A closure that is executed after the execution of the last script. The result of the
     * execution is passed as the first argument to the closure (the value of 'it')
     *
     * @param afterExecution The closure to execute
     */
    public void setAfterExecution(final Closure afterExecution) {
        this.afterExecution = afterExecution;
    }

    /**
     * Filter stacktraces to show only relevant lines of the exception thrown.
     *
     * @param cause the throwable whose stacktrace needs to be filtered
     */
    private void filterAndPrintStackTrace(final Throwable cause) {
        assert cause != null;

        //
        // TODO: Use message...
        //
        
        err.print("ERROR: ");
        err.println(cause);

        cause.printStackTrace(err);

        //
        // FIXME: What is the point of this?  AFAICT, this just produces crappy/corrupt traces and is completely useless
        //
        
//        StackTraceElement[] stackTrace = e.getStackTrace();
//
//        for (int i = 0; i < stackTrace.length; i++) {
//            StackTraceElement element = stackTrace[i];
//            String fileName = element.getFileName();
//
//            if ((fileName==null || (!fileName.endsWith(".java")) && (!element.getClassName().startsWith("gjdk")))) {
//                err.print("\tat ");
//                err.println(element);
//            }
//        }
    }

    //---------------------------------------------------------------------------
    // COMMAND LINE PROCESSING MACHINERY

    /** The statement text accepted to date */
    private StringBuffer accepted = new StringBuffer();

    /** A line of statement text not yet accepted */
    private String pending;

    //
    // FIXME: Doesn't look like 'line' is really used/needed anywhere... could drop it, or perhaps
    //        could use it to update the prompt er something to show the buffer size?
    //

    /** The current line number */
    private int line;
    
    /** Set to force clear of accepted */
    private boolean stale = false;

    /** A SourceUnit used to check the statement */
    private SourceUnit parser;

    /** Any actual syntax error caught during parsing */
    private Exception error;

    /**
     * Resets the command-line processing machinery after use.
     */
    protected void reset() {
        stale = true;
        pending = null;
        line = 1;
        parser = null;
        error = null;
    }

    //
    // FIXME: This Javadoc is not correct... read() will return the full code block read until "go"
    //
    
    /**
     * Reads a single statement from the command line.  Also identifies
     * and processes command shell commands.  Returns the command text
     * on success, or null when command processing is complete.
     * 
     * NOTE: Changed, for now, to read until 'execute' is issued.  At
     * 'execute', the statement must be complete.
     */
    protected String read() {
        reset();
        
        boolean complete = false;
        boolean done = false;
        
        while (/* !complete && */ !done) {
            // Read a line.  If IOException or null, or command "exit", terminate processing.
            try {
                pending = reader.readLine();
            }
            catch (IOException e) {
                //
                // FIXME: Shouldn't really eat this exception, may be something we need to see... ?
                //
            }

            // If result is null then we are shutting down
            if (pending == null) {
                return null;
            }

            // First up, try to process the line as a command and proceed accordingly
            // Trim what we have for use in command bits, so things like "help " actually show the help screen
            String command = pending.trim();

            if (COMMAND_MAPPINGS.containsKey(command)) {
                int code = ((Integer)COMMAND_MAPPINGS.get(command)).intValue();

                switch (code) {
                    case COMMAND_ID_EXIT:
                        return null;
                    
                    case COMMAND_ID_HELP:
                        displayHelp();
                        break;

                    case COMMAND_ID_DISCARD:
                        reset();
                        done = true;
                        break;

                    case COMMAND_ID_DISPLAY:
                        displayStatement();
                        break;

                    case COMMAND_ID_EXPLAIN:
                        explainStatement();
                        break;

                    case COMMAND_ID_BINDING:
                        displayBinding();
                        break;

                    case COMMAND_ID_EXECUTE:
                        if (complete) {
                            done = true;
                        }
                        else {
                            err.println(MESSAGES.getMessage("command.execute.not_complete"));
                        }
                        break;

                    case COMMAND_ID_DISCARD_LOADED_CLASSES:
                        resetLoadedClasses();
                        break;

                    case COMMAND_ID_INSPECT:
                        inspect();
                        break;

                    default:
                        throw new Error("BUG: Unknown command for code: " + code);
                }

                // Finished processing command bits, continue reading, don't need to process code
                continue;
            }

            // Otherwise, it's part of a statement.  If it's just whitespace,
            // we'll just accept it and move on.  Otherwise, parsing is attempted
            // on the accumulated statement text, and errors are reported.  The
            // pending input is accepted or rejected based on that parsing.

            freshen();

            if (pending.trim().length() == 0) {
                accept();
                continue;
            }

            // Try to parse the current code buffer
            final String code = current();
            
            if (parse(code)) {
                // Code parsed fine
                accept();
                complete = true;
            }
            else if (error == null) {
                // Um... ???
                accept();
            }
            else {
                // Parse failed, spit out something to the user
                report();
            }
        }

        // Get and return the statement.
        return accepted(complete);
    }

    private void inspect() {
        if (lastResult == null){
            err.println(MESSAGES.getMessage("command.inspect.no_result"));
            return;
        }

        //
        // FIXME: Update this once we have joint compile happy in the core build?
        //
        // this should read: groovy.inspect.swingui.ObjectBrowser.inspect(lastResult)
        // but this doesn't compile since ObjectBrowser.groovy is compiled after this class.
        //

        //
        // FIXME: When launching this, if the user tries to "exit" and the window is still opened, the shell will
        //        hang... not really nice user experience IMO.  Should try to fix this if we can.
        //
        
        try {
            Class type = Class.forName("groovy.inspect.swingui.ObjectBrowser");
            Method method = type.getMethod("inspect", new Class[]{ Object.class });
            method.invoke(type, new Object[]{ lastResult });
        }
        catch (Exception e) {
            err.println("Cannot invoke ObjectBrowser");
            e.printStackTrace();
        }
    }

    /**
     * Returns the accepted statement as a string.  If not complete, returns empty string.
     */
    private String accepted(final boolean complete) {
        if (complete) {
            return accepted.toString();
        }
        return "";
    }

    /**
     * Returns the current statement, including pending text.
     */
    private String current() {
        return accepted.toString() + pending + NEW_LINE;
    }

    /**
     * Accepts the pending text into the statement.
     */
    private void accept() {
        accepted.append(pending).append(NEW_LINE);
        line += 1;
    }

    /**
     * Clears accepted if stale.
     */
    private void freshen() {
        if (stale) {
            accepted.setLength(0);
            stale = false;
        }
    }

    //---------------------------------------------------------------------------
    // SUPPORT ROUTINES

    /**
     * Attempts to parse the specified code with the specified tolerance.
     * Updates the <code>parser and error members
     * appropriately.  Returns true if the text parsed, false otherwise.
     * The attempts to identify and suppress errors resulting from the
     * unfinished source text.
     */
    private boolean parse(final String code, final int tolerance) {
        assert code != null;

        boolean parsed = false;
        parser = null;
        error = null;

        // Create the parser and attempt to parse the text as a top-level statement.
        try {
            parser = SourceUnit.create("groovysh-script", code, tolerance);
            parser.parse();
            parsed = true;
        }

        // We report errors other than unexpected EOF to the user.
        catch (CompilationFailedException e) {
            if (parser.getErrorCollector().getErrorCount() > 1 || !parser.failedWithUnexpectedEOF()) {
                error = e;
            }
        }
        catch (Exception e) {
            error = e;
        }

        return parsed;
    }

    private boolean parse(final String code) {
        return parse(code, 1);
    }
    
    /**
     * Reports the last parsing error to the user.
     */
    private void report() {
        err.println("Discarding invalid text:"); // TODO: i18n
        new ErrorReporter(error, false).write(err);
    }

    //-----------------------------------------------------------------------
    // COMMANDS

    //
    // TODO: Add a simple command to read in a File/URL into the buffer for execution, but need better command
    //       support first (aka GShell) so we can allow commands to take args, etc.
    //

    private static final int COMMAND_ID_EXIT = 0;
    private static final int COMMAND_ID_HELP = 1;
    private static final int COMMAND_ID_DISCARD = 2;
    private static final int COMMAND_ID_DISPLAY = 3;
    private static final int COMMAND_ID_EXPLAIN = 4;
    private static final int COMMAND_ID_EXECUTE = 5;
    private static final int COMMAND_ID_BINDING = 6;
    private static final int COMMAND_ID_DISCARD_LOADED_CLASSES = 7;
    private static final int COMMAND_ID_INSPECT = 8;
    private static final int LAST_COMMAND_ID = 8;

    private static final String[] COMMANDS = {
        "exit",
        "help",
        "discard",
        "display",
        "explain",
        "execute",
        "binding",
        "discardclasses",
        "inspect"
    };

    private static final Map<String, Integer> COMMAND_MAPPINGS = new HashMap();

    static {
        for (int i = 0; i <= LAST_COMMAND_ID; i++) {
            COMMAND_MAPPINGS.put(COMMANDS[i], i);
        }

        // A few synonyms
        COMMAND_MAPPINGS.put("quit", COMMAND_ID_EXIT);
        COMMAND_MAPPINGS.put("go", COMMAND_ID_EXECUTE);
    }

    private static final Map<String, String> COMMAND_HELP = new HashMap();

    static {
        COMMAND_HELP.put(COMMANDS[COMMAND_ID_EXIT],    "exit/quit         - " + MESSAGES.getMessage("command.exit.descripion"));
        COMMAND_HELP.put(COMMANDS[COMMAND_ID_HELP],    "help              - " + MESSAGES.getMessage("command.help.descripion"));
        COMMAND_HELP.put(COMMANDS[COMMAND_ID_DISCARD], "discard           - " + MESSAGES.getMessage("command.discard.descripion"));
        COMMAND_HELP.put(COMMANDS[COMMAND_ID_DISPLAY], "display           - " + MESSAGES.getMessage("command.display.descripion"));

        //
        // FIXME: If this is disabled, then er comment it out, so it doesn't confuse the user
        //
        
        COMMAND_HELP.put(COMMANDS[COMMAND_ID_EXPLAIN], "explain           - " + MESSAGES.getMessage("command.explain.descripion"));
        COMMAND_HELP.put(COMMANDS[COMMAND_ID_EXECUTE], "execute/go        - " + MESSAGES.getMessage("command.execute.descripion"));
        COMMAND_HELP.put(COMMANDS[COMMAND_ID_BINDING], "binding           - " + MESSAGES.getMessage("command.binding.descripion"));
        COMMAND_HELP.put(COMMANDS[COMMAND_ID_DISCARD_LOADED_CLASSES],
                                                       "discardclasses    - " + MESSAGES.getMessage("command.discardclasses.descripion"));
        COMMAND_HELP.put(COMMANDS[COMMAND_ID_INSPECT], "inspect           - " + MESSAGES.getMessage("command.inspect.descripion"));
    }

    /**
     * Displays help text about available commands.
     */
    private void displayHelp() {
        out.println(MESSAGES.getMessage("command.help.available_commands"));

        for (int i = 0; i <= LAST_COMMAND_ID; i++) {
            out.print("    ");
            out.println(COMMAND_HELP.get(COMMANDS[i]));
        }
    }

    /**
     * Displays the accepted statement.
     */
    private void displayStatement() {
        final String[] lines = accepted.toString().split(NEW_LINE);

        if (lines.length == 1 && lines[0].trim().equals("")) {
            out.println(MESSAGES.getMessage("command.display.buffer_empty"));
        }
        else {
            // Eh, try to pick a decent pad size... but don't try to hard
            int padsize = 2;
            if (lines.length >= 10) padsize++;
            if (lines.length >= 100) padsize++;
            if (lines.length >= 1000) padsize++;

            // Dump the current buffer with a line number prefix
            for (int i = 0; i < lines.length; i++) {
                // Normalize the field size of the line number
                String lineno = DefaultGroovyMethods.padLeft(String.valueOf(i + 1), padsize, " ");
                
                out.print(lineno);
                out.print("> ");
                out.println(lines[i]);
            }
        }
    }

    /**
     * Displays the current binding used when instantiating the shell.
     */
    private void displayBinding() {
        Binding context = shell.getContext();
        Map variables = context.getVariables();
        Set set = variables.keySet();

        if (set.isEmpty()) {
            out.println(MESSAGES.getMessage("command.binding.binding_empty"));
        }
        else {
            out.println(MESSAGES.getMessage("command.binding.available_variables"));

            for (Object key : set) {
                out.print("    ");
                out.print(key);
                out.print(" = ");
                out.println(variables.get(key));
            }
        }
    }

    /**
     * Attempts to parse the accepted statement and display the parse tree for it.
     */
    private void explainStatement() {
        if (parse(accepted(true), 10) || error == null) {
            out.println(MESSAGES.getMessage("command.explain.tree_header"));
            //out.println(tree);
        }
        else {
            out.println(MESSAGES.getMessage("command.explain.unparsable"));
        }
    }

    private void resetLoadedClasses() {
        shell.resetLoadedClasses();
        
        out.println(MESSAGES.getMessage("command.discardclasses.classdefs_discarded"));
    }

    //
    // Custom JLine Completors to fancy up the user experience more.
    //

    private class CommandNameCompletor
        extends SimpleCompletor
    {
        public CommandNameCompletor() {
            super(new String[0]);

            // Add each command name/alias as a candidate
            Iterator iter = COMMAND_MAPPINGS.keySet().iterator();

            while (iter.hasNext()) {
                addCandidateString((String)iter.next());
            }
        }
    }

    //
    // TODO: Add local variable completion?
    //

    //
    // TODO: Add shell method completion?
    //

    /*
    private void findShellMethods(String complete) {
        List methods = shell.getMetaClass().getMetaMethods();
        for (Iterator i = methods.iterator(); i.hasNext();) {
            MetaMethod method = (MetaMethod) i.next();
            if (method.getName().startsWith(complete)) {
                if (method.getParameterTypes().length > 0) {
                    completions.add(method.getName() + "(");
                }
                else {
                    completions.add(method.getName() + "()");
                }
            }
        }
    }

    private void findLocalVariables(String complete) {
        Set names = shell.getContext().getVariables().keySet();

        for (Iterator i = names.iterator(); i.hasNext();) {
            String name = (String) i.next();
            if (name.startsWith(complete)) {
                completions.add(name);
            }
        }
    }
    */
}

Other Groovy examples (source code examples)

Here is a short list of links related to this Groovy InteractiveShell.java source code file:

new blog posts

 

Copyright 1998-2013 Alvin Alexander, alvinalexander.com
All Rights Reserved.