public class IO
{
    /**
     * Raw input stream.
     *
     * @see #in
     */
    public final InputStream inputStream;

    /**
     * Raw output stream.
     *
     * @see #out
     */
    public final OutputStream outputStream;

    /**
     * Raw error output stream.
     *
     * @see #err
     */
    public final OutputStream errorStream;

    /**
     * Prefered input reader.
     */
    public final Reader in;

    /**
     * Prefered output writer.
     */
    public final PrintWriter out;

    /**
     * Prefered error output writer.
     */
    public final PrintWriter err;

    /**
     * Construct a new IO container.
     *
     * @param in    The input steam; must not be null
     * @param out   The output stream; must not be null
     * @param err   The error output stream; must not be null
     */
    public IO(final InputStream in, final OutputStream out, final OutputStream err) {
        if (in == null) {
            throw new NullArgumentException("in");
        }
        if (out == null) {
            throw new NullArgumentException("out");
        }
        if (err == null) {
            throw new NullArgumentException("err");
        }

        this.inputStream = in;
        this.outputStream = out;
        this.errorStream = err;

        this.in = new InputStreamReader(in);
        this.out = new PrintWriter(out, true);
        this.err = new PrintWriter(err, true);
    }

    /**
     * Construct a new IO container.
     *
     * @param in    The input steam; must not be null
     * @param out   The output stream and error stream; must not be null
     */
    public IO(final InputStream in, final OutputStream out) {
        this(in, out, out);
    }

    /**
     * Helper which uses current values from {@link System}.
     */
    public IO() {
        this(System.in, System.out, System.err);
    }

    /**
     * Flush both output streams.
     */
    public void flush() {
        out.flush();
        err.flush();
    }

    public void close() throws IOException {
        in.close();
        out.close();
        err.close();
    }
} public interface Command
{
    /** Standard command success status code. */
    int SUCCESS = 0;

    /** Standard command failure status code. */
    int FAILURE = -1;

    String getName();

    void init(CommandContext context); // throws Exception ?

    Object execute(Object... args) throws Exception;
    
    void abort(); // throws Exception ?
    
    void destroy(); // throws Exception ?

    //
    // 'help' command helpers to allow external inspection of command help
    //

    // String usage() // single line used to render help page

    // String about() // single line to describe the command

    // String help() // full help page (includes usage + about + command line options)
} public interface CommandManager
{
    //
    // TODO: Rename, this has become a registry
    //

    boolean addCommandDefinition(CommandDefinition def);

    CommandDefinition getCommandDefinition(String name) throws CommandNotFoundException;

    Collection<CommandDefinition> commandDefinitions();

    Set<String> commandNames();
} public interface CommandContext
{
    IO getIO();
    
    Variables getVariables();

    MessageSource getMessageSource();
} public interface Variables
{
    void set(String name, Object value) throws ImmutableVariableException;

    void set(String name, Object value, boolean mutable) throws ImmutableVariableException;

    Object get(String name);

    Object get(String name, Object _default);

    boolean isMutable(String name);

    boolean isCloaked(String name);

    void unset(String name) throws ImmutableVariableException;
    
    boolean contains(String name);
    
    Iterator<String> names();

    Variables parent();

    //
    // Exceptions
    //

    class ImmutableVariableException
        extends RuntimeException
    {
        ///CLOVER:OFF
        
        public ImmutableVariableException(final String name) {
            super("Variable is immutable: " + name);
        }
    }
} public class VariablesImpl
    implements Variables
{
    private final Map<String,Object> map;

    private final Variables parent;

    private final Set<String> immutables = new HashSet<String>();

    public VariablesImpl(final Map<String,Object> map, final Variables parent) {
        if (map == null) {
            throw new NullArgumentException("map");
        }
        if (parent == null) {
            throw new NullArgumentException("parent");
        }

        this.map = map;
        this.parent = parent;
    }

    public VariablesImpl(final Variables parent) {
        if (parent == null) {
            throw new NullArgumentException("parent");
        }

        this.map = new HashMap<String,Object>();
        this.parent = parent;
    }

    public VariablesImpl(final Map<String,Object> map) {
        if (map == null) {
            throw new NullArgumentException("map");
        }

        this.map = map;
        this.parent = null;
    }

    public VariablesImpl() {
        this(new HashMap<String,Object>());
    }

    public void set(final String name, final Object value) {
        set(name, value, true);
    }

    public void set(final String name, final Object value, boolean mutable) {
        assert name != null;

        ensureMutable(name);

        map.put(name, value);

        if (!mutable) {
            immutables.add(name);
        }
    }

    public Object get(final String name) {
        assert name != null;

        Object value = map.get(name);
        if (value == null && parent != null) {
            value = parent.get(name);
        }

        return value;
    }

    public Object get(final String name, final Object _default) {
        Object value = get(name);
        if (value == null) {
            return _default;
        }

        return value;
    }

    public void unset(final String name) {
        assert name != null;

        ensureMutable(name);

        map.remove(name);
    }

    public boolean contains(final String name) {
        assert name != null;

        return map.containsKey(name);
    }

    public boolean isMutable(final String name) {
        assert name != null;

        boolean mutable = true;

        // First ask out parent if there is one, if they are immutable, then so are we
        if (parent != null) {
            mutable = parent.isMutable(name);
        }

        if (mutable) {
            mutable = !immutables.contains(name);
        }

        return mutable;
    }

    private void ensureMutable(final String name) {
        assert name != null;

        if (!isMutable(name)) {
            throw new ImmutableVariableException(name);
        }
    }

    public boolean isCloaked(final String name) {
        assert name != null;

        int count = 0;

        Variables vars = this;
        while (vars != null && count < 2) {
            if (vars.contains(name)) {
                count++;
            }

            vars = vars.parent();
        }

        return count > 1;
    }

    public Iterator<String> names() {
        // Chain to parent iterator if we have a parent
        return new Iterator<String>() {
            Iterator<String> iter = map.keySet().iterator();
            boolean more = parent() != null;

            public boolean hasNext() {
                boolean next = iter.hasNext();
                if (!next && more) {
                    iter = parent().names();
                    more = false;
                    next = hasNext();
                }

                return next;
            }

            public String next() {
                return iter.next();
            }

            public void remove() {
                throw new UnsupportedOperationException();
            }
        };
    }

    public Variables parent() {
        return parent;
    }

    public static boolean isIdentifier(final String name) {
        if (name == null || name.length() == 0) {
            return false;
        }

        char[] chars = name.toCharArray();

        if (!Character.isJavaIdentifierStart(chars[0])) {
            return false;
        }

        /*

        FIXME: This fails for stuff like 'gshell.prompt' which we should allow
               Eventually need to get this fixed, for now just skip part checking

        for (int i=1; i<chars.length; i++) {
            if (!Character.isJavaIdentifierPart(chars[i])) {
                return false;
            }
        }
        */

        return true;
    }
} public class CommandException
    extends Exception
{
    ///CLOVER:OFF
    
    public CommandException(String msg) {
        super(msg);
    }

    public CommandException(String msg, Throwable cause) {
        super(msg, cause);
    }

    public CommandException(Throwable cause) {
        super(cause);
    }

    public CommandException() {
        super();
    }
} public class CommandDefinition
{
    private final String name;

    private final String classname;

    private final String[] aliases;

    private final boolean enabled;

    private final String category;

    public CommandDefinition(final Properties props) throws InvalidDefinitionException {
        if (props == null) {
            throw new NullArgumentException("props");
        }

        this.name = props.getProperty("name");
        if (name == null) {
            throw new MissingPropertyException("name", props);
        }

        this.classname = props.getProperty("class");
        if (classname == null) {
            throw new MissingPropertyException("class", props);
        }

        this.aliases = loadAliasesFrom(props);

        this.enabled = Boolean.getBoolean(props.getProperty("enable"));

        this.category = props.getProperty("category");
        if (category == null) {
            throw new MissingPropertyException("category", props);
        }
    }

    public String toString() {
        return getName() + "=" + getClassName() +
                "{ aliases=" + Arguments.asString(getAliases()) +
                ", enabled=" + enabled +
                ", category=" + category +
                " }";
    }

    //
    // TODO: Add 'alias' and 'unalias' command support
    //

    private String[] loadAliasesFrom(final Properties props) {
        String input = props.getProperty("aliases");

        if (input != null) {
            String[] list = input.split(",");
            for (int i=0; i < list.length; i++) {
                list[i] = list[i].trim();
            }

            return list;
        }
        else {
            return new String[0];
        }
    }

    public String getName() {
        return name;
    }

    public String getClassName() {
        return classname;
    }

    public String[] getAliases() {
        return aliases;
    }

    public boolean isEnabled() {
        return enabled;
    }

    public String getCategory() {
        return category;
    }

    public Class loadClass() throws ClassNotFoundException {
        ClassLoader cl = Thread.currentThread().getContextClassLoader();
        return cl.loadClass(getClassName());
    }

    //
    // Exceptions
    //

    public static class InvalidDefinitionException
        extends CommandException
    {
        ///CLOVER:OFF

        public InvalidDefinitionException(String msg) {
            super(msg);
        }
    }

    public static class MissingPropertyException
        extends InvalidDefinitionException
    {
        ///CLOVER:OFF

        MissingPropertyException(String name, Properties props) {
            super("Missing '" + name + "' property in command definition: " + props);
        }
    }
} public class CommandManagerImpl
    implements CommandManager
{
    private static final Log log = LogFactory.getLog(CommandManager.class);

    private Map<String,CommandDefinition> commandDefMap = new HashMap<String,CommandDefinition>();

    private Map<String,CommandDefinition> commandAliasMap = new HashMap<String,CommandDefinition>();

    public CommandManagerImpl() throws CommandException {
        try {
            discoverCommands();
        }
        catch (Exception e) {
            throw new CommandException(e);
        }
    }

    private void discoverCommands() throws Exception {
        log.info("Discovering commands");

        ResourceFinder finder = new ResourceFinder("META-INF/");
        Map<String, Properties> map = finder.mapAllProperties("org.apache.geronimo.gshell.command");

        for (String filename : map.keySet()) {
            Properties props = map.get(filename);
            CommandDefinition def = new CommandDefinition(props);
            addCommandDefinition(def);
        }
    }

    public boolean addCommandDefinition(final CommandDefinition def) {
        if (def == null) {
            throw new IllegalArgumentException("Def is null");
        }

        boolean debug = log.isDebugEnabled();

        CommandDefinition prev = commandDefMap.put(def.getName(), def);
        if (debug) {
            log.debug("Added definition: " + def);
        }

        addCommandAliases(def);

        return prev != null;
    }

    private void addCommandAliases(final CommandDefinition def) {
        assert def != null;

        boolean debug = log.isDebugEnabled();

        for (String alias : def.getAliases()) {
            CommandDefinition prev = commandAliasMap.put(alias, def);
            if (debug) {
                log.debug("Added alias (to " + def.getName() + "): " + def);
                if (prev != null) {
                    log.debug("    Replaces previous alias to: " + prev.getName());
                }
            }
        }
    }

    public CommandDefinition getCommandDefinition(String name) throws CommandNotFoundException {
        if (name == null) {
            throw new NullArgumentException("name");
        }
        if (name.trim().length() == 0) {
            throw new IllegalArgumentException("Name is empty");
        }

        //
        // TODO: Issue warning if there is whitespace, that is a programming error (for someone)
        //       Investigate auto-trim and complain from the parser too, looks like we are catching
        //       non-traditional whitespace (ctrl chars, etc).
        //

        // Make sure there is not funky whitespace in there (from Telnet or something)
        name = name.trim();

        CommandDefinition def = commandDefMap.get(name);
        if (def == null) {
            def = commandAliasMap.get(name);

            if (def == null) {
                throw new CommandNotFoundException(name);
            }
        }

        return def;
    }

    public Set<String> commandNames() {
        return Collections.unmodifiableSet(commandDefMap.keySet());
    }

    public Collection<CommandDefinition> commandDefinitions() {
        return Collections.unmodifiableCollection(commandDefMap.values());
    }
} public interface MessageSource
{
    String getMessage(String code);

    String getMessage(String code, Object... args);
} public class MessageSourceImpl
    implements MessageSource
{
    //
    // TODO: Add a global message set that is overridden by command messages
    //
    
    private final ResourceBundle bundle;

    public MessageSourceImpl(final String name) {
        if (name == null) {
            throw new NullArgumentException("name");
        }

        bundle = ResourceBundle.getBundle(name);
    }

    public String getMessage(final String code) {
        return bundle.getString(code);
    }

    public String getMessage(final String code, final Object... args) {
        String format = getMessage(code);

        StringBuilder sb = new StringBuilder();
        Formatter f = new Formatter(sb);
        
        f.format(format, args);

        return sb.toString();
    }
} public interface StandardVariables
{
    String PROMPT = "gshell.prompt";
} public class CommandLineBuilder
{
    private static final Log log = LogFactory.getLog(CommandLineBuilder.class);

    private final Shell shell;

    private final CommandLineParser parser;

    public CommandLineBuilder(final Shell shell) {
        if (shell == null) {
            throw new NullArgumentException("shell");
        }

        this.shell = shell;
        this.parser = new CommandLineParser();
    }

    private ASTCommandLine parse(final String input) throws ParseException {
        assert input != null;

        Reader reader = new StringReader(input);
        ASTCommandLine cl = parser.parse(reader);

        // If debug is enabled, the log the parse tree
        if (log.isDebugEnabled()) {
            LoggingVisitor logger = new LoggingVisitor(log);
            cl.jjtAccept(logger, null);
        }

        return cl;
    }

    public CommandLine create(final String commandLine) throws ParseException {
        if (commandLine == null) {
            throw new NullArgumentException("commandLine");
        }
        if (commandLine.trim().length() == 0) {
            throw new IllegalArgumentException("Command line is empty");
        }

        final ASTCommandLine root = parse(commandLine);
        final ExecutingVisitor visitor = new ExecutingVisitor(this.shell);

        return new CommandLine() {
            public Object execute() throws Exception {

                //
                // TODO: Handle ErrorNotification
                //

                return root.jjtAccept(visitor, null);
            }
        };
    }
} public interface CommandLine
{
    /**
     * Execute the command-line.
     *
     * @return  The final result of the command-line.
     *
     * @throws Exception    Failed to execute command-line.
     */
    Object execute() throws Exception;
} public class Arguments
{
    public static Object[] shift(final Object[] args) {
        return shift(args, 1);
    }

    public static Object[] shift(final Object[] args, int pos) {
        assert args != null;
        assert args.length >= pos;

        String[] _args = new String[args.length - pos];
        System.arraycopy(args, pos, _args, 0, _args.length);
        return _args;
    }

    public static String asString(final Object[] args) {
        assert args != null;

        StringBuffer buff = new StringBuffer();

        for (int i=0; i<args.length; i++ ) {
            buff.append(args[i]);
            if (i + 1 < args.length) {
                buff.append(", ");
            }
        }

        return buff.toString();
    }

    public static String[] toStringArray(final Object[] args) {
        assert args != null;

        String[] strings = new String[args.length];

        for (int i=0; i<args.length; i++ ) {
            strings[i] = String.valueOf(args[i]);
        }

        return strings;
    }
}
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.commons.lang.time.StopWatch;
import org.apache.commons.lang.NullArgumentException;
import org.apache.geronimo.gshell.console.IO;
import org.apache.geronimo.gshell.command.Command;
import org.apache.geronimo.gshell.command.CommandManager;
import org.apache.geronimo.gshell.command.CommandContext;
import org.apache.geronimo.gshell.command.Variables;
import org.apache.geronimo.gshell.command.VariablesImpl;
import org.apache.geronimo.gshell.command.CommandException;
import org.apache.geronimo.gshell.command.CommandDefinition;
import org.apache.geronimo.gshell.command.CommandManagerImpl;
import org.apache.geronimo.gshell.command.MessageSource;
import org.apache.geronimo.gshell.command.MessageSourceImpl;
import org.apache.geronimo.gshell.command.StandardVariables;
import org.apache.geronimo.gshell.commandline.CommandLineBuilder;
import org.apache.geronimo.gshell.commandline.CommandLine;
import org.apache.geronimo.gshell.util.Arguments;
import java.util.Iterator;
/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you 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 org.apache.geronimo.gshell;



/**
 * This is the primary interface to executing named commands.
 *
 * @version $Rev$ $Date$
 */
public class Shell
{
    //
    // TODO: Introduce Shell interface?
    //

    private static final Log log = LogFactory.getLog(Shell.class);

    private final IO io;

    private final ShellContainer shellContainer = new ShellContainer();

    private final CommandManager commandManager;

    private final CommandLineBuilder commandLineBuilder;

    private final Variables variables = new VariablesImpl();

    public Shell(final IO io) throws CommandException {