expect4j.ExpectEmulation.java Source code

Java tutorial

Introduction

Here is the source code for expect4j.ExpectEmulation.java

Source

/*
 * Copyright (c) 2007 Justin Ryan
 * Copyright (c) 2013 Chris Verges <chris.verges@gmail.com>
 *
 * 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 expect4j;

import expect4j.matches.*;
import java.text.StringCharacterIterator;
import java.util.*;
import org.apache.oro.text.regex.MalformedPatternException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import tcl.lang.*;

/**
 * Register commands to support the Expect API
 *
 * Commands:
 * exp_continue
 * exp_internal 0;
 * expect
 * log_user 0;
 * send " ";
 * send -- "$command\r";
 * sleep
 * spawn
 * stty -echo;
 * stty echo;
 * timestamp
 *
 * Variables:
 * expect_out(1,string)
 *  through
 * expect_out(5,string)
 * expect_out(buffer)
 * spawn_id
 */
public class ExpectEmulation extends Extension {
    /**
     * Interface to the Java 2 platform's core logging facilities.
     */
    private static final Logger logger = LoggerFactory.getLogger(ExpectEmulation.class);

    //@Overrides
    public void init(Interp interp) {
        interp.createCommand("exp_continue", new ExpContinueCommand());
        interp.createCommand("exp_internal", new ExpInternalCommand());
        interp.createCommand("expect", new ExpectCommand());
        interp.createCommand("log_user", new LogUserCommand());
        interp.createCommand("send", new SendCommand());
        interp.createCommand("sleep", new SleepCommand());
        interp.createCommand("spawn", new SpawnCommand());
        interp.createCommand("stty", new SttyCommand());
        interp.createCommand("timestamp", new TimestampCommand());

        //interp.eval("rename subst ::tcl::subst");
        //interp.createCommand("subst", new tcl.lang.SubstCrCommand() );
        interp.createCommand("substcr", new tcl.lang.SubstCrCommand());

        try {
            interp.pkgProvide("expect", "2.0"); // closest equivalent
        } catch (TclException te) {
            logger.warn(te.getMessage());
        }
    }

    /**
     * expect [[-opts] pat1 body1] ... [-opts] patn [bodyn]
     *
     * TODO Fully integrate with Expect4j
     * TODO upvar the whole closure when running
     * TODO set expect_out array
     */
    public class ExpectCommand implements Command {
        void releaseClosures(Interp interp, Collection preserved) {
            Iterator iter = preserved.iterator();
            while (iter.hasNext()) {
                TclObject tclCode = (TclObject) iter.next();
                tclCode.release();
            }
        }

        public void cmdProc(Interp interp, TclObject args[]) throws TclException {
            // Do not allow empty expect statement
            if (args.length != 2)
                throw new TclNumArgsException(interp, 0, args,
                        "expect {[-opts] pat1 body1] ... [-opts] patn [bodyn]}");

            TclObject argArr = args[1];
            TclObject argv[] = TclList.getElements(interp, argArr);

            List /* <Pair> */ pairs = new ArrayList();
            int i = 0;
            String arg;
            logger.debug("Looking at expect args");
            Match pair;
            Collection preserved = new ArrayList(argv.length - i);

            while (i < argv.length) {
                arg = argv[i].toString();
                //log.info(i + " Looking at " + arg);

                if (arg.equals("timeout")) {
                    if (i + 1 >= argv.length)
                        throw new TclNumArgsException(interp, i, argv,
                                "expect [[-opts] pat1 body1] ... [-opts] patn [bodyn]");
                    TclObject tclCode = argv[++i];
                    TclClosure closure = new TclClosure(interp, tclCode);
                    pair = new TimeoutMatch(closure);

                    logger.debug("Adding Timeout Match");

                    pairs.add(pair);
                } else if (arg.equals("eof")) {
                    if (i + 1 >= argv.length)
                        throw new TclNumArgsException(interp, i, argv,
                                "expect [[-opts] pat1 body1] ... [-opts] patn [bodyn]");
                    TclObject tclCode = argv[++i];
                    TclClosure closure = new TclClosure(interp, tclCode);
                    pair = new EofMatch(closure);

                    logger.debug("Adding Eof Match");

                    pairs.add(pair);
                } else {
                    TclObject patternObj;
                    if (arg.startsWith("-")) {
                        // TODO do NumArgs check
                        patternObj = argv[++i];
                    } else {
                        patternObj = argv[i];
                        arg = "-gl"; // default to glob
                    }

                    String javaStr = patternObj.toString();
                    /*
                    StringBuffer hexString = new StringBuffer();
                    for(int j=0; j < javaStr.length(); j++ ) {
                    hexString.append( ((int) javaStr.charAt(j)) + ",");
                    }
                    log.info(i + " Characters " + hexString);
                     */
                    javaStr = javaStr.replaceAll("\\r", "\\\\r");
                    javaStr = javaStr.replaceAll("\\n", "\\\\n");
                    //interp.eval("subst -nobackslashes -nocommands {" + patternObj.toString() + "}", 0);

                    Command substCmd = interp.getCommand("substcr");
                    TclObject substArgv[] = new TclObject[] { TclString.newInstance("substcr"),
                            TclString.newInstance("-nocommands"), TclString.newInstance("-nobackslashes"),
                            patternObj };
                    substCmd.cmdProc(interp, substArgv);

                    TclObject substPatternObj = interp.getResult();
                    String pattern = substPatternObj.toString();

                    TclClosure closure = null;
                    TclObject tclCode = null;
                    if (i + 1 < argv.length) {
                        TclObject tclCodeDirect = argv[++i];
                        tclCodeDirect.preserve();
                        preserved.add(tclCodeDirect);
                        tclCode = tclCodeDirect;
                    }
                    closure = new TclClosure(interp, tclCode);

                    logger.debug(i + " Pattern Obj is >>" + javaStr + "<< and pattern is >>>" + pattern + "<<<");
                    try {
                        if (arg.startsWith("-re")) {
                            // In TCL \[ is used to tell TCL that this is not a nested command
                            /* Need to support:
                             * -re "User=(.+) Date=(.+) Time=(\[^\r]+)\r"
                             * -re "\[Pp]assword: "
                             * -re "/(\[a-z]+)/(\[0-9]+)/(\[a-z]+)\[\\$|>]"
                             * -re "/(\[a-z]+)/(\[0-9]+)/(\[a-z]+)>"
                             * -re "\\$ |....> "
                             * -re "(\[^\n]*)\r\n"
                             * -re "(\[^\r]*)\r\n"
                             */
                            pair = new RegExpMatch(pattern, closure);
                        } else if (arg.startsWith("-ex"))
                            throw new TclException(interp, "Exact matches not supported yet");
                        else if (arg.startsWith("-gl")) {
                            pair = new GlobMatch(pattern, closure);
                            logger.debug(i + " Glob at regexp " + ((GlobMatch) pair).getPattern().getPattern());
                        } else {
                            throw new TclException(interp, "Unknown type of pattern");
                        }
                    } catch (TclException te) {
                        releaseClosures(interp, preserved);
                        throw te;
                    } catch (MalformedPatternException mpe) {
                        releaseClosures(interp, preserved);
                        throw new TclException(interp, "Invalid pattern: " + pattern);
                    }
                    pairs.add(pair);
                }
                i++;

            } // end while

            // Lookup Expect
            Expect4j expect4j = expStateCurrent(interp);

            // Timeout
            try {
                TclObject timeoutObj = interp.getVar("timeout", null, 0);
                int timeout = TclInteger.get(interp, timeoutObj);
                expect4j.setDefaultTimeout(timeout * 1000);
            } catch (Exception e) {
                expect4j.setDefaultTimeout(Expect4j.TIMEOUT_DEFAULT);
            }

            boolean isDebug = isExpDebug(interp);
            boolean isEcho = isEcho(interp);
            boolean isLogUser = isLogUser(interp);
            // TODO
            //expect4j.setDebugLevels(isDebug, isEcho, isLogUser);

            // Run Expect
            int ret;
            try {
                ret = expect4j.expect(pairs);
            } catch (TclException te) {
                throw te;
            } catch (Exception e) {
                throw new TclException(interp, e.getMessage());
            } finally {
                releaseClosures(interp, preserved);
            }

            interp.setResult(ret);
        }
    }

    /* Commands in alphabetical order*/

    /**
     * exp_continue
     *
     * TODO Make calling Closure check for continue var
     */
    public class ExpContinueCommand implements Command {
        public void cmdProc(Interp interp, TclObject argv[]) throws TclException {
            if (argv.length != 1)
                throw new TclNumArgsException(interp, 0, argv, "");

            setExpContinue(interp, true);
            // TODO immeadiately set debug level on Expect4j object
        }
    }

    /**
     * exp_internal [-f file] [-info] [0|1]
     *
     * TODO find out how isExpDebug would be called
     */
    public class ExpInternalCommand implements Command {
        public void cmdProc(Interp interp, TclObject argv[]) throws TclException {
            if (argv.length != 2)
                throw new TclNumArgsException(interp, 0, argv, "[0|1]");

            String arg = argv[1].toString();
            if (arg.equals("1"))
                setExpDebug(interp, true);
            else if (arg.equals("0"))
                setExpDebug(interp, false);
            else
                throw new TclNumArgsException(interp, 0, argv, "[0|1]");
        }
    }

    /**
     * log_user [0|1]
     *
     * TODO find out how isLogUser would be called
     */
    public class LogUserCommand implements Command {
        public void cmdProc(Interp interp, TclObject argv[]) throws TclException {
            if (argv.length != 2)
                throw new TclNumArgsException(interp, 0, argv, "[0|1]");

            String arg = argv[1].toString();
            if (arg.equals("1"))
                setLogUser(interp, true);
            else if (arg.equals("0"))
                setLogUser(interp, false);
            else
                throw new TclNumArgsException(interp, 0, argv, "[0|1]");

            // TODO immeadiately set debug level on Expect4j object
        }
    }

    /**
     * Send
     */
    public class SendCommand implements Command {
        public void cmdProc(Interp interp, TclObject argv[]) throws TclException {
            logger.debug("Send Command arg # " + argv.length);
            if (argv.length == 1)
                throw new TclNumArgsException(interp, 1, argv, "[-flags] [--] string");

            boolean checkingFlags = true;
            int i = 1;
            String arg = null;
            for (; i < argv.length; i++) {
                arg = argv[i].toString();
                if (checkingFlags && arg.equals("--")) {
                    checkingFlags = false;
                    continue;
                }
                // skip flags
                if (checkingFlags && arg.startsWith("-"))
                    continue;
            }

            // Compensate for the added i++
            if (i - 1 == argv.length)
                throw new TclNumArgsException(interp, 1, argv, "[-flags] [--] string");

            //arg = interp.convertStringCRLF(arg); // TODO confirm is CRLF removal is necessary
            String pattern1 = "\r(?=[^\n])";
            String pattern2 = "\r$";
            arg = arg.replaceAll(pattern1, org.apache.commons.net.SocketClient.NETASCII_EOL);
            arg = arg.replaceAll(pattern2, org.apache.commons.net.SocketClient.NETASCII_EOL);

            Expect4j expect4j = expStateCurrent(interp);

            try {
                expect4j.send(arg);
            } catch (Exception ioe) {
                throw new TclException(interp, "Unable to send " + arg);
            }

            if (isEcho(interp))
                System.out.println(arg);
        }
    }

    /**
     * sleep seconds
     */
    public class SleepCommand implements Command {
        public void cmdProc(Interp interp, TclObject argv[]) throws TclException {
            if (argv.length > 2)
                throw new TclNumArgsException(interp, 0, argv, "seconds");

            int seconds = TclInteger.get(interp, argv[1]);
            try {
                Thread.sleep(seconds * 1000);
            } catch (InterruptedException e) {
            }
        }
    }

    /**
     * spawn [args] program [args]
     * Called with possible local paths, like C:\Windows\ssh.exe.
     */
    public class SpawnCommand implements Command, VarTrace {
        public void cmdProc(Interp interp, TclObject argv[]) throws TclException {
            if (argv.length == 1)
                throw new TclNumArgsException(interp, 0, argv, "[args] program [args]");

            // Skip the -args, e.g. -console, -ignore, -leaveopen, etc
            int i = 1;
            while (i < argv.length && argv[i].toString().indexOf('-') == 0) {
                i++;
            }

            if (i == argv.length) // missing program arg
                throw new TclNumArgsException(interp, i, argv, "[args] program [args]");
            String program = argv[i++].toString().trim();

            // Determine if we're running ssh or telnet and create Pair
            Expect4j expect4j;
            if (program.indexOf("ssh") != -1) {
                // SSH
                // Command will look like:
                // spawn $path(ssh) -l fieldsvc $chassis(scpAddr)
                String username = null;
                String password = null;
                while (argv[i].toString().indexOf('-') == 0) {
                    // process arg
                    logger.debug("ssh arg: " + argv[i].toString());
                    if (argv[i].toString().equals("-l") && i + 1 < argv.length - 1) {
                        username = argv[++i].toString();
                    }
                    if (argv[i].toString().equals("-P") && i + 1 < argv.length - 1) {
                        password = argv[++i].toString();
                    }
                    i++;
                }

                if (username == null)
                    throw new TclException(interp, "Username needs to be provided");
                else
                    logger.debug("Username: " + username);

                if (i >= argv.length)
                    throw new TclNumArgsException(interp, i - 1, argv, "[-l username] [-P password] hostname");
                String hostname = argv[i].toString().trim();

                try {
                    expect4j = ExpectUtils.SSH(hostname, username, password);
                } catch (Exception e) {
                    e.printStackTrace();
                    throw new TclException(interp, "Unable to connect using SSH");
                }
            } else if (program.indexOf("telnet") == 0) {
                // Telnet
                // spawn telnet $chassis(scpAddr) 7653
                String hostname = null;
                String portStr = null;
                if (i >= argv.length)
                    throw new TclNumArgsException(interp, i, argv, "hostname port");
                hostname = argv[i++].toString().trim();

                if (i < argv.length)
                    portStr = argv[i++].toString().trim();

                int port = 23;
                if (portStr != null) {
                    try {
                        port = Integer.parseInt(portStr);
                    } catch (NumberFormatException e) {
                        throw new TclException(interp, "Unable to parse the port number");
                    }
                }

                try {
                    expect4j = ExpectUtils.telnet(hostname, port);
                } catch (Exception e) {
                    throw new TclException(interp, "Unable to connect using Telnet");
                }
            } else {
                // Unsupported
                String[] cmdarray;
                int cmdidx = 0;
                int addlargs = argv.length - i + 1;

                // Guess OS
                String OS = System.getProperty("os.name").toLowerCase();
                if (OS.indexOf("windows 9") > -1) {
                    // Windows 98
                    cmdarray = new String[addlargs + 2];
                    cmdarray[cmdidx++] = "command.com";
                    cmdarray[cmdidx++] = "/c";
                } else if (OS.indexOf("windows") > -1) {
                    // NT
                    cmdarray = new String[addlargs + 2];
                    cmdarray[cmdidx++] = "cmd.exe";
                    cmdarray[cmdidx++] = "/c";
                } else {
                    // Unix
                    cmdarray = new String[addlargs];
                }

                // Append actual command and its args
                cmdarray[cmdidx++] = program;
                for (; i < argv.length; i++) {
                    cmdarray[cmdidx++] = argv[i].toString().trim();
                }

                try {
                    Process process = Runtime.getRuntime().exec(cmdarray);
                    expect4j = new Expect4j(process);
                } catch (Exception e) {
                    throw new TclException(interp, "Unable to start arbitary process");
                }
            }

            // Load and store lastSpawnId
            int nextId = 1;
            IntegerAssocData lastSpawnId = (IntegerAssocData) interp.getAssocData("lastSpawnId");
            if (lastSpawnId != null)
                nextId = lastSpawnId._i.intValue() + 1;
            lastSpawnId = new IntegerAssocData(nextId);
            interp.setAssocData("lastSpawnId", lastSpawnId);

            // register spawn_id with list
            MapAssocData spawnIds = (MapAssocData) interp.getAssocData("spawnIds");
            if (spawnIds == null) {
                spawnIds = new MapAssocData();
            }

            String spawnId = lastSpawnId._i.toString();
            logger.debug("Putting id in " + spawnId);
            spawnIds.put(spawnId, expect4j);
            interp.setAssocData("spawnIds", spawnIds);

            // register spawn_id with interp
            TclObject spawnIdObj = TclInteger.newInstance(nextId);
            interp.setVar("spawn_id", spawnIdObj, 0);
            interp.traceVar("spawn_id", this, 0);

            interp.setResult(spawnIdObj);
        }

        public void traceProc(Interp interp, String name1, String name2, int flags) {
            logger.debug("Tracing");
            if ((flags & TCL.TRACE_DESTROYED) != 0)
                logger.warn("Trace Destroyed");
            if ((flags & TCL.INTERP_DESTROYED) != 0)
                logger.warn("Interp Destroyed");
        }
    }

    /**
     * timestamp [-seconds NNN] [-gmt] [-format format]
     *
     * TODO support formattings
     */
    public class TimestampCommand implements Command {
        public void cmdProc(Interp interp, TclObject argv[]) throws TclException {
            if (argv.length > 1)
                throw new TclNumArgsException(interp, 0, argv, "");

            long epoch = new Date().getTime();
            if (epoch >= Double.MAX_VALUE)
                throw new TclException(interp, "Epoch is too large to convert to double");

            double epochd = new Long(epoch).doubleValue();
            TclObject result = TclDouble.newInstance(epochd);
            interp.setResult(result);
        }
    }

    /**
     * stty [-echo|echo]
     *
     * Only called in ask, which shouldn't be called in automated script mode
     */
    public class SttyCommand implements Command {
        public void cmdProc(Interp interp, TclObject argv[]) throws TclException {
            if (argv.length > 2)
                throw new TclNumArgsException(interp, 0, argv, "[echo|-echo]");

            String cmd = argv[1].toString();
            logger.debug("stty cmd is " + cmd);
            if (cmd.equals("echo"))
                setEcho(interp, true);
            else if (cmd.equals("-echo"))
                setEcho(interp, false);
            else
                throw new TclException(interp, "Only echo is supported");

            // TODO immeadiately set debug level on Expect4j object

        }
    }

    public class IntegerAssocData implements AssocData {
        public Integer _i;

        public IntegerAssocData(int value) {
            _i = new Integer(value);
        }

        public IntegerAssocData(String s) {
            _i = new Integer(s);
        }

        public void disposeAssocData(Interp interp) {
        }
    }

    public class MapAssocData extends HashMap implements AssocData {
        public void disposeAssocData(Interp interp) {
        }
    }

    /* TODO change return type to Expect4j */
    public static Expect4j expStateCurrent(Interp interp) throws TclException {
        TclObject spawnObj = interp.getVar("spawn_id", 0); // confirm that this works and we don't need TCL.NAMESPACE_ONLY

        Map spawnIds = (Map) interp.getAssocData("spawnIds");
        if (spawnIds == null)
            throw new TclException(interp, "spawn not called yet");

        String spawnId = spawnObj.toString();
        if (!spawnIds.containsKey(spawnId))
            throw new TclException(interp, "Unable to find spawn_id of " + spawnId);

        Expect4j expect4j = (Expect4j) spawnIds.get(spawnId);
        if (expect4j == null)
            throw new TclException(interp, "Unable to find Expect context from " + spawnId);

        return expect4j;
    }

    public static boolean isVar(Interp interp, String varname) throws TclException {
        TclObject obj = null;
        try {
            obj = interp.getVar(varname, null, TCL.GLOBAL_ONLY);
        } catch (Exception e) {
            return false;
        }

        if (obj == null) {
            return false;
        }

        return TclBoolean.get(interp, obj);
    }

    public static void setBooleanVar(Interp interp, String varname, boolean value) throws TclException {
        if (isVar(interp, varname) == value)
            return;

        TclObject obj = TclBoolean.newInstance(value);
        logger.debug("Setting " + varname + " to " + Boolean.toString(value));
        interp.setVar(varname, obj, TCL.GLOBAL_ONLY);
    }

    public static boolean isEcho(Interp interp) throws TclException {
        return isVar(interp, "exp_tty_echo");
    }

    public static void setEcho(Interp interp, boolean setEcho) throws TclException {
        setBooleanVar(interp, "exp_tty_echo", setEcho);
    }

    public static boolean isLogUser(Interp interp) throws TclException {
        return isVar(interp, "exp_log_user");
    }

    public static void setLogUser(Interp interp, boolean setLogUser) throws TclException {
        setBooleanVar(interp, "exp_log_user", setLogUser);
    }

    public static boolean isExpDebug(Interp interp) throws TclException {
        return isVar(interp, "exp_debug");
    }

    public static void setExpDebug(Interp interp, boolean setExpDebug) throws TclException {
        setBooleanVar(interp, "exp_debug", setExpDebug);
    }

    public static boolean isExpContinue(Interp interp) throws TclException {
        return isVar(interp, "exp_continue_set");
    }

    public static void setExpContinue(Interp interp, boolean setExpContinue) throws TclException {
        setBooleanVar(interp, "exp_continue_set", setExpContinue);
    }

    public static String escape(final String value) {
        String raw = value;
        boolean isString = false;

        if (value.indexOf('"') == 0 && value.lastIndexOf('"') == value.length() - 1) {
            isString = true;
            raw = value.substring(1, value.length() - 1);
        }

        final StringBuffer result = new StringBuffer();

        StringCharacterIterator iterator = new StringCharacterIterator(raw);
        char character = iterator.current();
        while (character != StringCharacterIterator.DONE) {
            /*
             * All literals need to have backslashes doubled.
             * &;`'"|*?~<>^()[]{}$\
             */
            switch (character) {
            case '&':
            case ';':
            case '\'':
            case '"':
            case '|':
            case '*':
            case '?':
            case '~':
            case '<':
            case '>':
            case '^':
            case '(':
            case ')':
            case '[':
            case ']':
            case '{':
            case '}':
            case '$':
            case '\\':
                result.append("\\");
            default:
                result.append(character);
            }
            character = iterator.next();
        }
        String clean = result.toString();
        if (isString)
            clean = '"' + clean + '"';

        return clean;
    }

}