/*
 * ExecCmd.java --
 *
 *	This file contains the Jacl implementation of the built-in Tcl "exec"
 *	command.
 *
 * Copyright (c) 1997 Sun Microsystems, Inc.
 *
 * See the file "license.terms" for information on usage and
 * redistribution of this file, and for a DISCLAIMER OF ALL
 * WARRANTIES.
 * 
 * SCCS: @(#) ExecCmd.java 1.12 97/12/17 18:38:28
 */

package tcl.lang;
import java.util.*;
import java.io.*;

/*
 * This class implements the built-in "exec" command in Tcl.
 */

class ExecCmd implements Command {


/*
 *----------------------------------------------------------------------
 *
 * CmdProc --
 *
 *	This procedure is invoked to process the "exec" Tcl command.
 *	See the user documentation for details on what it does.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	See the user documentation.
 *
 *----------------------------------------------------------------------
 */

public void
cmdProc(
    Interp interp, 	// The current interpreter.
    TclObject argv[])	// The arguments to exec.
throws 
    TclException 	// A standard Tcl exception.
{
    int      i, j;
    int      firstWord;             /* Index to the first non-switch arg */
    int      argLen = argv.length;  /* No of args to copy to argStrs     */
    boolean  eof;                   /* denotes end of process instream   */
    boolean  background  = false;   /* Indicates a bg process            */
    boolean  keepNewline = false;   /* Retains newline in pipline output */
    Process  p;                     /* The exec-ed process               */
    InputStream  in;                /* Read in the process output        */
    String   os;                    /* Stores the os.name property       */
    String   argStr;                /* Conversion of argv to a string    */
    String[] argStrs;               /* String arguments for exec         */
    StringBuffer sbuf;

    /*
     * Check for a leading "-keepnewline" argument.
     */

    for (firstWord = 1; firstWord < argLen; firstWord++) {
	argStr = argv[firstWord].toString();
	if ((argStr.length() > 0) && (argStr.charAt(0) == '-')) {
	    if (argStr.equals("-keepnewline")) {
		keepNewline = true;
	    } else if (argStr.equals("--")) {
		firstWord++;
		break;
	    } else {
		throw new TclException(interp, "bad switch \"" +
			argStr + "\": must be -keepnewline or --");
	    }
	} else {
	    break;
	}
    }

    if (argLen <= firstWord) {
	    throw new TclNumArgsException(interp, 1, argv,
		"?switches? arg ?arg ...?\"");
    }

    /*
     * See if the command is to be run in background.
     */

    if ("&".equals(argv[argLen-1].toString())) {
	argLen--;
	argv[argLen] = null;
	background   = true;
    }

    /*
     * Convert all of the args from TclObjects one String, 
     * which will be passed to exec().
     */

    sbuf = new StringBuffer();
    for (i = firstWord; i < argLen; i++) {
	sbuf.append(argv[i].toString() + " ");
    }
    argStr = sbuf.toString();
    sbuf.setLength(0);

    try {
	/*
	 * It is necessary to perform system specific 
	 * operations before calling exec.  For now Solaris
	 * and Windows execs are supported, in all other cases
	 * we simply call exec and give it our "best shot"
	 */

	if (Util.isUnix()) {
	    p = execUnix(interp, argStr);
	} else if (Util.isWindows()) {
	    p = execWin(interp, argStr);
	} else {
	    p = Runtime.getRuntime().exec(argStr);
	}
	
	/*
	 * Wait for the process to finish running, then get the
	 * stdout and set the result.
	 */

	p.waitFor();
	in  = p.getInputStream();
	eof = false;
	
	while (!eof) {
	    i = in.read();
	    eof = (i == -1);
	    if (!eof) {
		sbuf.append((char)i);
	    }
	}

	/*
	 * Check for errors in the Process object's errorStream.
	 * If something is there throw a TclException with the 
	 * contents of this stream.
	 */

	checkExecError(interp, argStr, p);

	/*
	 * If the last character of interp->result is a newline, then 
	 * remove the newline character (the newline would just confuse 
	 * things).  Finally, we set pass the result to the interpreter.
	 */

	int length = sbuf.length();
	if (!keepNewline && (length > 0) &&
		(sbuf.charAt(length - 1) == '\n')) {
	    sbuf.setLength(length - 1);
	}
	
	interp.setResult(sbuf.toString());
	
    } catch (IOException e) {
	/*
	 * Generic Tcl response 
	 */

	throw new TclException(interp, "couldn't execute \"" +
		argStr + "\": no such file or directory");
    } catch (InterruptedException e) {
	/*
	 * Do Nothing...
	 */
    }
}

/*
 *----------------------------------------------------------------------
 *
 * execWin --
 *
 *	This procedure is invoked to process system specific "exec" calls for
 *	Windows.  Since new Process dosent know the current tclsh directory, a
 *	file has to be created that cds to the current interp dir, and executes
 *	the command there. 
 *
 * Results:
 *	Returns the new process.
 *
 * Side effects:
 *	See the user documentation.
 *
 *----------------------------------------------------------------------
 */

private Process 
execWin (Interp interp, String cmd) 
        throws IOException, InterruptedException {

    /*
     * Currently this dosent work...
     */

    return (Runtime.getRuntime().exec(cmd));
}

/*
 *----------------------------------------------------------------------
 *
 * execUnix --
 *
 *	This procedure is invoked to process system specific "exec" calls for
 *	Unix.  Since new Process dosent know the current tclsh directory, we
 *	exec the command in a Unix shell, but we force the Unix shell to cd to
 *	the current dir first. 
 *
 * Results:
 *	Returns the Process object returned from the exec call.
 *
 * Side effects:
 *	See the user documentation.
 *
 *----------------------------------------------------------------------
 */

private Process 
execUnix (Interp interp, String cmd) 
        throws IOException, InterruptedException {
    String[] argStrs = new String[3];   /* String arguments for exec */

    argStrs[0] = "sh";
    argStrs[1] = "-c";
    argStrs[2] = "cd " + interp.getWorkingDir().toString() + "; " + cmd;

    return(Runtime.getRuntime().exec(argStrs));
}

/*
 *----------------------------------------------------------------------
 *
 * checkExecError --
 *
 * 	Once the process has completed execution, see if any info was
 * 	written to the error stream.  If so, read in the whole message
 * 	and throw a TclException, with the contents of the error message.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	See the user documentation.
 *
 *----------------------------------------------------------------------
 */

private void 
checkExecError(Interp interp, String cmd, Process p) 
        throws TclException {
    int i;
    boolean      eof = false;
    InputStream  err;
    StringBuffer sbuf = new StringBuffer();
    
    try {
	if (p != null) {
	    err = p.getErrorStream();
	    
	    while (!eof) {
		i = err.read();
		eof = (i == -1);
		if (!eof) {
		    sbuf.append((char)i);
		}
	    }
	}
    } catch (IOException e) {
	return;
    }
    if (sbuf.length() != 0) {
	throw new TclException(interp, "couldn't execute \"" +
		cmd + "\": " + sbuf);
    }
}

} // end ExecCmd
