Name: dbT83986 Date: 02/25/99
The project I lead needs to include a CLI for the functionality we are developing.
The CLI needs to be able to work:
. interactive session
. one-shot command (from the command line)
. script file
I decided to take advantage of StreamTokenizer to split my input.
The problem is that 'nextToken()' blocks on read() when an EOL character is encountered. This causes
my program not to process the current line that the user may have entered until the next line is
typed in, or EOF is encountered.
/*
* %W% %E%
*
* Copyright 1998 Sun Microsystems, Inc. All Rights Reserved.
*
*/
package com.sun.esm.cli;
import java.io.EOFException;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.IOException;
import java.io.StringReader;
import java.io.StreamTokenizer;
import java.util.Hashtable;
import java.util.Vector;
/**
* Basic Shell Class
*
* This is the base class to handle generic shell interaction.
*<p>
* The Shell can operate in two modes:
*<ul>
*<li>Single shot command
*<li>Interactive shell
*</ul>
* As a single shot command, the class is invoked to execute a specific
* command manifested by the argument list passed to it.
*<p>
* As an interactive shell, the user is faced with an execution
* environment through which he types requests to the shell.
*<p>
* When invoked with a pathname passed as a parameter, the class
* reads from the file as if it was from interactively, but with no prompts.
*<p>
* The class should be invoked 'in a loop'. It returns a Vector of the
* tokens that the command was made up from.
*<p>
* It is up to the invoking class to interpret the tokens and execute
* appropriately.
*
* @version %I%, %E%
*/
public class Shell
{
private static final int CLIBUFSIZE = 1024;
private static int history = -1;
private String progname = "sxsh";
private String ps1 = "sx> ";
private String ps2 = "> ";
private StreamTokenizer ist = null;
private boolean inter = false;
// this is a hash table to maintain shell variables
//
private Hashtable varTable = new Hashtable ();
/** simple constructor for the class
*
* Instantiates a Shell object.
*
* Default program name is set to "sxsh".
* Default primary prompt is set to "sx> ".
* Default secondary prompt is set to "> ".
*/
public Shell ()
{
}
/** parameterized constructor for the class
*
* @param progname name of the program/class invoking
* @param ps1 primary prompt string
* @param ps2 secondary prompt string
*/
public Shell (String progname, String ps1, String ps2)
{
this.progname = progname;
this.ps1 = ps1;
this.ps2 = ps2;
}
/** method to set the parameters for the tokenizer
*/
private void setTokEnv ()
{
// set the tokenization environment
//
ist.slashSlashComments (false);
ist.slashStarComments (false);
ist.commentChar ('#');
ist.eolIsSignificant (true);
}
/** shell handling method
*
* Executes the shell with the argument list passed.
* If the list is null, assumes interactive execution
*
* @param argv command to execute
*/
public void exec (String[] argv)
{
inter = (null == argv);
if (! inter)
{
StringBuffer sb = new StringBuffer();
for (int i = 0; i < argv.length; i++)
{
if (i > 0) sb.append (" ");
sb.append (argv[i]);
}
// convert the string into a StringBufferInputStream
//
ist = new StreamTokenizer (new StringReader (sb.toString()));
}
else
ist = new StreamTokenizer (new InputStreamReader(System.in));
// set the tokenization parameters
//
setTokEnv ();
}
/** shell handling method
*
* Executes the shell with the argument list passed.
* If the list is null, assumes interactive execution
*
* @param path where execution commands are found
*/
public void exec (String path)
{
inter = (null == path);
if (!inter)
try
{
ist = new StreamTokenizer(new FileReader (path));
}
catch (FileNotFoundException e)
{
System.err.println ("storexsh: ERROR: file " + path +
" not found");
}
else
ist = new StreamTokenizer (new InputStreamReader(System.in));
// set the tokenization parameters
//
setTokEnv ();
}
/** method to insert a variable to the shell
*
* This method accepts as input the name of a variable and
* a value to be associated with that variable
*
* @param var variable name to be assigned
* @param val value to assign to 'val'
*/
private void setVar (String var, String val)
{
if (null != var && null != val) {
varTable.put (var, val);
}
}
/** method to return the value of the 'var' that is passed
*
* This method returns the value that the variable named 'var'
* may have been assigned. If no such variable exists,
* it returns an empty string.
*
* @param var variable name whose value we are looking for
*
* @return value for the variable, empty string if none found
*/
private String getVal (String var)
{
String val = (String) varTable.get (var);
return (null == val ? "" : val);
}
/** method to do variable replacement
*
* This methods accepts a string as input. If the first character
* is a '$', then the remaining string is considered to be the
* name of a storex shell variable, and is replaced with the value
* for that variable.
*
* @param arg argument to be possibly replaced
*
*
* @return value for the variable
*/
private String varReplace (String arg)
{
String val = arg;
if (0 == arg.indexOf ('$') && arg.length() > 1)
val = getVal (arg.substring (1));
return val;
}
/** public method exposed to return the Vector that has been tokenized
*
* This method returns a Vector that contains the list of rokens
* that the tokenizer has separated
*
* @exception EOFException thrown when reached end of file on input
* @exception IOException thrown when an IO error occurred
*/
public Vector getTokens () throws EOFException, IOException
{
return shell();
}
/** method to loop executing the shell
*
* This method loops while reading input from the InputReaderStream.
* If in interactive fashion (the input reader is System.in),
* then a prompt is shown.
* If in a non-interactive fashion, no prompt is shown.
*
* Continuation lines are defined by being terminated with a '/'.
*
* Comments are identified by a '#', the remaining string is discarded.
*
* @return array of string corresponding to the arguments in the
* command line.
*/
private Vector shell () throws EOFException, IOException
{
Vector argv = null;
// loop forever (breaks on eof)
//
char[] cbuf = new char[CLIBUFSIZE];
// Vector to capture the tokens parsed
//
argv = new Vector ();
boolean eol = false; // flag for End-Of-Line
boolean eof = false; // flag for End-Of-File
String prv = null;
String val = null;
String prompt = ps1;
int token = 0;
boolean fst_in_line = true;
// iterate until the all tokens have been read.
// this is not EOL because if the last character is '\', then
// the next line of non-empty tokens has to be appended to the
// tokens accumulated so far
//
history++;
while (! eof)
{
int i = history;
// present the prompt, if interactive execution
//
if (inter) System.out.print (i + " " + prompt);
fst_in_line = true;
eol = false;
while (! eol)
{
// get the next token
//
try {
token = ist.nextToken ();
System.err.println (i + " token - " + token);
System.err.println (i + " ttype - " + ist.ttype);
}
catch (IOException e)
{
System.err.println (progname +
": ERROR: IOException caught " +
"while parsing next token");
throw (e);
}
switch (token)
{
case StreamTokenizer.TT_EOF: // leave at EOF
eof = true;
eol = true;
System.err.println (i + " --> got EOF");
break;
case StreamTokenizer.TT_EOL: // leave if EOL
System.err.println (i + " --> got EOL");
eol = true;
break;
case StreamTokenizer.TT_NUMBER:
System.err.println (i + " --> got number");
// should not happen, as we set parseNumbers(false)
break;
case StreamTokenizer.TT_WORD: // get string token
val = ist.sval;
System.err.println (i + " --> string token " + val);
break;
default:
System.err.println (i + " ttype " + ist.ttype);
switch (ist.ttype)
{
case '"': // get comment value
val = ist.sval;
System.err.println (i + " --> quoted string " +
val);
break;
case '\'': // get comment value
val = ist.sval;
System.err.println (i+ " --> quoted string " +
val);
break;
default:
System.err.println (i + " got default " +
ist.sval);
}
}
// now, handle the current element - 'prv' is never
// inserted at the discovery time, but at the beginning
// of the next line, or at the end of the stream
//
if (fst_in_line && null != val)
{
String elem = val;
if (null != prv)
{
elem = prv + val;
prv = null;
}
elem = varReplace (elem);
argv.addElement (elem);
fst_in_line = false;
}
}
// we reached here at the end of the line
//
if (argv.size() > 0)
{
if (prv != null)
{
// if it ended with '/', and is next to a non-space,
// then preserve that token to be joined to the
// next coming one
//
// sx> ls abcd efgh\$ <- EOL
// > tuv xyz
//
// should separate into:
//
// ls abcd efghtuv xyz
//
// whereas:
//
// sx> ls abcd efgh \$ <- EOL
// > tuv xyz
//
// should separate into:
//
// ls abcd efgh tuv xyz
//
if (prv.endsWith ("\\"))
{
prompt = ps2;
eol = false;
// pop prv from the stack, remove the backslash
//
int idx = argv.size();
if (idx > 0)
{
int lci = prv.length() - 2;
prv = prv.substring (0, lci);
}
}
}
}
// if a leftover 'prv' is still there, append it
//
if (null != prv)
{
String elem = null;
elem = varReplace (prv);
argv.addElement (elem);
}
// if EOF received, the user wants to exit
//
if (eof)
{
System.err.println (" reached EOF");
token = StreamTokenizer.TT_EOF;
}
}
// if reached here because of an eof, throw Exception
//
if (token == StreamTokenizer.TT_EOF)
throw (new EOFException("EOF on shell"));
// return the tokens
//
return argv;
}
/*
* Version of the implementation
*/
private static final String sccs_id = "%Z%%M% %I% %E% SMI";
}
(Review ID: 48306)
======================================================================