The Line Oriented Talker: Telnet Handler version 2
This Telnet handler cleans up the previous
version of the Telnet handler. It provides a framework for better
command parsing, polling the network for data, and using
Message objects for communication with an enhanced version of the Talker registry.
Handler.java
import java.lang.*;
import java.net.*;
import java.io.*;
import java.util.*;
public abstract class Handler implements Runnable
{
public void setConnection(Socket sockConnection)
{
this.sockConnection = sockConnection;
}
public void start()
{
if (tListener == null)
{
tListener = new Thread(this);
tListener.start();
}
}
public void run()
{
if (sockConnection != null)
{
try
{
is = sockConnection.getInputStream();
os = sockConnection.getOutputStream();
if (is != null && os != null)
{
// perform the interpretation on input and do the real
// work
handleInput();
}
}
catch (IOException e)
{
System.err.println(e.toString() + ". Handler couldn't get I/O streams on connection.");
}
}
try
{
sockConnection.close();
}
catch (IOException e)
{
System.err.println(e.toString() + ". Handler couldn't close the connection. Oh well.");
}
}
public InputStream getInputStream()
{
return is;
}
public OutputStream getOutputStream()
{
return os;
}
/**
* Handles the input. This method must be overridden by the developer.
* This method should probably implement some while() loop that handles
* input.
*/
protected abstract void handleInput();
/**
* Given a Vector of registered handlers, add itself to the registry.
* Returns true on successful registration.
*/
protected abstract boolean register(Vector vRegistry);
/**
* Given a Vector of registered handlers, remove itself from the registry.
*/
protected abstract void unregister(Vector vRegistry);
/**
* Write a message to the client.
*/
protected abstract void postMessage(Message mMessage);
InputStream is = null;
OutputStream os = null;
Socket sockConnection;
Thread tListener = null;
}
Handler Differences
The previous version of the Handler
differed by:
- Building I/O stream inside the constructor (instead of inside the
listening thread)
- Handled input directly inside the
run() method (instead of in a seperate
abstract method) and then passed to an abstract processor
- Wrote strings directly to the socket (instead of letting child classes
handle that task)
- Was unaware of a talker registry or registration methods.
The second version of the talker is superior--more complex, but also assumes
more functionality in the child of the Handler. Children are now required to
deal with a complex Message and decide what to do about I/O on
a handler-by-handler basis, rather than assuming the client always wants
Strings. This design also requires that all Handlers be aware of the
importance of the Talker registry. They can choose not to do anything with
the registry, but they do have the opportunity to be registered.
Telnet.java
import java.io.*;
import java.net.*;
import java.util.*;
import java.lang.reflect.*;
public class Telnet extends Handler
{
public Telnet()
{
super();
System.out.println("Telnet " + this + " instantiated");
}
public void handleInput()
{
// set up the I/O streams
in = new BufferedReader(new InputStreamReader(getInputStream()));
out = new BufferedWriter(new OutputStreamWriter(getOutputStream()));
discardInput();
if (login())
{
while (bLoggedIn)
{
try
{
processLine(in.readLine());
}
catch (IOException e)
{
errCheck(e);
}
}
}
disconnect();
}
public void postMessage(Message message)
{
try
{
out.write(message.getString(), 0, message.getString().length());
out.flush();
}
catch (IOException e)
{
errCheck(e);
}
}
private void errCheck(Exception e)
{
System.err.println("[Telnet handler " + this + "] " + e.toString() + " (" + (iErrors + 1) + "/" + ERROR_THRESHOLD + ")");
iErrors++;
if (iErrors > ERROR_THRESHOLD || e instanceof java.net.SocketException)
{
disconnect();
}
}
private boolean login()
{
// get the welcome message
postMessage(new Message(TalkerRegistry.getWelcomeMessage()));
discardInput();
while (!bLoggedIn)
{
// ask for the username
postMessage(new Message(TalkerRegistry.getLoginMessage()));
try
{
strUserName = in.readLine();
strUserName = strUserName.trim();
if (strUserName.length() > 0)
{
bLoggedIn = TalkerRegistry.register(this);
}
}
catch (IOException e)
{
errCheck(e);
}
}
return bLoggedIn;
}
// commands that can be prefaced with @ are declared
// userCommand (ie, userQuit, userWhisper) etc.
// each command is responsible for parsing the rest of the line
public void processLine(String strLine)
{
if (strLine != null)
{
strLine = strLine.trim();
if (strLine.length() > 0)
{
// check for the @ commands
if (strLine.indexOf('@') == 0)
{
// get this class
Class clThis = this.getClass();
Class clString = null;
Class [] claParameters = new Class[1];
try
{
clString = Class.forName("java.lang.String");
claParameters[0] = clString;
}
catch (ClassNotFoundException e)
{
errCheck(e);
}
strLine.trim();
int iBlank = strLine.indexOf(' ');
String strCommand = strLine;
String strParameters = "";;
if (iBlank != -1)
{
strCommand = firstUpper(strCommand.substring(1, iBlank));
strParameters = strLine.substring(iBlank + 1).trim();
}
else
{
strCommand = strCommand.substring(1);
}
strCommand = firstUpper(strCommand);
strCommand = "user" + strCommand;
try
{
Method methodCommand = clThis.getMethod(strCommand, claParameters);
Object [] objArgs = new Object[1];
objArgs[0] = strParameters;
methodCommand.invoke(this, objArgs);
}
catch (NoSuchMethodException e)
{
System.err.println("Invalid command: "+strCommand);
}
catch (Exception e)
{
errCheck(e);
}
}
else
{
// send a message
TalkerRegistry.postMessage(this, strLine);
}
}
}
}
public String firstUpper(String strCommand)
{
if (strCommand.length() > 1)
{
return strCommand.substring(0,1).toUpperCase() + strCommand.substring(1).toLowerCase();
}
else
{
return strCommand.toUpperCase();
}
}
public String getUserName()
{
return strUserName;
}
private void discardInput()
{
// discard any input
try
{
while (in.ready())
{
in.read(); // discard input
}
}
catch (IOException e)
{
errCheck(e);
}
}
private void disconnect()
{
if (TalkerRegistry.isRegistered(this))
{
TalkerRegistry.unregister(this);
}
bLoggedIn = false;
try
{
sockConnection.close();
}
catch (IOException e)
{
System.err.println(e.toString() + ". Telnet error closing connection.");
}
}
protected boolean register(Vector vRegistry)
{
boolean bRegistered = false;
// check to see if user name already in the registry
if (vRegistry.size() > 0)
{
// first, make a copy of the current vector
Vector vRegistryClone = (Vector)vRegistry.clone();
// now, enumerate the registered users
for (Enumeration eT = vRegistryClone.elements(); eT.hasMoreElements(); )
{
// get the next registered handler and turn it into a handler object
Telnet tObj = (Telnet)eT.nextElement();
// if the registred handler user name does not equal the prospective user name
if (!(tObj.getUserName().equals(getUserName())))
{
// register the prospective user
vRegistry.addElement(this);
bRegistered = true;
}
else
{
// give an error message
postMessage(new Message("That name is already in use. Please choose another.\n"));
}
}
}
else
{
// first login
vRegistry.addElement(this);
bRegistered = true;
}
if (bRegistered)
{
System.out.println(this + " logged in.");
TalkerRegistry.postMessage(this, "Logged in.");
}
return bRegistered;
}
public void unregister(Vector vRegistry)
{
if (vRegistry.indexOf(this) != -1)
{
vRegistry.removeElement(this);
System.out.println(this + " logged out.");
TalkerRegistry.postMessage(this, "Logged out.");
}
}
// user commands
public void userQuit(String strCommandLine)
{
bLoggedIn = false;
}
private BufferedReader in;
private BufferedWriter out;
private int iErrors = 0;
private static final int ERROR_THRESHOLD = 5;
private boolean bLoggedIn = false;
private String strUserName;
}
Ohmigod! What did he do to the Telnet handler?
It's a big step from the previous version of
the Telnet handler to this version, but it was worth it. This version:
- Telnet no longer registers when constructed (it registers when the user
is logged in)
handleInput() takes care of the I/O streams and socket input instead of
relying on the Handler parent class to provide them
- Error handling is improved by providing a threshold for errors to shut
down the Telnet handler
processLine() now dynamically looks for @commands that
match methods programmed on the Telnet handler object (uses Java reflection)
- Login, logout, and registration methods are implemented.