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: 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: